Jansiel Notes

整理了ES7到ES14的新特性

ES7(ECMAScript2016)

Array.prototype.includes()

判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

该方法有两个参数,第一个参数为要查找的值,第二个参数(可选)是开始搜索的索引(默认从0开始)

第二个参数(下面简称 fromIndex)有以下特点

  • fromIndex<0时,相当于从 fromIndex + array.length 开始搜索,还是从前往后进行搜索
  • fromIndex < -array.length时,相当于从0检索整个数组
  • fromIndex >= array.length,则不会搜索数组并返回 false
 1const arr1 = [1, 2, 3];
 2console.log(arr1.includes(1)); // true
 3console.log(arr1.includes(1, 1)); // false
 4console.log(arr1.includes(2, 1)); // true
 5console.log(arr1.includes(2, -1)); // false
 6console.log(arr1.includes(2, -2)); // true
 7console.log(arr1.includes(2, -100)); // true
 8console.log(arr1.includes(2, 3)); // false
 9console.log(arr1.includes(2, 100)); // false
10const arr2 = ["a", "b", "c"];
11console.log(arr2.includes("a")); // true
12console.log(arr2.includes("d")); // false
13

幂运算符

返回第一个操作数取第二个操作数的幂的结果,即 2 ** 3 表示求2的3次方,等价于 Math.pow(),不同之处在于,它还接受 BigInt 作为操作数。

1console.log(2 ** 3); // 8
2console.log(BigInt(9007199254740991) ** BigInt(2));
3// 81129638414606663681390495662081n
4

ES8(ECMAScript2017)

async、await

asyncawait 关键字是基于 Generator 函数的语法糖,使得我们可以更简洁地编写基于 promise 的异步代码。

async function 声明创建一个绑定到给定名称的新异步函数。每次调用该异步函数时,都会返回一个新的 Promise 对象。该函数可以包含零个或者多个 await 表达式。 await 用于等待一个异步方法执行完成,只有当异步完成后才会继续往后执行。

 1const fn1 = () =>
 2  new Promise((resolve) => {
 3    setTimeout(() => {
 4      resolve(1);
 5    }, 500);
 6  });
 7
 8const fn2 = () =>
 9  new Promise((resolve) => {
10    setTimeout(() => {
11      resolve(2);
12    }, 500);
13  });
14
15const asyncFn = async () => {
16  let res1 = await fn1();
17  console.log(res1);
18  let res2 = await fn2();
19  console.log(res2);
20  console.log(3);
21};
22
23asyncFn();
24// 结果会依次输出1,2,3
25

Async 函数总是返回一个 promise。如果其返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。

 1async function foo() {
 2  return 1;
 3}
 4
 5// 上面的函数类似于下面的函数
 6
 7function foo() {
 8  return Promise.resolve(1);
 9}
10

Object.values()

返回一个给定对象的自有可枚举字符串键属性值组成的数组。

1const obj = {
2  a: 123,
3  b: "something",
4  c: true,
5};
6
7console.log(Object.values(obj));
8// [ 123, 'something', true ]
9

Object.entries()

返回一个数组,包含给定对象自有的可枚举字符串键属性的键值对。

1const obj = {
2  a: 123,
3  b: "something",
4  c: true,
5};
6
7console.log(Object.entries(obj));
8// [ [ 'a', 123 ], [ 'b', 'something' ], [ 'c', true ] ]
9

Object.getOwnPropertyDescriptors()

返回给定对象的所有自有属性(包括不可枚举属性)的属性描述符。

 1const obj = {
 2  a: 123,
 3};
 4
 5console.log(Object.getOwnPropertyDescriptors(obj));
 6/*
 7{
 8  a: {
 9    value: 123, // 与属性关联的值
10    writable: true, // 值可以更改时,为 true
11    enumerable: true, // 当且仅当此属性在相应对象的属性枚举中出现时,为 true
12    configurable: true, // 当且仅当此属性描述符的类型可以更改且该属性可以从相应对象中删除时,为 true
13  },
14};
15*/
16

String.prototype.padStart()

用指定字符串从当前字符串的头部填充(如果需要会重复填充),直到达到给定的长度。返回新字符串。

该方法有两个参数

  • 第一个参数表示填充后字符串的长度。
  • 第二个参数为可选(默认为空格),表示用于填充当前 str 的字符串。
 1const str1 = "abc";
 2const str2 = str1.padStart(5, "-");
 3const str3 = str1.padStart(5);
 4const str4 = str1.padStart(2, "-"); // 如果第一个参数小于或等于字符串的长度,则会直接返回当前字符串
 5const str5 = str1.padStart(4, "ab"); // 第二个参数过长无法适应给定的长度,则会被截断
 6console.log(str1); // 'abc'
 7console.log(str2); // '--abc'
 8console.log(str3); // '  abc'
 9console.log(str4); // 'abc'
