JavaScript的现代进阶:从ES6到ES15看这一篇就够了

🎉 博客主页:【剑九_六千里-CSDN博客】【剑九_六千里-掘金社区
🎨 上一篇文章:【JavaScript如何判断一个对象是否属于某个类?
🎠 系列专栏:【面试题-八股系列
💖 感谢大家点赞👍收藏⭐评论✍

在这里插入图片描述

在这里插入图片描述

文章目录


引言

自从ES6(ECMAScript 2015)以来,JavaScript作为一门语言经历了前所未有的变革,每年的新版本都带来了令人振奋的新特性和优化,极大地提升了开发者的生产力和代码的可维护性。本文将深入探讨从ES6ES15(ECMAScript 2024)期间JavaScript的演变历程,旨在为开发者提供一份全面的指南,涵盖语言的关键更新和实用示例。

1. ES6 (ECMAScript 2015): 现代JavaScript的起点

1.1. let 和 const

  • let 用于声明块级作用域的变量,解决了变量提升的问题。
  • const 用于声明不可重新赋值的常量,提高了代码的可预测性。
let count = 0;
const PI = 3.14;

1.2. 解构赋值

对象解构和数组解构提供了从复杂数据结构中快速提取数据的便捷方式。

const {firstName, lastName} = {firstName: 'John', lastName: 'Doe'};
console.log(firstName, lastName); // John Doe
const [first, second] = [1, 2];
console.log(first, second); // 1 2

测试:
在这里插入图片描述

1.3. 模板字符串

允许在字符串中嵌入表达式,极大地提高了字符串拼接的可读性和效率。

const name = 'Alice';
console.log(`Hello, ${name}!`); // Hello, Alice!

测试:
在这里插入图片描述

1.4. 箭头函数

提供了一种更简洁的函数定义方式,同时自动绑定this,避免了闭包中的this问题。

const add = (a, b) => a + b;
add(1, 2); // 3

测试:
在这里插入图片描述

1.5. 类

虽然JavaScript本质上是基于原型的,但类语法提供了面向对象编程的语法糖,使代码更易于理解和维护。

class Person {
    constructor(name) {
        this.name = name;
    }
    greet() {
        console.log(`Hello, ${this.name}`);
    }
}

const person = new Person('John');
person.greet()

测试:
在这里插入图片描述

1.6. 模块系统

引入了importexport关键字,实现了真正的模块化编程,提高了代码的组织性和复用性。

// myModule.js
export const PI = 3.14;

// main.js
import {PI} from './myModule.js';

1.7. Promises

为异步编程提供了一个更优雅的解决方案,替代了回调地狱,使异步代码更易于理解和调试。

const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Done!'), 1000);
});
console.log(promise)

测试:
在这里插入图片描述

1.8. Symbols

用于创建唯一属性键,避免了命名冲突,特别适用于私有属性的实现。

const uniqueId = Symbol('id');
console.log(uniqueId);

测试:
在这里插入图片描述

1.9. Array.from()

  • 从类数组对象或可迭代对象创建新的数组实例。
const arrayLike = {length: 3, 0: 'a', 1: 'b', 2: 'c'};
console.log(arrayLike)
const arr = Array.from(arrayLike);
console.log(arr)

测试:
在这里插入图片描述

1.10. Map 和 Set

Map 是一种容器,用于存储键值对。与普通的JavaScript对象不同,Map 的键可以是任意类型的值,包括函数、对象、基本类型等。Map 的一些主要特性包括:

  • 键值对的顺序是基于插入顺序的。
  • 可以通过键来快速检索值。
  • 键和值都可以是任意类型的值。
  • 提供了 get, set, delete, has, clear 等方法来操作数据。
  • Map 的大小可以通过 size 属性获取。
const myMap = new Map();
myMap.set('key1', 'value1');
myMap.set(123, 'another value');
console.log(myMap.get('key1')); // 输出: value1
console.log(myMap.size);        // 输出: 2

测试:
在这里插入图片描述

Set 是一种集合,用于存储唯一的元素列表。Set 自动确保没有重复的值。其主要特性包括:

  • 元素的顺序也是基于插入顺序的。
  • 可以检查元素是否存在,添加新元素,删除元素等。
  • Set 的元素可以是任何类型的值,但每个值只能出现一次。
  • Set 的大小同样可以通过 size 属性获取。
const mySet = new Set();
mySet.add('a');
mySet.add('b');
mySet.add('a'); // 这不会添加,因为 'a' 已经存在
console.log(mySet.has('a')); // 输出: true
console.log(mySet.size);    // 输出: 2

测试:
在这里插入图片描述

1.11. Array.of()

  • 从零个或多个参数创建新的数组实例。
const arr = Array.of(1, 2, 3);
console.log(arr)

测试:
在这里插入图片描述

1.12. 生成器函数 function* () {}

  • 可以使用yield关键字暂停和恢复函数的执行。当生成器函数被调用时,它不会立即执行,而是返回一个迭代器对象。当迭代器的next()方法被调用时,函数会从上一次暂停的地方继续执行,直到遇到下一个yield表达式或函数结束。
  • 可以返回一个序列。生成器函数可以产生一系列的值,而不是像普通函数那样只能返回一个值。
  • 可以接收外部传入的值。通过next()方法的参数,可以向生成器函数内部传递值。
function* numberGenerator() {
    yield 1; // 第一次调用next()时返回
    yield 2; // 第二次调用next()时返回
    return 'done'; // 可以返回一个最终值,但通常不使用
}

const gen = numberGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 'done', done: true }
console.log(gen.next()); // { value: undefined, done: true }

测试:
在这里插入图片描述

在这个例子中,numberGenerator是一个生成器函数,它会产生两个数字,然后结束。每次调用gen.next()都会执行生成器函数直到遇到下一个yield表达式或者函数结束,并返回一个包含valuedone属性的对象。value属性包含了yield表达式产生的值,而done属性表示生成器是否已经完成了所有的迭代。

1.10. 默认参数和剩余参数

  • 函数参数变得更加灵活,支持默认值和收集不定数量的参数。
// 参数默认值
function sum(a = 10, ...nums) {
    return a + nums.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum()); // 10