10console.log(str5); // 'aabc'
11

String.prototype.padEnd()

与String.prototype.padStart()类似,用指定字符串从当前字符串的尾部填充(如果需要会重复填充),直到达到给定的长度。返回新字符串。

该方法有两个参数

  • 第一个参数表示填充后字符串的长度。
  • 第二个参数为可选(默认为空格),表示用于填充当前 str 的字符串。
 1const str1 = "abc";
 2const str2 = str1.padEnd(5, "-");
 3const str3 = str1.padEnd(5);
 4const str4 = str1.padEnd(2, "-"); // 如果第一个参数小于或等于字符串的长度,则会直接返回当前字符串
 5const str5 = str1.padEnd(4, "ab"); // 第二个参数过长无法适应给定的长度,则会被截断
 6console.log(str1); // 'abc'
 7console.log(str2); // 'abc--'
 8console.log(str3); // 'abc  '
 9console.log(str4); // 'abc'
10console.log(str5); // 'abca'
11

ES9(ECMAScript2018)

for await...of

for await...of 语句创建一个循环,该循环遍历 异步可迭代对象 以及 同步可迭代对象。该语句只能在可以使用 await 的上下文中使用。

 1const fn = (value) => {
 2  return new Promise((resolve, reject) => {
 3    setTimeout(() => {
 4      resolve(value);
 5    }, 1000);
 6  });
 7};
 8
 9const arr = [fn(1), fn(2), fn(3)];
10
11async function asyncFn() {
12  for await (let item of arr) {
13    console.log(item);
14    console.log("--");
15    if (item === 2) {
16      break; // 关闭迭代器,触发返回
17    }
18  }
19}
20
21asyncFn();
22// 控制台打印如下
23// 1
24// --
25// 2
26// --
27
 1// 同步可迭代也可以
 2const arr = [1, 2, 3];
 3
 4async function asyncFn() {
 5  for await (let item of arr) {
 6    console.log(item);
 7  }
 8}
 9
10asyncFn();
11
 1// 由于异步生成器函数的返回值符合异步可迭代协议,因此可以使用 for await...of 来迭代它们。
 2async function* asyncGenerator() {
 3  let i = 0;
 4  while (i < 3) {
 5    yield i++;
 6  }
 7}
 8
 9(async () => {
10  for await (const num of asyncGenerator()) {
11    console.log(num);
12  }
13})();
14// 0
15// 1
16// 2
17

Symbol.asyncIterator 符号可以指定一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于 for await...of 循环。

 1const obj = {
 2  count: 0,
 3  [Symbol.asyncIterator]() {
 4    const self = this;
 5    return {
 6      next() {
 7        self.count++;
 8        return Promise.resolve({
 9          done: self.count > 3,
10          value: self.count,
11        });
12      },
13    };
14  },
15};
16
17(async () => {
18  for await (const item of obj) {
19    console.log(item);
20  }
21})();
22// 1
23// 2
24// 3
25
 1const myAsyncIterable = new Object();
 2myAsyncIterable[Symbol.asyncIterator] = async function* () {
 3  yield "hello";
 4  yield "async";
 5  yield "iteration!";
 6};
 7
 8(async () => {
 9  for await (const x of myAsyncIterable) {
10    console.log(x);
11  }
12})();
13//  "hello"
14//  "async"
15//  "iteration!"
16

展开语法 (Spread syntax)

 1const obj1 = {
 2  a: 1,
 3  b: 2,
 4};
 5
 6const obj2 = {
 7  ...obj1,
 8  c: 3,
 9};
10
11console.log(obj2); // { a: 1, b: 2, c: 3 }
12

剩余语法 (Rest syntax)

1const fn = (a, ...rest) => {
2  console.log(a); // 1
3  console.log(rest); // [ 2, 3, 4 ]
4};
5
6fn(1, 2, 3, 4);
7
 1const obj1 = {
 2  a: 1,
 3  b: 2,
 4  c: 3,
 5};
 6
 7const { a, ...rest } = obj1;
 8console.log(a); // 1
 9console.log(rest); // { b: 2, c: 3 }
10

Promise.prototype.finally()

Finally 可以注册一个方法并返回一个 Promise,在 promise 执行结束时,无论结果是 fulfilled 或者是 rejected 都会执行 finally 指定的回调函数。可以避免在 promise 的 then()catch() 处理器中重复编写代码。

 1function checkMail() {
 2  return new Promise((resolve, reject) => {
 3    if (Math.random() > 0.5) {
 4      resolve('Mail has arrived');
 5    } else {
 6      reject(new Error('Failed to arrive'));
 7    }
 8  });
 9}
10
11checkMail()
12  .then((mail) => {
13    console.log(mail);
14  })
15  .catch((err) => {
16    console.error(err);
17  })
18  .finally(() => {
19    console.log('Experiment completed');
20  });
21

正则表达式具名捕获组

(?<Name>x),具名捕获组,匹配"x"并将其存储在返回的匹配项的 groups 属性中,该属性位于 <Name> 指定的名称下。尖括号 ( <>) 用于组名。

1const reg = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
2const match = reg.exec("2021-02-23");
3console.log(match.groups["year"]); // 2021
4console.log(match.groups["month"]); // 02
5console.log(match.groups["day"]); // 23
6

ES10(ECMAScript2019)

String.prototype.trimStart()

StringtrimStart() 方法会从字符串的开头移除空白字符,并返回一个新的字符串,而不会修改原始字符串。

trimLeft() 是该方法的别名。目前已被标为弃用。

1const str = " hello world ";
2console.log(str.trimStart());// "hello world "
3

String.prototype.trimEnd()

StringtrimEnd() 方法会从字符串的结尾移除空白字符,并返回一个新的字符串,而不会修改原始字符串。 trimRight() 是该方法的别名。目前已被标为弃用。

1const str = " hello world ";
2console.log(str.trimEnd()); // " hello world"
3

Array.prototype.flat()

flat() 方法创建一个新的数组,并根据指定深度递归地将所有子数组元素拼接到新的数组中。

该方法有一个可选参数depth,指定要提取嵌套数组的结构深度,默认值为 1。

1const arr = [1, [2, 3], 4, 5];
2console.log(arr.flat()); // [ 1, 2, 3, 4, 5 ]
3const arr2 = [1, [2, [3, 4]], 5, 6];
4console.log(arr2.flat()); // [ 1, 2, [ 3, 4 ], 5, 6 ]
5console.log(arr2.flat(2)); // [ 1, 2, 3, 4, 5, 6 ]
6const arr3 = [1, [2, [3, 4]], [5, [6, [7, [8, [9]]]]]];
7console.log(arr3.flat(Infinity)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
8

Array.prototype.flatMap()

flatMap() 方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。

它等价于在调用 map() 方法后再调用深度为 1 的 flat()arr.map(...args).flat()),但比分别调用这两个方法稍微更高效一些。

 1const arr = [1, [2, 3], 4, 5];
 2function flatMapArr(list) {
 3  return list.flatMap((e) => {
 4    if (e.length) {
 5      return flatMapArr(e);
 6    } else {
 7      return e * 2;
 8    }
 9  });
10}
11console.log(flatMapArr(arr)); // [ 2, 4, 6, 8, 10 ]
12
13const arr = [1, 2, 3, 4];
14console.log(
15  arr.flatMap((e) => {
16    if (e % 2 === 0) {
17      return [e, e * 2];
18    } else {
19      return e;
20    }
21  })
22); // [ 1, 2, 4, 3, 4, 8 ]
23

Function.prototype.toString()

Function也是对象,对象有toString() 的方法(Object.prototype.toString()),Function对象重写了 toString() 方法,而没有继承 toString

对于用户定义的 Function 对象, toString 方法返回一个字符串,其中包含用于定义函数的源文本段。

 1function fn(a, b) {
 2  // 注释文本
 3  return a + b;
 4}
 5
 6console.log(fn.toString());
 7/**
 8function fn(a, b) {
 9  // 注释文本
10  return a + b;
11}
12 */
13
14const fn2 = (a, b) => {
15  // 我是注释
16  return a + b;
17};
18console.log(fn2.toString());
19/**
20(a, b) => {
21  // 我是注释
22  return a + b;
23}
24 */
25

Object.fromEntries()

与上面的Object.entries()相反, Object.fromEntries() 静态方法将键值对列表转换为一个对象。

 1const arr = [
 2  ["a", 1],
 3  ["b", 2],
 4  ["c", 3],
 5];
 6console.log(Object.fromEntries(arr)); // { a: 1, b: 2, c: 3 }
 7
 8const set = new Set([
 9  ["a", 1],
10  ["b", 2],
11  ["c", 3],
12]);
13console.log(Object.fromEntries(set)); // { a: 1, b: 2, c: 3 }
14
15const map = new Map([
16  ["a", 1],
17  ["b", 2],
18  ["c", 3],
19]);
20console.log(Object.fromEntries(map)); // { a: 1, b: 2, c: 3 }
21
 1const obj = {
 2  a: 1,
 3  b: 2,
 4  c: 3,
 5};
 6const obj2 = Object.fromEntries(
 7  Object.entries(obj).map(([key, value]) => [key, value * 2])
 8);
 9console.log(obj2); // { a: 2, b: 4, c: 6 }