// 剩余参数
function sum(...nums) {
    return nums.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

测试:
在这里插入图片描述

1.11. String.prototype.repeat()

  • 重复字符串多次。
const str = 'abc'.repeat(3);
console.log(str); // 'abcabcabc'

测试:
在这里插入图片描述

2. ES7 (ECMAScript 2016): 数学运算的提升

2.1. 指数运算符 (**)

  • 简化了幂运算的语法。
const result = 2 ** 3;
console.log(result); // 8

测试:
在这里插入图片描述

3. ES8 (ECMAScript 2017): 异步编程的革命

3.1. 异步函数 (async/await)

  • 使异步代码看起来更像同步代码,极大地简化了异步流程的控制。
async function fetchData() {
  const response = await fetch('/data');
  return await response.json();
}

3.2. 扩展运算符 (…)

  • 用于复制数组和对象中的元素,也用于函数调用时收集参数。
const arr = [...[1, 2], ...[3, 4]];
console.log(arr)

测试:
在这里插入图片描述

3.3. Array.prototype.includes()

  • 检查数组是否包含特定元素,返回布尔值。
const arr = [1, 2, 3];
console.log(arr.includes(2)); // true

测试:
在这里插入图片描述

3.4. String.prototype.padStart() 和 String.prototype.padEnd()

  • 在字符串的开始或结束填充字符。
const str1 = '123'.padStart(7, '0'); 
console.log(str1); // '0000123'

const str2 = '123'.padEnd(7, '0'); 
console.log(str2); // '1230000'

测试:
在这里插入图片描述

4. ES9 (ECMAScript 2018): 异步迭代的引入

4.1. 异步迭代 (async iterators)

  • 支持异步遍历数据流,为处理大量数据提供了新的途径。
const asyncIterable = {
	[Symbol.asyncIterator]() {
	    return {
	        next: () => Promise.resolve({done: false, value: 1})
	    };
	}
};
console.log(asyncIterable)

测试:
在这里插入图片描述

4.2. String.prototype.trimLeft() 和 String.prototype.trimRight()

  • 左右两侧去除空白字符。
const str1 = '   hello world   ';
console.log(str1.trimLeft()); // 'hello world   '
const str2 = '   hello world   ';
console.log(str2.trimRight()); // '   hello world'

测试:
在这里插入图片描述

5. ES10 (ECMAScript 2019): 数组和字符串的增强

5.1. 扁平化数组 (flat and flatMap)

  • 提供了将多维数组扁平化到指定深度的能力。
  • 它可以接受一个可选的参数 depth,表示要展开的数组的深度。如果不提供 depth 参数,或者将其设置为 1,那么 flat 将只会展开第一层的子数组。
const arr = [1, [2, [3, [4]]]];
const flatArr = arr.flat(2);
console.log(flatArr); // [1, 2, 3, [4]]

const deepArr = [1, [2, [3, [4, [5]]]]];
const deepFlatArr = deepArr.flat(4);
console.log(deepFlatArr); // [1, 2, 3, 4, 5]

测试:
在这里插入图片描述
在这里插入图片描述

5.2. Array.prototype.flatMap()

  • Array.prototype.flatMap() 结合了 map()flat() 的功能。flatMap() 首先对数组中的每个元素应用一个映射函数,然后将结果展平一层。这使得处理和转换嵌套数组变得更加简单和高效。

语法:array.flatMap(callback(element[, index[, array]])[, thisArg])

  • flatMap() 接受一个回调函数作为参数,这个函数会被应用于数组的每个元素,并且可以返回一个新的数组。flatMap() 会自动将这些新数组展平到一个数组中,而不是返回一个嵌套数组。
  • callback: 必需。一个函数,其参数与 map() 方法相同。
  • element: 当前被处理的数组元素。
  • index: 可选。当前元素的索引。
  • array: 可选。调用 flatMap 的数组本身。
  • thisArg: 可选。指定执行回调函数时 this 的值。
const numbers = [[1, 2], [3, 4], [5, 6]];

const flattened = numbers.flatMap(x => x);

console.log(flattened); // 输出: [1, 2, 3, 4, 5, 6]

测试:
在这里插入图片描述

5.3. String.prototype.trimStart 和 trimEnd

  • 提供了更细粒度的字符串修剪功能。
const str1 = '   Hello World   ';
console.log(str1.trimStart());
const str2 = '   Hello World   ';
console.log(str2.trimEnd());

测试:
在这里插入图片描述

5.4. String.prototype.matchAll

  • 返回字符串中所有匹配正则表达式的迭代器,便于处理复杂的文本模式。
const str = 'hello world';
const regex = /l/g;
const matches = str.matchAll(regex);
for (const match of matches) {
    console.log(match);
}

测试:
在这里插入图片描述

6. ES11 (ECMAScript 2020): 高级数据操作

6.1. 可选链操作符 (?.)

  • 安全地访问深层嵌套的属性,避免了运行时错误。
const obj = {a:{b: null}};
console.log(obj.a?.b?.c); // undefined

测试:
正常访问obj.a.b.c会报错,因为c属性不存在:

在这里插入图片描述
使用可选链操作符,可正常使用:
在这里插入图片描述

6.2. 空值合并运算符 (??)

  • 当值为nullundefined时提供默认值,简化了条件判断。
let a = null;
let b = "Hello";

console.log(a ?? b); // 输出: "Hello"
console.log(0 ?? b); // 输出: 0, 因为 0 不是 null 或 undefined

测试:
在这里插入图片描述

6.3. .at() 方法

JavaScript 中,我们通常使用方括号[]来访问数组的第 i 个元素。这个过程非常简单,但实际上我们只是访问了索引为 i-1 的数组属性而已。

const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b

然而,当我们希望通过方括号来访问数组末尾的第 N 个元素时,我们需要使用索引 arr.length - N

const arr = ['a', 'b', 'c', 'd'];
// 从末尾开始第一个元素
console.log(arr[arr.length - 1]); // d
// 倒数第二个元素 console.log 
console.log(arr[arr.length - 2]); // c

借助全新的at()方法,可以以更加精简和富有表现力的方式来实现这一目标。要访问数组末尾的第N个元素,只需将负值-N作为参数传递给at()方法即可。

const arr = ['a', 'b', 'c', 'd'];
// 从末尾开始第一个元素
console.log(arr.at(-1)); // d
// 倒数第二个元素 console.log 
console.log(arr.at(-2)); // c

测试:
在这里插入图片描述

除了数组之外,字符串和TypedArray对象现在也有at()方法。

const str = 'Coding Beauty';
console.log(str.at(-1)); // y
console.log(str.at(-2)); // t
const typedArray = new Uint8Array([16, 32, 48, 64]);
console.log(typedArray.at(-1)); // 64
console.log(typedArray.at(-2)); // 48

测试:
在这里插入图片描述

6.4. BigInt 类型

  • 支持任意精度的大整数,满足了对大数字运算的需求。
  • 常规的 Number 类型在 JavaScript 中有其大小限制(最大安全整数是 2^53 - 1),而 BigInt 可以表示超出这个范围的整数。

6.4.1. 创建 BigInt:

通过后缀 n 创建:

const bigIntValue = 1234567890123456789012345678901234567890n;
console.log(bigIntValue);
console.log(typeof bigIntValue);

测试:
在这里插入图片描述

使用 BigInt() 函数转换:

const num = 1234567890123456789012345678901234567890;
const bigIntValue = BigInt(num);
console.log(bigIntValue);
console.log(typeof bigIntValue);

测试:
在这里插入图片描述

6.4.2. BigInt 的基本操作

加法、减法、乘法和除法都可以在 BigInt 类型上进行:

const a = 1234567890123456789012345678901234567890n;
const b = 9876543210987654321098765432109876543210n;

const sum = a + b; // 加法
const difference = a - b; // 减法
const product = a * b; // 乘法
const quotient = a / b; // 除法,注意除法结果仍然是一个 BigInt,但可能需要使用 `n` 后缀来表示结果,或者使用 `Math.floor` 或 `Math.round` 来获取整数部分

console.log(sum);
console.log(difference);
console.log(product);
console.log(quotient);

测试:
在这里插入图片描述

6.4.3. 注意事项

  • BigInt 不能与 Number 直接进行数学运算,除非使用 Number() 显式转换,但这会丢失 BigInt 的精度优势。
  • BigInt 的除法运算结果默认是 BigInt 类型,如果需要得到精确的小数部分,通常需要转换成字符串或其他数据类型进行处理。

6.5. Promise.allSettled

  • 等待所有Promise完成,无论结果是fulfilled还是rejected,提供了更完整的Promise控制。

具体得使用可查看JavaScript异步编程规范->实现一个符合Promise A+规范的 Promise

7. ES12 (ECMAScript 2021)

7.1. String.prototype.replaceAll

const str = 'hello world';
console.log(str.replaceAll('l', 'L')); // heLLo worLd

7.2. Promise.any 和 AggregateError

promise.any可以返回任意一个提前resolve的结果,在现实的应用中,这种情况是非常常见的,我们来模拟一个例子:

const prom1 = new Promise((resolve, reject) => {
  setTimeout(
    () => resolve("promise one"),
    Math.floor(Math.random() * 100)
  );
});
const prom2 = new Promise((resolve, reject) => {
  setTimeout(
    () => resolve("promise two"),
    Math.floor(Math.random() * 100)
  );
});
const prom3 = new Promise((resolve, reject) => {
  setTimeout(
    () => resolve("promise three"),
    Math.floor(Math.random() * 100)
  );
});

(async function() {
  const result = await Promise.any([prom1, prom2, prom3]);
  console.log(result); 
})();

上述代码可以随机输出promise onepromise twopromise three

在这里插入图片描述

如果将上述代码改成所有的都reject,那么在 nodeJS 环境 会抛出AggregateError

const prom1 = new Promise((resolve, reject) => {
  setTimeout(
    () => reject("promise one rejected"),
    Math.floor(Math.random() * 100)
  );
});
const prom2 = new Promise((resolve, reject) => {
  setTimeout(
    () => reject("promise two rejected"),
    Math.floor(Math.random() * 100)
  );
});
const prom3 = new Promise((resolve, reject) => {
  setTimeout(
    () => reject("promise three rejected"),
    Math.floor(Math.random() * 100)
  );
});

try{
(async function() {
  const result = await Promise.any([prom1, prom2, prom3]);
  console.log(result); 
})();
} catch(error) {
  console.log(error.errors);
}

测试:

在这里插入图片描述
在浏览器环境则正常:

在这里插入图片描述

7.3. WeakRef

下面WearkRef的解释来自MDN
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

示例:
这个例子演示了在一个 DOM 元素中启动一个计数器,当这个元素不存在时停止:

class Counter {
  constructor(element) {
    // Remember a weak reference to the DOM element
    this.ref = new WeakRef(element);
    this.start();
  }

  start() {
    if (this.timer) {
      return;
    }

    this.count = 0;

    const tick = () => {
      // Get the element from the weak reference, if it still exists
      const element = this.ref.deref();
      if (element) {
        element.textContent = ++this.count;
      } else {
        // The element doesn't exist anymore
        console.log("The element is gone.");
        this.stop();
        this.ref = null;
      }
    };

    tick();
    this.timer = setInterval(tick, 1000);
  }

  stop() {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = 0;
    }
  }
}