10

Symbol.prototype.description

description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

1console.log(Symbol("test").description); // test
2console.log(Symbol.iterator.description); // Symbol.iterator
3console.log(Symbol.for("desc").description); // desc
4

可选的 Catch 绑定

 1try {
 2	// try code
 3} catch (error) {
 4	// catch code
 5}
 6
 7try {
 8  // try code
 9} catch {
10	// catch code
11}
12// 现在catch中可以不添加参数
13

ES11(ECMAScript2020)

String.prototype.matchAll()

matchAll() 方法返回一个迭代器,该迭代器包含了检索字符串与 正则表达式 进行匹配的所有结果。

 1const str = "test1 apple test2 orange test3";
 2for (const match of str.matchAll(/test\d/g)) {
 3  console.log(
 4    `找到了${match[0]},起始位置为:${match.index}, 结束位置为:${
 5      match.index + match[0].length
 6    }`
 7  );
 8}
 9// 找到了test1,起始位置为:0, 结束位置为:5
10// 找到了test2,起始位置为:12, 结束位置为:17
11// 找到了test3,起始位置为:25, 结束位置为:30
12

BigInt

一个内置对象,可以表示任意大的整数。

Js 中 Number类型只能安全的表示-(2^53-1)至 2^53-1 范的值,BigInt 可以超出2的53次方。

可以用在一个整数字面量后面加 n 的方式定义一个 BigInt,或者调用函数 BigInt()

1const num1 = 9007199254740991n;
2const num2 = BigInt(9007199254740991);
3const num3 = BigInt(0b1111111111111111111);
4const num4 = BigInt(0xfffffffffffff);
5

BigInt在某些方面类似于 Number,但是也有几个关键的不同点:不能用于 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。

BigInt可以用于这些运算符 +*-**%,以及除 >>> (无符号右移)之外的位运算符,因为 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigIntBigInt 不支持单目 ( +) 运算符。

当使用 BigInt 时,带小数的运算会被取整。

1console.log(BigInt(5) / BigInt(2)); // 2n
2

Promise.allSettled()

Promise.all() 接受一个 Promise 可迭代对象作为输入,并返回一个单独的 promise,但是该方法输入的Promise中有任何Promise被reject,则返回的 Promise 将进入reject状态,并带有第一个被拒绝的原因。

而** Promise.allSettled()\\ 则当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。

 1const p1 = Promise.resolve(1);
 2const p2 = 2;
 3const p3 = Promise.reject(3);
 4
 5Promise.all([p1, p2, p3])
 6  .then((data) => {
 7    console.log("Promise.all then:", data);
 8  })
 9  .catch((err) => {
10    console.log("Promise.all catch:", err);
11  });
12
13Promise.allSettled([p1, p2, p3])
14  .then((data) => {
15    console.log("Promise.allSettled then:", data);
16  })
17  .catch((err) => {
18    console.log("Promise.allSettled catch:", err);
19  });
20
21// 打印结果如下
22/*
23Promise.allSettled then: [
24  { status: 'fulfilled', value: 1 },
25  { status: 'fulfilled', value: 2 },
26  { status: 'rejected', reason: 3 }
27]
28Promise.all catch: 3
29*/
30

globalThis

不同的 JavaScript 环境中获取全局对象需要不同的语句。

  • 在 Web 中,可以通过 windowself 或者 frames 取到全局对象。
  • 但是在 Web Workers中,只有 self 可以。
  • 在 Node.js 中,必须使用 global

globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。

可选链运算符(?.)

通过连接的对象的引用或函数可能是 undefinednull 时,可选链运算符提供了一种方法来简化被连接对象的值访问。

在引用为空( null 或者 undefined) 的情况下不会引起错误,可选链表达式短路返回值是 undefined

 1const obj = {
 2  name: "jack",
 3  cat: {
 4    name: "mimi",
 5    age: 6,
 6  },
 7};
 8
 9console.log(obj.dog?.age); // undefined
10console.log(obj.run?.()); // undefined
11

空值合并运算符(??)

空值合并运算符??)是一个逻辑运算符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