const counter = new Counter(document.getElementById("counter"));
counter.start();
setTimeout(() => {
  document.getElementById("counter").remove();
}, 5000);

7.4. 逻辑运算符和赋值表达式(&&=,||=,??=)

已知 &&|| 是被来进行逻辑操作的运算符。ES12提供了&&|| 的二元操作符:

let x = 1;
let y = 3;
x &&= y; // x = x && y; // 如果x为true,则x将被赋值为y的值
x ||= y; // x = x || y; // 如果x为false,则x将被赋值为y的值
console.log(x); // 3
console.log(y); // 3

测试:

在这里插入图片描述
还提供了??的二元操作符:

let x;
let y = 2;
x ??= y; // 判断x是不是空,如果是空那么将y的值赋给x。
console.log(x); // 2

测试:

在这里插入图片描述

7.5. 数字分隔符

这个新特性是为了方便程序员看代码而出现的,如果数字比较大,那么看起来就不是那么一目了然,比如下面的长数字:

const number= 1000000000000

一眼看不出这个数字的体量到底是多大,所以ES12提供了数字分隔符_
分隔符不仅可以分割十进制,也可以分割二净值或者十六净值的数据,非常好用。

const number = 1_000_000_000_000; // 十进制
console.log(number);
const binary = 0b1010_0101_1111_1101; // 二进制
console.log(binary);
const hex = 0xAF_BF_C3; // 十六进制
console.log(hex);

测试:

在这里插入图片描述

8. ES13 (ECMAScript 2022)

8.1. 类

ES13之前,类字段只能在构造函数中声明。与许多其他语言不同,无法在类的最外层作用域中声明或定义它们。

class Car {
    constructor() {
      this.color = 'blue';
      this.age = 2;
    }
  }
  const car = new Car();
  console.log(car.color); // blue
  console.log(car.age); // 

ES13 消除了这个限制。现在我们可以编写这样的代码:

class Car {
  color = 'blue';
  age = 2;
}
const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2

8.3. 私有方法和字段

ES13 以前,不可能在类中声明私有成员。成员传统上带有下划线( \\_)前缀,以表明它是私有的,但仍然可以从类外部访问和修改它。

class Person {
  _firstName = 'Joseph';
  _lastName = 'Stevens';  
  get name() {
    return `${this._firstName} ${this._lastName}`;
  }
}
const person = new Person();
console.log(person.name); // Joseph Stevens
// 仍可以从类外部访问, 原本打算设为私有的成员
console.log(person._firstName); // Joseph
console.log(person._lastName); // Stevens
// 也可以修改
person._firstName = 'Robert';
person._lastName = 'Becker';
console.log(person.name); // Robert Becker

测试:

在这里插入图片描述