与逻辑或运算符不同的是,当左侧的操作数为假值(即在布尔上下文中认定为false的值,比如 ''0)时,会返回其右侧操作数。

1const valA = false ?? 1;
2const valB = 0 ?? 1;
3const valC = null ?? 1;
4const valD = undefined ?? 1;
5
6console.log(valA, valB, valC, valD); // false 0 1 1
7

Dynamic Import()

ES Module是一套静态的模块系统,在之前,import/export 声明只能出现在顶层作用域,不支持按需加载、懒加载。但是现在已经支持动态加载模块了。

1const btn = document.querySelector(".button");
2btn.addEventListener("click", () => {
3  import("./resources.js").then((res) => {
4    console.log(res);
5  });
6});
7

import.meta

import.meta 是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的 URL。

1console.log(import.meta.url)
2

export-as-from

聚合模块,导入并导出

1export * as module from "module-name";
2
3// 相当于
4
5import * as module from "module-name";
6export { module };
7
1export * from "module-name";
2export * as name1 from "module-name";
3export { name1, /* …, */ nameN } from "module-name";
4export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name";
5export { default, /* …, */ } from "module-name";
6export { default as name1 } from "module-name";
7

ES12(ECMAScript2021)

String.prototype.replaceAll()

与replace类似,该方法有两个参数 replaceAll(pattern, replacement)replaceAll() 方法返回一个新字符串,其中所有匹配 pattern 的部分都被替换为 replacementpattern 可以是一个字符串或一个 RegExpreplacement 可以是一个字符串或一个在每次匹配时调用的函数。原始字符串保持不变。

1const str = "My dog is bigger than your dog";
2const newStr = str.replaceAll("dog", "cat");
3console.log(newStr); // My cat is bigger than your cat
4

Promise.any()

Promise.any() 将一个 Promise 可迭代对象作为输入,返回第一个成功的值。与 Promise.race() 不同的是, Promise.race() 是第一个promise敲定(resolve或reject)时返回。当所有输入 Promise 都被reject时,会以一个包含拒绝原因数组的 AggregateError 拒绝。

 1const p1 = Promise.reject(1);
 2const p2 = Promise.reject(2);
 3const p3 = Promise.resolve(3);
 4
 5Promise.race([p1, p2, p3])
 6  .then((data) => {
 7    console.log(data);
 8  })
 9  .catch((err) => {
10    console.log(err); // 1
11  });
12
13Promise.any([p1, p2, p3])
14  .then((data) => {
15    console.log(data); // 3
16  })
17  .catch((err) => {
18    console.log(err);
19  });
20
21Promise.any([p1, p2])
22  .then((data) => {
23    console.log(data);
24  })
25  .catch((err) => {
26    console.log(err); // [AggregateError: All promises were rejected] { [errors]: [ 1, 2 ] }
27  });
28

逻辑赋值

&&=

逻辑与赋值x &&= y)运算仅在 x 为真值(布尔转换后为true的值)时为其赋值。

1let a = 0;
2let b = 1;
3
4a &&= b;
5console.log(a); // 0
6b &&= a;
7console.log(b); // 0
8

||=

逻辑或赋值( x ||= y)运算仅在 x 为假值(布尔转换后为false的值)时为其赋值。

1let a = 0;
2let b = 1;
3
4a ||= b;
5console.log(a); // 1
6b ||= a;
7console.log(b); // 1
8

??=

逻辑空赋值运算符( x ??= y)仅在 x 是空值( nullundefined)时对其赋值。

 1let a = 0;
 2let b = undefined;
 3let c = 1;
 4
 5a ??= 2;
 6console.log(a); // 0
 7b ??= 2;
 8console.log(b); // 2
 9c ??= 2;
10console.log(c); // 1
11

数值分隔符

定义number数据时,可以使用 _ 分割符,让数据更美观清晰。

1const num1 = 10_00_000_000;
2console.log(num1); // 1000000000
3const num2 = 1_1n;
4console.log(num2); // 11n
5const num3 = 0b11011_101;
6console.log(num3); // 221
7

WeakRef

WeakRef 对象允许你保留对另一个对象的弱引用。

对象的 弱引用 是指该引用不会阻止 GC 回收这个对象。而与此相反的,一个普通的引用(或者说 强引用)会将与之对应的对象保存在内存中。只有当该对象没有任何的强引用时,JavaScript 引擎 GC 才会销毁该对象并且回收该对象所占的内存空间。如果上述情况发生了,那么你就无法通过任何的弱引用来获取该对象。