ES13 之后,我们现在可以通过在类前面添加 ( #) 来向类添加私有字段和成员。尝试从外部访问这些属性时,nodeJS环境将会引发错误:

class Person {
    #firstName = 'Joseph';
    #lastName = 'Stevens';  
    get name() {
        return `${this.#firstName} ${this.#lastName}`;
    }
}
const person = new Person();
console.log(person.name);
console.log(person.#firstName);
console.log(person.#lastName);

测试:

在这里插入图片描述
而浏览器环境则可以正常访问:

在这里插入图片描述

8.4. await顶层操作

JavaScript 中,await 运算符用于暂停执行,直到 一个 Promise 被解决(执行或拒绝)。 以前只能在 async 中使用此运算符。ES13 以后可以在全局作用域中直接使用 await

function setTimeoutAsync(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}
//语法错误:await 仅在异步函数中有效
await setTimeoutAsync(3000);

有了 ES13,现在我们可以:

function setTimeoutAsync(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}
//  等待超时 - 没有错误抛出
await setTimeoutAsync(3000);

8.5. 静态类字段和静态私有方法

现在可以在 ES13 中为类声明静态字段和静态私有方法。静态方法可以使用关键字this访问类中的其他私有/公共静态成员,实例方法可以使用this.constructor访问他们。

class Person {
  static #count = 0;  static getCount() {
    return this.#count;
  }  
  constructor() {
    this.constructor.#incrementCount();
  }  
  static #incrementCount() {
    this.#count++;
  }
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount()); // 2

测试:

在这里插入图片描述

8.6. 类静态块

  • ES13 引入了一项特性,允许开发者定义仅在创建类时执行一次的静态块。这一特性与其他面向对象编程语言(如 C\#Java)中的静态构造函数相似

  • 在一个类的主体中,你可以定义任意数量的静态 {} 初始化块。它们会按照声明的顺序与任何交错的静态字段初始值设定项一起执行。此外,你还可以通过块中的 super 关键字访问超类的静态属性。这为开发者提供了更多的灵活性和控制能力。

class Vehicle {
  static defaultColor = 'blue';
}
class Car extends Vehicle {
  static colors = [];  
  static {
    this.colors.push(super.defaultColor, 'red');
  }  
  static {
    this.colors.push('green');
  }
}
console.log(Car.colors); // [ 'blue', 'red', 'green' ]

测试:

在这里插入图片描述
这段代码展示了如何利用类的静态属性和静态代码块来初始化类的静态属性,以及如何从派生类访问基类的静态属性。这种方式可以用于在类加载时执行一些初始化逻辑,比如预加载数据或配置类的静态成员。下面是对代码的逐行解析:

  1. 定义了一个名为Vehicle的基类,其中包含一个静态属性defaultColor,被设置为字符串'blue'
  2. 定义了一个名为Car的派生类,继承自VehicleCar类中包含一个静态数组属性colors,初始为空数组。
  3. 第一个static代码块是在类体内部定义的,这种结构允许你在类加载时执行一些初始化代码。在这个代码块中,this.colors.push(super.defaultColor,'red')被执行,将Vehicle类的静态属性defaultColor(即'blue')和字符串'red'添加到Car类的colors数组中。
  4. 第二个static代码块继续向Car类的colors数组中添加字符串'green'
  5. 最后一行代码console.log(Car.colors);打印出Car类的colors静态属性,输出结果为[ 'blue', 'red', 'green' ],这正是两次调用push方法后的数组内容。

8.7. 检查对象中的私有字段

开发者如今可以利用这一新功能,使用运算符in来方便地检查对象是否包含某个特定的私有字段。

class Car {
  #color;  
  hasColor() {
    return #color in this;
  }
}
class House {
  #color;  
  hasColor() {
    return #color in this;
  }
}
const car = new Car();
const house = new House();
console.log(car.hasColor()); // true;
console.log(car.hasColor.call(house)); // false
console.log(house.hasColor()); // true
console.log(house.hasColor.call(car)); // false

通过运算符in,可以准确区分不同类中具有相同名称的私有字段
测试:

在这里插入图片描述

8.9. 正则表达式匹配索引

ES13之前,我们只能获取字符串中正则表达式匹配的起始索引:

const str = 'sun and moon';
const regex = /and/;
const matchObj = regex.exec(str);
console.log(matchObj); // [ 'and', index: 4, input: 'sun and moon', groups: undefined ]

ES13之后,可以通过指定一个/d正则表达式标志来获取匹配开始和结束的两个索引。这一特性赋予了更多的灵活性和控制能力。

const str = 'sun and moon';
const regex = /and/d;
const matchObj = regex.exec(str);
/**
[
  'and',
  index: 4,
  input: 'sun and moon',
  groups: undefined,
  indices: [ [ 4, 7 ], groups: undefined ]
]
 */
console.log(matchObj);

测试:

在这里插入图片描述

设置标志后d,返回的对象将具有indices包含起始索引和结束索引的属性

8.10. Object.hasOwn()方法

JavaScript 中,我们可以使用Object.prototype.hasOwnProperty()方法来检查对象是否具有给定的属性。

class Car {
  color = 'green';
  age = 2;
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // true
console.log(car.hasOwnProperty('name')); // false

测试:
在这里插入图片描述

然而,这种方法存在一些问题。首先,Object.prototype.hasOwnProperty()方法并未受到保护,这意味着我们可以通过自定义的hasOwnProperty()方法来覆盖它,而这个自定义方法可能会具有与Object.prototype.hasOwnProperty()不同的行为。需要额外注意的是这一点。

class Car {
  color = 'green';
  age = 2;  // This method does not tell us whether an object of
  // this class has a given property.
  hasOwnProperty() {
    return false;
  }
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // false
console.log(car.hasOwnProperty('name')); // false

测试:

在这里插入图片描述

另外一个问题是,如果我们使用了 null 原型(通过 Object.create(null) 创建的对象),那么试图调用该方法将会产生错误。

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
// TypeError: obj.hasOwnProperty 不是函数
console.log(obj.hasOwnProperty('color'));

测试:

在这里插入图片描述

为了克服这些问题,我们可以利用属性调用方法Object.prototype.hasOwnProperty.call()来解决。具体示例如下所示:

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.prototype.hasOwnProperty.call(obj, 'color')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // false

测试:

在这里插入图片描述

这种方式并不十分便利。为了避免重复,我们可以编写一个可重用的函数,这样可以使我们的代码更加简洁和高效:

function objHasOwnProp(obj, propertyKey) {
  return Object.prototype.hasOwnProperty.call(obj, propertyKey);
}
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(objHasOwnProp(obj, 'color')); // true
console.log(objHasOwnProp(obj, 'name')); // false

测试:

在这里插入图片描述

现在不需要在那样做了,我们还可以使用全新的内置方法Object.hasOwn()来处理这个问题。它与我们之前编写的可重用函数类似,接受对象和属性作为参数,并且返回一个布尔值,如果指定的属性是对象的直接属性,则返回true;否则返回false

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.hasOwn(obj, 'color')); // true
console.log(Object.hasOwn(obj, 'name')); // false

测试:

在这里插入图片描述

8.11. 错误原因属性

现在,错误对象增加了一个cause属性,该属性用于指定导致错误抛出的原始错误。通过这种方式,我们可以为错误添加额外的上下文信息,从而更好地诊断意外的行为。要指定错误的原因,我们可以在作为构造函数的第二个参数传递Error()的对象中设置属性来实现。这种方法能够提供更丰富的错误追踪和调试信息

function userAction() {
    try {
      apiCallThatCanThrow();
    } catch (err) {
      throw new Error('New error message', { cause: err });
    }
  }
  try {
    userAction();
  } catch (err) {
    console.log(err);
    console.log(err.cause);
    console.log(`Cause by: ${err.cause}`);
  }

测试:

在这里插入图片描述

8.12. 从数组最后查找

JavaScript 中,我们已经可以使用Arrayfind()方法来查找数组中满足指定测试条件的元素。类似地,我们也可以使用findIndex()方法来获取满足条件的元素的索引值。find()findIndex()都是从数组的第一个元素开始搜索,但在某些情况下,从最后一个元素开始搜索可能会更有效。

有些情况下,我们知道从数组的末尾进行查找可能会获得更好的性能表现。例如,在这里我们尝试查找数组中prop属性等于"value"的项目。这时候,可以通过使用reverse()方法将数组反转,然后使用find()findIndex()方法来从末尾开始搜索。下面是具体的实现示例:

const letters = [
  { value: 'v' },
  { value: 'w' },
  { value: 'x' },
  { value: 'y' },
  { value: 'z' },
];
const found = letters.find((item) => item.value === 'y');
const foundIndex = letters.findIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

测试:
在这里插入图片描述

上面的代码可以获取正确结果,但由于目标对象更接近数组的尾部,如果我们使用findLast()findLastIndex()方法来从数组的末尾进行搜索,很可能能够显著提升程序的执行效率。通过这种方式,我们可以更快地找到所需的元素或索引,从而优化代码性能。

const letters = [
  { value: 'v' },
  { value: 'w' },
  { value: 'x' },
  { value: 'y' },
  { value: 'z' },
];
const found = letters.findLast((item) => item.value === 'y');
const foundIndex = letters.findLastIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

测试:
在这里插入图片描述

在一些特定的使用场景中,我们需要从数组的末尾开始搜索来获取准确的元素。举个例子,假设我们要查找数字列表中的最后一个偶数,使用find()findIndex()方法可能会导致错误的结果:

const nums = [7, 14, 3, 8, 10, 9];
// 给出 14,而不是 10
const lastEven = nums.find((value) => value % 2 === 0);
// 给出 1,而不是 4 
const lastEvenIndex = nums.findIndex((value) => value % 2 === 0);
console.log(lastEven); // 14
console.log(lastEvenIndex); // 1

测试:
在这里插入图片描述

如果我们在调用reverse()方法之前使用数组的slice()方法创建新的数组副本,就可以避免不必要地改变原始数组的顺序。然而,在处理大型数组时,这种方法可能会导致性能问题,因为需要复制整个数组。

此外,findIndex()方法在反转数组时仍然无法达到预期效果,因为元素的反转会导致它们在原始数组中的索引改变。为了获取元素的原始索引,我们需要进行额外的计算,这意味着需要编写更多的代码来处理这种情况。

const nums = [7, 14, 3, 8, 10, 9];
// 在调用reverse()之前使用展开语法复制整个数组
// calling reverse()
const reversed = [...nums].reverse();
// 正确给出 10 
const lastEven = reversed.find((value) => value % 2 === 0);
// 给出 1,而不是 4 
const reversedIndex = reversed.findIndex((value) => value % 2 === 0);
// 需要重新计算得到原始索引
const lastEvenIndex = reversed.length - 1 - reversedIndex;
console.log(lastEven); // 10
console.log(reversedIndex); // 1
console.log(lastEvenIndex); // 4

测试:
在这里插入图片描述

使用findLast()findLastIndex()方法在需要查找数组中最后一个符合条件的元素或索引时非常实用。它们能够准确地定位目标对象,并且从数组末尾开始搜索,提供了高效的解决方案。

const nums = [7, 14, 3, 8, 10, 9];
const lastEven = nums.findLast((num) => num % 2 === 0);
const lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0);
console.log(lastEven); // 10
console.log(lastEvenIndex); // 4

测试:
在这里插入图片描述

9. ES14 (ECMAScript 2023)

9.1. Array.prototype.toSorted

JavaScriptArray.prototype.sort() 具有排序的功能,但 Array.prototype.sort() 会产生副作用,会修改原数据:

let arr = [3, 5, 8, 2, 1];
console.log(arr.sort());
console.log(arr);

测试:
在这里插入图片描述
Array.prototype.toSorted 不仅可以排序,还是个纯函数,并不会改变原数据:

let arr = [3, 5, 8, 2, 1];
console.log(arr.toSorted());
console.log(arr);

测试:
在这里插入图片描述
同时 toSorted()sort() 一样,接受一个可选参数作为比较函数。例如,我们可以使用 toSorted() 创建一个按降序排列的新数组:

let arr = [3, 5, 8, 2, 1];
let toSortedArr = arr.toSorted((a, b) => a - b); // a - b 升序,b - a 降序
console.log(toSortedArr);
console.log(arr);

测试:
在这里插入图片描述
toSorted() 也可以应用于对象数组。这种情况下,需要提供一个使用对象上的数据的比较函数,因为对象没有自然的排序方式:

  1. 通过字符串排列顺序比较
const objects = [
    { name: "John", age: 30 },
    { name: "Jane", age: 25 },
    { name: "Bill", age: 40 },
    { name: "Mary", age: 20 }
];
const sortedObjects = objects.toSorted((a, b) => {
    return a.name.localeCompare(b.name);
});
console.log(sortedObjects);

测试:
在这里插入图片描述

  1. 通过age升序排列:
const objects = [
    { name: "John", age: 30 },
    { name: "Jane", age: 25 },
    { name: "Bill", age: 40 },
    { name: "Mary", age: 20 }
];
const sortedObjects = objects.toSorted((a, b) => {
    return a.age - b.age;
});
console.log(sortedObjects);

测试:
在这里插入图片描述

9.2. Array.prototype.toReversed

JavaScriptArray.prototype.reverse() 具有反转数组的功能,但 Array.prototype.sort() 会产生副作用,会修改原数据:

let arr = [3, 5, 8, 2, 1];
console.log(arr.reverse());
console.log(arr);

测试:
在这里插入图片描述

Array.prototype.toReversed 不仅可以反转数组,还是个纯函数,并不会改变原数据:

let arr = [3, 5, 8, 2, 1];
console.log(arr.toSorted());
console.log(arr);

测试:
在这里插入图片描述

9.3. Array.prototype.with

Array.prototype.with() 允许你在不修改原始数组的情况下创建一个新的数组,其中某个元素已经被替换。这个方法提供了对数组的一种不可变更新方式,这对于函数式编程风格非常有用,因为它避免了直接修改数据,从而减少了副作用。

  1. 语法
arr.with(index, newValue);
  1. 参数
  • index:你想要替换的元素的索引。
  • newValue:你想要在新数组中放置的新值。
  1. 返回值
    返回一个新的数组,其中 index 位置的元素被 newValue 替换。原始数组保持不变
    测试:

  2. 代码示例

const originalArray = [1, 2, 3];
const newArray = originalArray.with(1, 4);

console.log(originalArray); // 输出: [1, 2, 3]
console.log(newArray);      // 输出: [1, 4, 3]

测试:
在这里插入图片描述

9.4. Array.prototype.toSpliced

已知 Array.prototype.splice 返回被替换项,并可以在数组中插入一些内容,但是会改变原数组:

const arr = ["red", "orange", "yellow", "green", "blue", "purple"];
const newArr = arr.splice(2, 1, "pink", "cyan");
console.log(newArr); // ['yellow']
console.log(newArr[0]); // 'yellow'
console.log(newArr[2]); // undefined
console.log(arr); // ['red', 'orange', 'pink', 'cyan', 'green', 'blue', 'purple']

测试:
在这里插入图片描述

Array.prototype.toSpliced 是一个纯函数,返回新数组,并且不产生副作用,原数组不会被修改:

const arr = ["red", "orange", "yellow", "green", "blue", "purple"];
const newArr = arr.toSpliced(2, 1, "pink", "cyan");
console.log(newArr); // ["red", "orange", "pink", "cyan", "green", "blue", "purple"]
console.log(newArr[0]); // 'red'
console.log(newArr[2]); // 'pink'
console.log(arr); // ['red', 'orange', 'yellow', 'green', 'blue', 'purple']

测试:
在这里插入图片描述

9.5. 正式的 shebang 支持

JavaScript中,尤其是在Node.js环境中,shebang(也称为hashbang)是一种特殊的第一行注释,用于指示脚本应该使用哪种解释器或运行时环境来执行。对于Node.js脚本,shebang通常看起来像这样:

#!/usr/bin/env node

这条shebang行告诉系统使用node命令(即Node.js运行时)来执行脚本。/usr/bin/env是一个Unix/Linux实用程序,它允许你指定一个环境变量或路径查找列表中的可执行文件,这使得脚本可以在不同的系统上更灵活地运行,因为node可能安装在不同的路径下。

为了使包含shebang的脚本可执行,你需要更改文件的权限。在Unix/Linux系统中,你可以使用chmod命令来做到这一点:

chmod +x yourscript.js

之后,你就可以直接运行脚本,就像执行任何其他可执行文件一样:

./yourscript.js

请注意,在Windows系统中,shebang行不会被解释,因此你仍然需要通过node命令来运行脚本:

node yourscript.js

尽管如此,shebang行在跨平台项目中仍然有用,因为它可以确保在Unix-like系统上的行为一致。

测试:
在这里插入图片描述

9.6. Symbol 作为 WeakMap 的键

WeakMap 被用来存储 Symbol 类型的键及其对应的值。由于 Symbol 值是全局唯一的,这使得它们成为 WeakMap 键的理想选择,因为它们可以确保键的唯一性。ES14 之前,WeakMap 仅允许对象作为键值,新特性更容易创建和共享key

// 定义一个 WeakMap 来存储 Symbol 类型的键和它们被调用的次数
let map = new WeakMap();

// 使用 Symbol 的全局方法来创建一个新的唯一 symbol
const symbolKey = Symbol('uniqueKey');

// 定义一个函数来处理 symbol 并记录它被调用的次数
function useSymbol(symbol) {
    function doSomethingWith(s) {
        // 检查 s 是否为 Symbol 类型,如果是则转换为字符串描述
        const str = typeof s === 'symbol' ? s.toString() : s;
        console.log(`Doing something with ${str}`);
    }

    // 获取 symbol 对应的调用次数,如果没有则默认为 0
    let called = map.get(symbol) || 0;
    
    // 执行操作并记录调用次数
    doSomethingWith(symbol);
    map.set(symbol, ++called);
    
    // 输出当前的调用次数
    console.log(`Called: ${called} times`);
}

// 调用 useSymbol 函数
useSymbol(symbolKey);

这段代码定义了一个WeakMap来存储Symbol类型的键和它们被调用的次数。然后使用Symbol的全局方法创建了一个新的唯一Symbol作为键。useSymbol函数用于处理这个Symbol并记录它的调用次数。在函数内部,首先定义了一个doSomethingWith函数,用于执行对Symbol的操作。然后通过WeakMap获取该Symbol对应的调用次数,如果没有则默认为0。接下来执行操作并记录调用次数,最后输出当前的调用次数。通过调用useSymbol函数,可以实现对Symbol的处理和调用次数的记录。

测试:
在这里插入图片描述

10. ES15 (ECMAScript 2024)

10.1. Group By 分组

在 ES15 之前,JavaScript 并没有想 Java 那样提供分组的方法,以前如果需要实现一个分组功能,我们需要这么做:

  • 以 age 属性来分组,需要自己实现一个groupBy方法
function myGroupBy(array, iteratee) {
    return array.reduce((groups, value) => {
        const key = iteratee(value);
        if (!groups[key]) {
            groups[key] = [];
        }
        groups[key].push(value);
        return groups;
    }, {});
}

// 示例使用
const data = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 },
    { name: 'Charlie', age: 25 },
    { name: 'David', age: 30 }
];

const groupedData = myGroupBy(data, item => item.age);

console.log(groupedData);

在这个例子中,myGroupBy 函数使用了 reduce 方法来累积分组的结果。iteratee 参数是一个函数,它决定了数组中的每个元素应该如何被分组。在上面的示例中,我们按照年龄 (age) 对数据进行分组。

如果你需要使用 TypeScript,你可以添加类型注解以确保类型安全:

function myGroupBy<T, K>(array: T[], iteratee: (item: T) => K): Record<K, T[]> {
    return array.reduce((groups, value) => {
        const key = iteratee(value);
        if (!groups[key]) {
            groups[key] = [];
        }
        groups[key].push(value);
        return groups;
    }, {} as Record<K, T[]>);
}

这个版本的 myGroupBy 函数具有类型参数T K,分别表示数组元素的类型和分组键的类型。这将帮助 TypeScript 编译器推断正确的类型并在开发过程中提供更好的类型检查。

测试:

在这里插入图片描述
ES15 之后,直接就可以使用自带的 groupBy 方法来实现:

const data = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 },
    { name: 'Charlie', age: 25 },
    { name: 'David', age: 30 }
];

const groupedData = groupBy(data, item => item.age);

console.log(groupedData);

测试:

在这里插入图片描述

10.2. Math.sign()