使用 deref() 可以返回当前实例的 WeakRef 对象所绑定的 target 对象,如果该 target 对象已被 GC 回收则返回 undefined

1let obj = { a: 1 };
2const weakRef = new WeakRef(obj);
3console.log(weakRef.deref()); // { a: 1 }
4

ES13(ECMAScript2022)

Class声明式的类字段

在之前,如果要声明类字段,只能在constructor 中通过向 this 赋值实现

现在则可以直接在最外层书写声明式的类字段

 1// old
 2class Person {
 3  constructor() {
 4    this.name = "John";
 5  }
 6}
 7
 8// new
 9class Person {
10  name = "John";
11}
12

Class 私有属性

我们可以通过添加 # 前缀来创建私有属性,私有属性在类的外部无法合法地引用。

 1class Animal {
 2  #name = "mimi";
 3
 4  #takeName(val) {
 5    this.#name = val;
 6  }
 7
 8  get name() {
 9    return this.#name;
10  }
11
12  set name(val) {
13    this.#takeName(val);
14  }
15}
16
17const animalOne = new Animal();
18console.log(animalOne.name); // mimi
19animalOne.name = "doudou";
20console.log(animalOne.name); // doudou
21
22console.log(ClassOne.#name); // 报错
23console.log(ClassOne.#takeName); // 报错
24

私有静态属性,一样添加 # 前缀创建

 1class Animal {
 2  static #name = "mimi";
 3
 4  static #takeName(val) {
 5    this.#name = val;
 6  }
 7
 8  static get name() {
 9    return this.#name;
10  }
11
12  static set name(val) {
13    this.#takeName(val);
14  }
15}
16
17console.log(Animal.name); // mimi
18Animal.name = "doudou";
19console.log(Animal.name); // doudou
20
21console.log(Animal.#name); // 报错
22console.log(Animal.#takeName); // 报错
23

扩展了in操作符对于私有属性的检查

 1class Animal {
 2  #name = "mimi";
 3
 4  hasName() {
 5    return #name in this;
 6  }
 7}
 8
 9const myAnimal = new Animal();
10console.log(myAnimal.hasName()); // true
11

static静态初始化块

static 关键字除了定义 静态方法和字段 外,现在还可以定义静态初始化块。

在类中可以通过 static 关键字定义一系列静态代码块,这些代码块会在类被创造时 执行一次

 1class Animal {
 2  static name = "mimi";
 3
 4  static {
 5    this.name = "doudou";
 6  }
 7}
 8
 9console.log(Animal.name); // doudou
10

await可以在模块最外层使用

await除了在async函数中使用,还可以在模块最外层使用。

 1<!DOCTYPE html>
 2<html lang="en">
 3  <head>
 4    <meta charset="UTF-8" />
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 6    <title>Document</title>
 7  </head>
 8  <body></body>
 9</html>
10<script type="text/javascript">
11  const fn = () => Promise.resolve(1);
12  await fn();
13</script>
14

尝试上面代码打开页面会发现,页面报错了,报错如下

image-20240531172821492.png

这是因为要在模块的最外层(或者叫顶级主体)才有效,注意是 模块。

修改 script 标签为 module 后,发现代码正常生效了。

1<script type="module">
2  const fn = () => Promise.resolve(1);
3  await fn();
4</script>
5

Array.prototype.at()

at() 方法接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数。

1const arr = [1, 2, 3, 4, 5, 6];
2
3console.log(arr.at(2)); // 3
4console.log(arr.at(-2)); // 5
5

String.prototype.at()

与上面类似,接收一个整数值并返回一个新的字符串,该字符串由位于指定偏移量处的单个 UTF-16 码元组成。允许正整数和负整数。负整数从数组中的最后一个元素开始倒数。

1const str = "abcdefghijk";
2
3console.log(str.at(2)); // c
4console.log(str.at(-2)); // j
5

Object.hasOwn()

该方法接收两个参数,第一个是js对象,第二个是测试的属性,string或Symbol。

如果指定的对象 自身 有指定的属性,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false

 1let obj = {
 2  a: 1,
 3};
 4console.log(Object.hasOwn(obj, "a")); // true
 5console.log(Object.hasOwn(obj, "toString")); // false
 6
 7let arr = ["a", "b", "c"];
 8console.log(Object.hasOwn(arr, 2)); // true
 9console.log(Object.hasOwn(arr, 3)); // false
10

Object.hasOwn() 旨在取代 Object.prototype.hasOwnProperty()。在支持 Object.hasOwn 的浏览器中,建议使用 Object.hasOwn(),而非 hasOwnProperty()

因为 Object.prototype.hasOwnProperty() 会有以下问题

1、JavaScript 并不保护属性名称 hasOwnProperty;具有此名称(hasOwnProperty)属性的对象可能会返回不正确的结果:

1const obj = {
2  hasOwnProperty() {
3    return false;
4  },
5  a: 1,
6};
7console.log(obj.hasOwnProperty("a")); // false
8console.log(Object.hasOwn(obj, "a")); // true
9

2、因为 hasOwnPropertyObject.Object.prototype 中,使用 Object.create(null) 创建的对象不从 Object.prototype 继承,使得 hasOwnProperty() 不可访问。

1const obj = Object.create(null);
2obj.a = 1;
3console.log(obj.hasOwnProperty("a")); // 报错,TypeError: obj.hasOwnProperty is not a function
4console.log(Object.hasOwn(obj, "a")); // true
5

Error: cause

Error 实例中的 cause 数据属性指示导致该错误的具体原始原因。

它通过 options.cause 参数被传入 Error() 构造函数,并且有可能不存在。

 1function isNumber(val) {
 2  if (Object.prototype.toString.call(val) !== "[object Number]") {
 3    throw new Error("error", {
 4      cause: { message: "该参数类型不是数值", input: val },
 5    });
 6  }
 7}
 8
 9try {
10  isNumber("a");
11} catch (error) {
12  console.log(error.cause); // { message: '该参数类型不是数值', input: 'a' }
13}
14

Array.prototype.findLast()

findLast() 方法反向迭代数组,也就是从尾部开始查找并返回满足提供的测试函数的第一个元素的值。如果没有找到对应元素,则返回 undefined

find 方法查找方向相反, find 是从头部开始查找。

1const arr = [1, 2, 3, 4, 5, 6];
2console.log(arr.findLast((e) => e % 2 === 0)); // 6
3

Array.prototype.findLastIndex()

findLastIndex() 方法反向迭代数组,也就是从尾部开始查找并返回满足所提供的测试函数的第一个元素的索引。若没有找到对应元素,则返回 -1。

findIndex 方法查找方向相反, findIndex 是从头部开始查找。

1const arr = [1, 2, 3, 4, 5, 6];
2console.log(arr.findLastIndex((e) => e % 2 === 0)); // 5
3

RegExp扩展

通过给正则表示式添加d标签, RegExp.prototype.exec()String.prototype.match 等的结果会附加了一个 indices 属性。

indices 属性本身是一个索引数组,其中包含每个捕获的子字符串的一对开始索引和结束索引。

 1const text = "apple";
 2const reg = /pp/d;
 3console.log(reg.exec(text));
 4/*
 5[
 6  'pp',
 7  index: 1,
 8  input: 'apple',
 9  groups: undefined,
10  indices: [ [ 1, 3 ], groups: undefined ]
11]
12*/
13

另外,索引数组本身将具有一个 groups 属性,其中包含每个具名捕获组的开始索引和结束索引。

 1let users = `名单:姓氏:李,名字:雷`;
 2let regexpNames = /姓氏:(?<first>.+),名字:(?<last>.+)/d;
 3const res = regexpNames.exec(users);
 4console.log(res);
 5/*
 6[
 7  '姓氏:李,名字:雷',
 8  '李',
 9  '雷',
10  index: 3,
11  input: '名单:姓氏:李,名字:雷',
12  groups: [Object: null prototype] { first: '李', last: '雷' },
13  indices: [
14    [ 3, 12 ],
15    [ 6, 7 ],
16    [ 11, 12 ],
17    groups: [Object: null prototype] { first: [Array], last: [Array] }
18  ]
19]
20*/
21console.log(users.slice(res.indices[0][0], res.indices[0][1])); // 姓氏:李,名字:雷
22console.log(users.slice(res.indices[1][0], res.indices[1][1])); // 李
23console.log(users.slice(res.indices[2][0], res.indices[2][1])); // 雷
24console.log(res.indices.groups["first"]); // [6, 7]
25console.log(res.indices.groups["last"]); // [11, 12]
26

ES14(ECMAScript2023)

Array.prototype.toSorted()

Array 实例的 toSorted() 方法是 sort() 方法的复制方法版本。它返回一个新数组,而 sort() 会修改原数组。

sort()toSorted() 参数一样。

 1const arr1 = [2, 1, 4, 5, 3];
 2const arr2 = arr1.toSorted();
 3console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
 4// 原数组: [ 2, 1, 4, 5, 3 ]
 5// 返回的数组:[1, 2, 3, 4, 5]
 6
 7const arr3 = [2, 1, 4, 5, 3];
 8const arr4 = arr3.sort();
 9console.log("原数组:", arr3, "\n", "返回的数组:", arr4);
10// 原数组: [ 1, 2, 3, 4, 5 ]
11// 返回的数组: [ 1, 2, 3, 4, 5 ]
12

Array.prototype.toReversed()

Array 实例的 toReversed() 方法是 reverse() 方法对应的复制版本。它返回一个元素顺序相反的新数组,而 reverse() 会修改原数组。

 1const arr1 = [1, 2, 3, 4];
 2const arr2 = arr1.toReversed();
 3console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
 4// 原数组: [ 1, 2, 3, 4 ]
 5// 返回的数组: [ 4, 3, 2, 1 ]
 6
 7const arr3 = [1, 2, 3, 4];
 8const arr4 = arr3.reverse();
 9console.log("原数组:", arr3, "\n", "返回的数组:", arr4);
10// 原数组: [ 4, 3, 2, 1 ]
11// 返回的数组: [ 4, 3, 2, 1 ]
12

Array.prototype.toSpliced()

Array 实例的 toSpliced() 方法是 splice() 的复制版本。它返回一个新数组,新数组为删除/添加/替换后的新数组。不会修改原数组。

splice() 会修改原数组,该方法返回的是包含了删除的元素的数组。

 1const arr1 = ["jack", "mike", "jay", "lucy"];
 2const arr2 = arr1.toSpliced(1, 2, "depp");
 3console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
 4// 原数组: [ 'jack', 'mike', 'jay', 'lucy' ]
 5// 返回的数组: [ 'jack', 'depp', 'lucy' ]
 6
 7const arr3 = ["jack", "mike", "jay", "lucy"];
 8const arr4 = arr3.splice(1, 2, "depp");
 9console.log("原数组:", arr3, "\n", "返回的数组:", arr4);
10// 原数组: [ 'jack', 'depp', 'lucy' ]
11// 返回的数组: [ 'mike', 'jay' ]
12

Array.prototype.with()

Array 实例的 with() 方法是使用方括号表示法修改指定索引值的复制方法版本。它会返回一个新数组,其指定索引处的值会被新值替换。

该方法接收两个参数,第一个参数为要修改的数组索引(负数索引会从数组末尾开始计数——即当 index < 0 时,会使用 index + array.length),第二个参数为分配给指定索引的值。

 1const arr1 = ["jack", "mike", "jay", "lucy"];
 2const arr2 = arr1.with(1, "depp");
 3console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
 4// 原数组: [ 'jack', 'mike', 'jay', 'lucy' ]
 5// 返回的数组: [ 'jack', 'depp', 'jay', 'lucy' ]
 6
 7const arr3 = ["jack", "mike", "jay", "lucy"];
 8arr3[1] = "depp";
 9console.log(arr3); // [ 'jack', 'depp', 'jay', 'lucy' ]
10

Symbol 作为 WeakMap 键

WeakMaps 仅允许使用对象作为键,这是 WeakMaps 的一个限制。新功能扩展了 WeakMap API,允许使用唯一的 Symbol 作为键。

并在 WeakRefFinalizationRegistry 中支持 Symbol。

1const weakMap = new WeakMap();
2const SymbolKey = Symbol("a");
3weakMap.set(SymbolKey, "b");
4console.log(weakMap.get(SymbolKey)); // b
5

es14开始,Symbol 类型的值也可以作为弱引用了。但是并不是所有的 Symbol 类型都可以作为弱引用。例如 Symbol.for() 创建的 Symbol 是不可以作为弱引用的。

因为 Symbol.for() 创建的 symbol会被放入symbol注册表中。 Symbol.for(key) 根据给定的键 key 从运行时的symbol注册表中查找对应的symbol,找到则返回,否则就是返回新创建的 symbol。

1const symbolA = Symbol("aaa");
2const symbolB = Symbol("aaa");
3console.log(symbolA === symbolB); // false
4
5const symbolC = Symbol.for("bbb");
6const symbolD = Symbol.for("bbb");
7console.log(symbolC === symbolD); // true
8

因为Symbol.for()创建的symbol随时有可能被找回。所以其不可以作为弱引用。

1const weakMap = new WeakMap();
2const SymbolKey = Symbol.for("a");
3weakMap.set(SymbolKey, "b"); // 报错,TypeError: Invalid value used as weak map key
4

Symbol.iterator 也可以作为Symbol创建的键

1const weakMap = new WeakMap();
2weakMap.set(Symbol.iterator, "a");
3console.log(weakMap.get(Symbol.iterator)); // a
4