Math.sign() 方法用于判断一个数的符号,即正负性:

  • 如果参数是正数,返回1
  • 如果参数是零,返回0
  • 如果参数是-0(负零),返回-0
  • 如果参数不是数字(NaN),返回NaN
let a = 2;
let b = +3;
let c = -4;
let d = 0;
let e = -0;
let f = NaN;
console.log(Math.sign(a)); // 1
console.log(Math.sign(b)); // 1
console.log(Math.sign(c)); // -1
console.log(Math.sign(d)); // 0
console.log(Math.sign(e)); // -0
console.log(Math.sign(f)); // NaN

测试:
在这里插入图片描述

10.3. Promise.withResolvers

Promise.withResolvers,这是一个Promise API扩展,它允许你创建一个Promise对象,同时立即获得用于解析或拒绝该Promise的函数。这与传统的Promise构造函数不同,在传统构造函数中,你需要立即调用解析或拒绝的函数,而在Promise.withResolvers中,你可以稍后调用这些函数。

Promise.withResolvers返回一个对象,其中包含以下属性:

  • promise:新创建的Promise对象。
  • resolve:一个函数,用于在适当的时候解析Promise
  • reject:一个函数,用于在适当的时候拒绝Promise

例如:

const { promise, resolve, reject } = Promise.withResolvers();
// ...做一些异步操作...
// 当异步操作完成时,调用resolve或reject函数
resolve('Success!');
// 或者
reject(new Error('Failed!'));

10.4. 正则表达式标志 /v

在正则表达式中,通常使用的标志有:

  • g:全局匹配,查找所有匹配项,而不仅仅是第一个。
  • i:忽略大小写。
  • m:多行模式,使 ^$ 能够匹配每行的开始和结束,而不仅仅是整个字符串的开始和结束。
  • s:点号(.)匹配所有字符,包括换行符。
  • uUnicode模式,使正则表达式能够正确处理Unicode字符。
  • y:粘性匹配,确保正则表达式从字符串的当前位置开始匹配,而不是从任何位置开始。

ES15 新增了 /v标识符,v 标志是 u 标志的“升级”,可启用更多与 Unicode 相关的功能。使用 v 标志,不光可以继承 u 标志所有的功能,还支持以下功能

  • 扩展的集合符号,允许计算字符或字符串集合的差异、相交和联合
const reg1 = /[\p{Decimal_Number}--[0-9]]/v; // Non-ASCII decimal digits
const reg2 = /[\p{ASCII}&&\p{Letter}]/v; // ASCII letters
const reg3 = /[[\p{ASCII}&&\p{Letter}]\p{Number}]/v; // ASCII letters, or any digit
console.log(reg1.test("1"));
console.log(reg2.test("1"));
console.log(reg3.test("1"));

测试:
在这里插入图片描述

  • 字符串的属性,允许使用\p 转义的多节点属性
const reg4 = "Did you see the 👩🏿‍❤️‍💋‍👩🏾 emoji?".match(/\p{RGI_Emoji}/v) // ["👩🏿‍❤️‍💋‍👩🏾"]
console.log(reg4);

测试:
在这里插入图片描述

  • 集合中的多节点字符串,使用一个新的\q 转义
const reg5 = /[\r\n\q{\r\n|NEWLINE}]/v; // Matches \r, \n, \r\n or NEWLINE
console.log(reg5);
console.log(reg5.test("\r"));
console.log(reg5.test("\r\n"));

测试:
在这里插入图片描述

10.5. ArrayBuffers 和 SharedArrayBuffers 的新功能

10.5.1. ArrayBuffers

JavaScript中,ArrayBuffer 是一种用于表示原始二进制数据的类数组对象。它提供了固定长度的缓冲区,用于存储二进制数据。ArrayBuffer 不直接操作数据,而是通过视图(如 TypedArrayDataView)来读取和写入数据。

ArrayBuffer 的特性:

  • 原始数据存储ArrayBuffer 提供了一块连续的、固定大小的内存区域,可以用来存储二进制数据。
  • 低级接口:与 Blob 这样的高级接口不同,ArrayBuffer 更接近底层内存操作。
  • 视图ArrayBuffer 通常与 TypedArray(如 Int8Array, Uint8Array, Float32Array 等)或 DataView 一起使用,以不同的数据类型和字节序来访问缓冲区中的数据。
  • 共享内存:在 Web Workers 或其他多线程环境中,ArrayBuffer 可以被多个执行环境共享,这在 SharedArrayBuffer 中表现得更为明显,后者支持真正的共享内存操作。

使用示例:

const buffer = new ArrayBuffer(8); // 创建一个 8 字节的 ArrayBuffer
console.log(buffer, 'buffer');
const view = new Uint8Array(buffer); // 创建一个视图,可以按无符号 8 位整数读写数据
console.log(view, 'view');

view[0] = 1; // 写入数据
console.log(view[0], 'view[0]'); // 读取数据

测试:

在这里插入图片描述

  1. ArrayBuffers 就地调整大小
    ES15 之前 ArrayBuffer 的大小一旦创建后就不能改变,ES15 之后 ArrayBuffer 可通过
    resize 方法就地调整大小:
// 创建一个 8 字节的 ArrayBuffer
const buffer = new ArrayBuffer(8); 
console.log(buffer.maxByteLength, 'buffer.maxByteLength');
// 获取ArrayBuffer的长度
console.log(buffer.byteLength); 
// 修改buffer的长度
buffer.resize(16);
// 获取ArrayBuffer的长度
console.log(buffer.byteLength);

测试:
在这里插入图片描述
此时可以看到报错了,这是因为此时创建的这个 ArrayBuffer 最大为 8 字节,当我们修改大小时超过了 ArrayBuffer 最大字节,修改如下,给 ArrayBuffer 传递第二个参数,手动设置最大长度:

// 创建一个 8 字节的 ArrayBuffer;{maxByteLength: 32} 设置最大长度
const buffer = new ArrayBuffer(8, {maxByteLength: 32}); 
console.log(buffer.maxByteLength, 'buffer.maxByteLength');
// 获取ArrayBuffer的长度
console.log(buffer.byteLength); 
// 修改buffer的长度
buffer.resize(16);
// 获取ArrayBuffer的长度
console.log(buffer.byteLength);

测试:

在这里插入图片描述

  1. ArrayBuffers .transfer() 可转移
    ES15 之前也是可以进行转移的,之前是通过参数进行配置的,现在对外提供了一个 transfer 函数调用,更加方便了:
// 创建一个 8 字节的 ArrayBuffer
const buffer = new ArrayBuffer(8);
console.log(buffer.detached, 'buffer创建');

// buffer 转移
const transferred = buffer.transfer();

console.log(buffer.detached, 'buffer转移');
console.log(transferred.detached, 'transferred.detached');

测试:

在这里插入图片描述
detached 属性是一个访问器属性,其 set 访问器函数是 undefined,这意味着你只能读取此属性。该属性的值在创建 ArrayBuffer 时设置为 false。如果 ArrayBuffer 已被传输,则该值将变为 true,这将使该实例从其底层内存中分离。一旦缓冲区被分离,它就不再可用。

10.5.2. SharedArrayBuffers

SharedArrayBufferArrayBuffer 的一个子类,设计用于实现共享内存,使得多个 JavaScript 执行上下文(例如 Web Workers)可以在同一段内存上进行读写操作。这在多线程编程中非常有用,尤其是在需要高性能数据共享和同步的场景下。
SharedArrayBuffer 特性:

  • 共享内存SharedArrayBuffer 实例表示的缓冲区可以在多个执行上下文中共享,允许线程间通信而无需复制数据。
  • 持久性SharedArrayBuffer 不会被垃圾回收器回收,除非整个页面被卸载或者 SharedArrayBuffer 被撤销(如通过 Atomics.waitAbort)。

使用示例一:

// 创建一个 8 字节的 SharedArrayBuffer
const sab = new SharedArrayBuffer(8); 
console.log(sab, 'sab');

// 创建一个视图
const i32a = new Int32Array(sab); 
console.log(i32a, 'i32a');

i32a[0] = 42; // 写入数据
console.log(i32a[0]); // 读取数据

const transferred = sab.transfer();
console.log(transferred, 'transferred');

测试:

在这里插入图片描述

SharedArrayBuffers 可以调整大小,但它们只能增长而不能缩小。它们不可转移,因此无法获取 ArrayBuffers 的方法 .transfer()

使用示例二(在多线程中使用):

// 在主线程中
const worker = new Worker('worker.js');
const sab = new SharedArrayBuffer(8);
const i32a = new Int32Array(sab);
i32a[0] = 42;
worker.postMessage({arrayBuffer: sab}, [sab]);

// 在 worker.js 中
self.onmessage = function(event) {
    const receivedSab = event.data.arrayBuffer;
    const receivedI32a = new Int32Array(receivedSab);
    console.log(receivedI32a[0]); // 输出应该是 42
};

在这个例子中,postMessage 的第二个参数是一个数组,包含了要共享所有权的 ArrayBufferSharedArrayBuffer 对象。这样,Worker 就可以直接访问和修改共享内存了。

10.6. String.prototype.isWellFormed

String.prototype.isWellFormed() 方法返回一个表示该字符串是否包含单独代理项的布尔值,如果字符串不包含单独代理项,返回 true,否则返回 falseJavaScript 中的字符串是 UTF-16 编码的。UTF-16 编码中有代理对的概念。

字符串基本上表示为 UTF-16 码元的序列。在 UTF-16 编码中,每个码元都是 16 位长。这意味着最多有 216 个或 65536 个可能的字符可表示为单个 UTF-16 码元。

然而,整个 Unicode 字符集比 65536 大得多。额外的字符以**代理对(surrogate pair)**的形式存储在 UTF-16 中,代理对是一对 16 位码元,表示一个单个字符。为了避免歧义,配对的两个部分必须介于 0xD8000xDFFF 之间,并且这些码元不用于编码单码元字符。

前导代理,也称为高位代理,其值在 0xD8000xDBFF 之间(含),而后尾代理,也称为低位代理,其值在 0xDC00 和 0xDFFF 之间(含)。

“单独代理项(lone surrogate)”是指满足以下描述之一的 16 位码元:

  • 它在范围 0xD8000xDBFF 内(含)(即为前导代理),但它是字符串中的最后一个码元,或者下一个码元不是后尾代理。
  • 它在范围 0xDC000xDFFF 内(含)(即为后尾代理),但它是字符串中的第一个码元,或者前一个码元不是前导代理。
const strings = [
  // 单独的前导代理
  "ab\uD800",
  "ab\uD800c",
  // 单独的后尾代理
  "\uDFFFab",
  "c\uDFFFab",
  // 格式正确
  "abc",
  "ab\uD83D\uDE04c",
];

for (const str of strings) {
  console.log(str.isWellFormed());
}
// 输出:
// false
// false
// false
// false
// true
// true

测试:

在这里插入图片描述

避免 encodeURI() 错误:
如果传递的字符串格式不正确, encodeURI 会抛出错误。可以通过使用 isWellFormed() 在将字符串传递给 encodeURI() 之前测试字符串来避免这种情况。

const illFormed = "https://example.com/search?q=\uD800";

try {
  encodeURI(illFormed);
} catch (e) {
  console.log(e); // URIError: URI malformed
}

if (illFormed.isWellFormed()) {
  console.log(encodeURI(illFormed));
} else {
  console.warn("Ill-formed strings encountered."); // Ill-formed strings encountered.
}

10.7. String.prototype.toWellFormed

如果你需要将字符串转换为格式正确的字符串,可以使用 toWellFormed() 方法。toWellFormed() 方法返回一个字符串,其中该字符串的所有单独代理项都被替换为 Unicode 替换字符 U+FFFD

toWellFormed() 方法返回新的字符串是原字符串的一个拷贝,其中所有的单独代理项被替换为 Unicode 替换字符 U+FFFD。如果 str 是格式正确的,仍然会返回一个新字符串(本质上是 str 的一个拷贝)。

const strings = [
 // 单独的前导代理
 "ab\uD800",
 "ab\uD800c",
 // 单独的后尾代理
 "\uDFFFab",
 "c\uDFFFab",
 // 格式正确
 "abc",
 "ab\uD83D\uDE04c",
];

for (const str of strings) {
 console.log(str.toWellFormed());
}
// Logs:
// "ab�"
// "ab�c"
// "�ab"
// "c�ab"
// "abc"
// "ab😄c"

测试:
在这里插入图片描述
避免 encodeURI() 错误:
如果传递的字符串格式不正确, encodeURI 会抛出错误。可以先通过使用 toWellFormed() 将字符串转换为格式正确的字符串来避免这种情况。

const illFormed = "https://example.com/search?q=\uD800";

try {
  encodeURI(illFormed);
} catch (e) {
  console.log(e); // URIError: URI malformed
}

console.log(encodeURI(illFormed.toWellFormed())); // "https://example.com/search?q=%EF%BF%BD"

测试:

在这里插入图片描述

10.8. Atomics.waitAsync()

Atomics.waitAsync 对应的方法是 Atomics.wait,它们都是在等待 SharedArrayBuffer 所代表的共享内存的某个位置的值变化。

Atomics.wait 是让线程睡眠来等待,从而致使线程阻塞,这种阻塞在 UI 线程是不可接受的,它会导致整个页面卡死无法响应,因此如果你在 UI 主线程调用 Atomics.wait ,浏览器会抛出异常。

Atomics.waitAsync() 静态方法异步等待共享内存的特定位置并返回一个 Promise

备注: 此操作仅适用于基于 SharedArrayBufferInt32ArrayBigInt64Array 视图。

语法:

Atomics.waitAsync(typedArray, index, value)
Atomics.waitAsync(typedArray, index, value, timeout)

参数:

  • typedArray:基于 SharedArrayBufferInt32ArrayBigInt64Array

  • indextypedArray 中要等待的位置。

  • value:要测试的期望值。

  • timeout (可选):等待时间,以毫秒为单位。NaN(以及会被转换为 NaN 的值,例如 undefined)会被转换为 Infinity。负值会被转换为 0

返回值:
一个 Object,包含以下属性:

  • async:一个布尔值,指示 value 属性是否为 Promise

  • value:如果 asyncfalse,它将是一个内容为 "not-equal""timed-out" 的字符串(仅当 timeout 参数为 0 时)。如果 asynctrue,它将会是一个 Promise,其兑现值为一个内容为 "ok""timed-out" 的字符串。这个 promise 永远不会被拒绝。

异常:

  • TypeError:如果 typedArray 不是一个基于 SharedArrayBufferInt32ArrayBigInt64Array,则抛出该异常。

  • RangeError:如果 index 超出 typedArray 的范围,则抛出该异常。

使用示例:
给定一个共享的 Int32Array

const sab = new SharedArrayBuffer(1024);
const int32 = new Int32Array(sab);

另一个读取线程休眠并在位置 0 处等待,预期该位置的值为 0result.value 将是一个 promise

const result = Atomics.waitAsync(int32, 0, 0, 1000);
// { async: true, value: Promise {<pending>} }

在该读取线程或另一个线程中,对内存位置 0 调用以令该 promise 解决为 "ok"

Atomics.notify(int32, 0);
// { async: true, value: Promise {<fulfilled>: 'ok'} }

如果它没有解决为 "ok",则共享内存该位置的值不符合预期(value 将是 "not-equal" 而不是一个 promise)或已经超时(该 promise 将解决为 "time-out")。

11. 结语

从ES6到ES15,JavaScript的发展轨迹清晰地展示了其作为一门语言的成熟过程。新特性的引入不仅丰富了语言本身,也为开发者提供了更多的工具和表达方式,使得JavaScript成为构建现代Web应用的首选语言。掌握这些新特性,不仅能提高代码质量和开发效率,还能让你的项目更具竞争力。随着未来版本的继续演进,JavaScript的潜力无限,值得我们持续关注和学习。

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

剑九_六千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值