ES6(ES2015) 之后,新增的哪些特性是你日常使用的

ES2016

  • Array.prototype.includes:用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, 3].includes(3, 3); // false,从索引 3 开始查找
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true
["1", "2", "3"].includes(3); // false
  • exponentiationOperator(指数运算符,):幂()运算符返回第一个操作数取第二个操作数的幂的结果。它等价于 Math.pow(),不同之处在于,它还接受 BigInt 作为操作数。
2 ** 3; // 8
3 ** 2; // 9
3 ** 2.5; // 15.588457268119896
10 ** -1; // 0.1
NaN ** 2; // NaN
NaN ** 0; // 1
1 ** Infinity; // NaN

ES2017

  • Async/Await
    async function* 关键字可用于在表达式中定义一个异步生成器函数,返回的是一个promise对象。async 表明当前函数是异步函数,不会阻塞线程导致后续代码停止运行。
    await 表示紧跟在后面的表达式需要等待结果。
function resolveAfter2Seconds(x) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function f1() {
  let x = await resolveAfter2Seconds(10);
  console.log(x); // 10
}

f1();
  • 字符串填充(String.prototype.padStart 和 String.prototype.padEnd):
    padStart() 方法从字符串的开头用另一个字符串填充一个字符串到一定长度,并返回一个达到一定长度的结果字符串。
"abc".padStart(10); // "       abc"
"abc".padStart(10, "foo"); // "foofoofabc"
"abc".padStart(6, "123465"); // "123abc"
"abc".padStart(8, "0"); // "00000abc"
"abc".padStart(1); // "abc"

与 padStart() 方法类似,padEnd() 方法用另一个字符串填充一个字符串到特定长度。但是,padEnd() 方法从字符串的末尾开始填充。

"abc".padEnd(10); // "abc       "
"abc".padEnd(10, "foo"); // "abcfoofoof"
"abc".padEnd(6, "123456"); // "abc123"
"abc".padEnd(1); // "abc"
  • Object.values():该静态方法返回一个给定对象的自有可枚举字符串键属性值组成的数组。

Object.values() 返回一个数组,其元素是直接在 object 上找到的可枚举字符串键属性值。这与使用 for...in 循环迭代相同,只是 for...in 循环还枚举原型链中的属性。Object.values() 返回的数组顺序和 for...in 循环提供的数组顺序相同。

const obj = { foo: "bar", baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

// 类数组对象
const arrayLikeObj1 = { 0: "a", 1: "b", 2: "c" };
console.log(Object.values(arrayLikeObj1)); // ['a', 'b', 'c']

// 具有随机键排序的类数组对象
// 使用数字键时,将按键的数字顺序返回值
const arrayLikeObj2 = { 100: "a", 2: "b", 7: "c" };
console.log(Object.values(arrayLikeObj2)); // ['b', 'c', 'a']

// getFoo 是一个不可枚举的属性
const myObj = Object.create(
  {},
  {
    getFoo: {
      value() {
        return this.foo;
      },
    },
  },
);
myObj.foo = "bar";
console.log(Object.values(myObj)); // ['bar']
  • Object.entries() 静态方法返回一个数组,包含给定对象自有的可枚举字符串键属性的键值对。

Object.entries() 返回一个数组,其元素是直接在 object 上找到相应的可枚举字符串键属性的键值对数组。这与使用 for…in 循环迭代相同,只是使用 for…in 循环也枚举原型链中的属性。Object.entries() 返回的数组顺序和 for…in 循环提供的顺序相同。

const obj = { foo: "bar", baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

// 类数组对象
const obj = { 0: "a", 1: "b", 2: "c" };
console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]

// 具有随机键排序的类数组对象
const anObj = { 100: "a", 2: "b", 7: "c" };
console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]

// getFoo 是一个不可枚举的属性
const myObj = Object.create(
  {},
  {
    getFoo: {
      value() {
        return this.foo;
      },
    },
  },
);
myObj.foo = "bar";
console.log(Object.entries(myObj)); // [ ['foo', 'bar'] ]
  • Object.getOwnPropertyDescriptors 静态方法返回给定对象的所有自有属性描述符。

该方法允许查看对象的所有自有属性的精确描述。在 JavaScript 中,一个属性由一个字符串值的名称或一个 Symbol 和一个属性描述符组成。

const object1 = {
  property1: 42,
};

const descriptors1 = Object.getOwnPropertyDescriptors(object1);

console.log(descriptors1.property1.writable);
// Expected output: true

console.log(descriptors1.property1.value);
// Expected output: 42
  • Atomics 和 SharedArrayBuffer(与多线程操作相关)

Atomics 命名空间对象包含对 SharedArrayBufferArrayBuffer 对象执行原子操作的静态方法。

ES2018

  • Promise.prototype.finally:一个Promise调用链要么成功到达最后一个.then(),要么失败触发.catch()。在某些情况下,你想要在无论Promise运行成功还是失败,运行相同的代码,例如清除,删除对话,关闭数据库连接等。Promise.prototype.finally() 允许你指定最终的逻辑
let isLoading = true;

fetch(myRequest)
  .then((response) => {
    const contentType = response.headers.get("content-type");
    if (contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then((json) => {
    /* 进一步处理 JSON */
  })
  .catch((error) => {
    console.error(error); // 这行代码也可能抛出错误,例如:when console = {}
  })
  .finally(() => {
    isLoading = false;
  });
  • 正则表达式 新增特性
    • s (dotAll) Flag - . 匹配任意字符
    • Named capture groups - 命名捕获组
    • Lookbehind assertions - 后向断言
    • Unicode property escapes - Unicode 属性转义
      在正则表达式中,点(.)是一个特殊符号,它能够匹配除了换行符(如\n\r)之外的任何一个字符。为了匹配包括换行符在内的所有字符,可以使用两个互补的字符类,例如[\d\D]。这个表达式指示正则表达式引擎寻找一个数字(\d)或者一个非数字字符(\D),从而实现匹配任意字符的效果。

例如,以下代码段展示了如何使用这种匹配方式:

console.log(/one[\d\D]two/.test('one\ntwo')); // 输出:true

. 匹配任意字符

ES2018引入了一种新的模式,使得点(.)可以匹配所有字符,包括换行符。这种模式可以通过在正则表达式中添加s标志来激活:

console.log(/one.two/.test('one\ntwo'));     // 输出:false
console.log(/one.two/s.test('one\ntwo'));    // 输出:true

使用s标志的好处在于它保持了向后兼容性,这意味着现有的使用点字符的正则表达式模式不会产生冲突。

命名捕获组

在某些情况下,使用数字引用捕获组可能会导致混淆。例如,考虑以下正则表达式,它用于匹配日期:

const re = /(\d{4})-(\d{2})-(\d{2})/;
const match = re.exec('2019-01-10');

在美式和英式日期格式中,确定哪个捕获组代表月份或日期可能会令人困惑。ES2018引入了命名捕获组的概念,使用(?<name>...)语法,使得模式更加清晰:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = re.exec('2019-01-10');

console.log(match.groups);          // 输出:{year: "2019", month: "01", day: "10"}
console.log(match.groups.year);    // 输出:2019
console.log(match.groups.month);   // 输出:01
console.log(match.groups.day);     // 输出:10

在正则表达式中,可以通过\k<name>语法稍后引用命名捕获组。例如,要匹配句子中连续重复的单词,可以使用以下模式:

const re = /\b(?<dup>\w+)\s+\k<dup>\b/;
const match = re.exec('Get that that cat off the table!');

console.log(match.index);    // 输出:4
console.log(match[0]);       // 输出:that that

replace()方法的替换字符串中,可以通过$<name>语法来插入命名捕获组。例如:

const str = 'red & blue';

console.log(str.replace(/(?<red>red) & (?<blue>blue)/, '$<blue> & $<red>'));

// 输出:blue & red

请注意,我在最后一段代码中更正了一个小错误,即在替换字符串中使用了正确的语法$<blue> & $<red>,而不是<blue> &amp; <red>。这样可以确保替换操作能够正确地引用命名捕获组。

后视断言

JavaScript 的 ES2018 版本引入了后视断言,这是一项其他正则表达式早已具备的功能。与之前只支持先行断言的 JavaScript 相比,后视断言允许开发者根据模式之前的子字符串来匹配模式。后视断言使用语法 (?<=…) 表示。例如,若要匹配产品价格而不捕获货币符号(美元、英镑或欧元),可以使用如下正则表达式:

const re = /(?<=\$|£|€)\d+(\.\d*)?/;
console.log(re.exec('199'));     // → null
console.log(re.exec('$199'));    // → ["199", undefined, index: 1, input: "$199", groups: undefined]
console.log(re.exec('€50'));     // → ["50", undefined, index: 1, input: "€50", groups: undefined]

此外,后视断言还有否定版本 (?<!..),它确保只有在模式之前不匹配特定模式时才进行匹配。例如,正则表达式 /(?<!un)\ba/ 可以匹配单词 “available”,前提是它前面没有 “un” 前缀:

const re = /(?<!un)\ba/;

Unicode property escapes - Unicode 属性转义

ES2018 还引入了 Unicode 属性转义,这是一种新的转义序列,它为正则表达式提供了对完整 Unicode 标准的支持。假设需要匹配 Unicode 字符 “㉛”,它虽然是一个数字,但不能使用 \d 来匹配,因为 \d 只匹配 ASCII 字符集中的 [0-9]。相反,可以使用 Unicode 属性转义来匹配 Unicode 中的任何数字:

const str = '㉛';
console.log(/\d/u.test(str));    // → false
console.log(/\p{Number}/u.test(str));     // → true

同样,若要匹配任何 Unicode 字母字符,可以使用 \p{Alphabetic}:

const str = 'ض';
console.log(/\p{Alphabetic}/u.test(str));     // → true
// \w 不能匹配 'ض'
console.log(/\w/u.test(str));    // → false

Unicode 属性转义的否定形式为 \P{…},如下所示:

console.log(/\P{Number}/u.test('㉛'));    // → false
console.log(/\P{Number}/u.test('ض'));    // → true
console.log(/\P{Alphabetic}/u.test('㉛'));    // → true
console.log(/\P{Alphabetic}/u.test('ض'));    // → false

除了 Alphabetic 和 Number,Unicode 属性转义还支持多个其他属性。可以在最新的规范提案中找到支持的 Unicode 属性的完整列表。

  • Object.rest 参数属性
    在 JavaScript 中,ES2015 标准为我们带来了一项非常实用的新特性——展开运算符(...)。这个特性极大地简化了我们对数组进行复制和合并的操作。想想看,以前我们需要用到 concat()slice() 方法来处理数组,现在只需简单地使用展开运算符即可轻松搞定。
const arr1 = [10, 20, 30];
// 使用展开运算符复制 arr1
const copy = [...arr1];
console.log(copy); // 输出:[10, 20, 30]

const arr2 = [40, 50];
// 使用展开运算符将 arr2 与 arr1 合并
const merge = [...arr1, ...arr2];
console.log(merge); // 输出:[10, 20, 30, 40, 50]

不仅如此,展开运算符在将数组作为参数传递给函数时也大有用武之地。比如,我们可以用它来求数组的最大值,代码简洁到不行:

const arr = [10, 20, 30];
// 使用展开运算符传递数组参数给 Math.max 函数
console.log(Math.max(...arr)); // 输出:30

到了 ES2018,展开运算符的应用范围进一步扩大,这次它被应用到了对象字面量中,我们称之为展开属性。通过这个特性,我们可以轻松地将一个对象的属性复制到另一个新对象中,就像下面这样:

const obj1 = {
  a: 10,
  b: 20
};
// 使用展开属性创建 obj2,并添加一个新属性 c
const obj2 = {
  ...obj1,
  c: 30
};
console.log(obj2); // 输出:{a: 10, b: 20, c: 30}

如果对象中有同名属性,不用担心,最后声明的属性会覆盖之前的属性。此外,展开属性还能作为 Object.assign() 方法的替代品,用于合并多个对象。但是要注意,Object.assign() 和展开属性在处理继承属性和 setter 时的行为是有区别的。

const obj1 = {a: 10};
const obj2 = {b: 20};
const obj3 = {c: 30};
// 使用展开属性合并对象
console.log({...obj1, ...obj2, ...obj3}); // 输出:{a: 10, b: 20, c: 30}

值得注意的是,展开属性只会复制对象的可枚举自有属性,并且它是浅拷贝,也就是说,如果对象属性中包含了另一个对象,那么复制的只是对该对象的引用。

const obj = {x: {y: 10}};
const copy1 = {...obj};
const copy2 = {...obj};
console.log(copy1.x === copy2.x); // 输出:true

最后,ES2015 还引入了剩余参数的概念,允许我们使用 ... 来捕获函数中的剩余参数作为数组。这一特性在数组解构中非常有用,如下所示:

const arr = [10, 20, 30];
const [x, ...rest] = arr;
console.log(x); // 输出:10
console.log(rest); // 输出:[20, 30]

同样的,ES2018 将剩余参数的概念扩展到了对象解构中,允许我们使用剩余属性来捕获对象中的剩余属性。

const obj = {
  a: 10,
  b: 20,
  c: 30
};
const {a, ...rest} = obj;
console.log(a); // 输出:10
console.log(rest); // 输出:{b: 20, c: 30}

但是,使用剩余属性时要注意,它必须位于对象属性声明的最后,否则会导致语法错误。而且,对象解构中不能同时使用多个剩余属性,除非它们是嵌套的。

const obj = {
  a: 10,
  b: {
    x: 20,
    y: 30,
    z: 40
  }
};
const {b: {x, ...rest1}, ...rest2} = obj; // 正确
const {...rest, ...rest2} = obj; // 语法错误

这些特性无疑为 JavaScript 的数组和对象操作带来了更多的灵活性和便利,让我们的代码更加简洁和易于维护。

  • async 迭代器和 for await…of 循环

在编程的世界里,我们经常需要遍历数据集合。这在 ES2015 之前,主要依赖于 forfor...inwhile 循环等传统语句,或者是 map()filter()forEach() 等数组方法。但随着 ES2015 的到来,迭代器接口的引入为 JavaScript 程序员提供了一种新的方式来逐个处理集合中的元素。

迭代器接口的基础

迭代器接口的核心在于 Symbol.iterator 属性。如果一个对象拥有这个属性,那么它就是可迭代的。在 ES2015 中,字符串和集合对象(如 SetMapArray)都自带了 Symbol.iterator 属性,这意味着它们可以直接用于迭代。

下面是一个简单的例子,展示了如何使用迭代器来访问数组中的每个元素:

const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // 输出:{value: 10, done: false}
console.log(iterator.next()); // 输出:{value: 20, done: false}
console.log(iterator.next()); // 输出:{value: 30, done: false}
console.log(iterator.next()); // 输出:{value: undefined, done: true}

Symbol.iterator 属性指向一个返回迭代器的函数。迭代器的主要操作是通过 next() 方法进行的,该方法返回一个对象,包含 valuedone 两个属性。value 属性包含了集合中的下一个元素,而 done 属性是一个布尔值,指示是否已经到达集合的末尾。

普通对象的可迭代性

默认情况下,普通对象并不是可迭代的。但通过定义 Symbol.iterator 属性,我们可以赋予它们可迭代的能力。下面是一个示例,展示了如何为一个普通对象添加迭代器接口:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.iterator]() {
    const values = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        return {
          value: this[values[i++]],
          done: i > values.length
        };
      }
    };
  }
};

const iterator = collection[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

简化迭代器接口的生成器函数

虽然上面的代码可以正常工作,但它的复杂性是不必要的。幸运的是,JavaScript 提供了生成器函数,可以大大简化创建迭代器的过程:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  *[Symbol.iterator]() {
    for (let key in this) {
      yield this[key];
    }
  }
};

const iterator = collection[Symbol.iterator];

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

在这个生成器中,我们使用 for...in 循环来遍历对象的属性,并使用 yield 关键字来产生每个属性的值。这样不仅代码更简洁,而且可读性也更强。

异步迭代器和 for...await...of 语句

迭代器的一个局限性是它们不适合处理异步数据源。为了解决这个问题,ES2018 引入了异步迭代器和异步可迭代对象。异步迭代器与传统迭代器的主要区别在于,它们返回的不是一个普通的 {value, done} 对象,而是一个 Promise,该 Promise 会解析为 {value, done} 对象。

下面是一个异步迭代器的例子:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.asyncIterator]() {
    const values = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        return Promise.resolve({
          value: this[values[i++]],
          done: i > values.length
        });
      }
    };
  }
};

const iterator = collection[Symbol.asyncIterator];

console.log(iterator.next().then(result => console.log(result)));
// ...

为了简化异步迭代器的创建,我们同样可以使用异步生成器函数:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  async *[Symbol.asyncIterator]() {
    for (let key in this) {
      yield this[key];
    }
  }
};

const iterator = collection[Symbol.asyncIterator];

console.log(iterator.next().then(result => console.log(result)));
// ...

for...await...of 语句

为了遍历异步可迭代对象,ES2018 引入了 for...await...of 语句。这个语句只能在异步函数中使用,它在每次迭代中等待异步迭代器的 next() 方法返回的 Promise 解决。

const collection = {
  a: 10,
  b: 20,
  c: 30,
  async *[Symbol.asyncIterator]() {
    for (let key in this) {
      yield this[key];
    }
  }
};

(async function () {
  for await (const x of collection) {
    console.log(x);
  }
})();
// 输出:
// 10
// 20
// 30

请记住,for...await...of 语句是 JavaScript 控制流的一大进步,它允许我们以声明式的方式处理异步序列,这在处理异步数据流时非常有用。

异常处理

在使用异步迭代器时,next() 方法可能会返回一个被拒绝的 Promise。为了优雅地处理这些异常,我们可以将 for...await...of 语句包裹在 try...catch 语句中:

const collection = {
  [Symbol.asyncIterator]() {
    return {
      next: () => Promise.reject(new Error('Something went wrong.'))
    };
  }
};

(async function() {
  try {
    for await (const value of collection) {}
  } catch (error) {
    console.log('Caught: ' + error.message);
  }
})();
// 输出:
// Caught: Something went wrong.

通过这种方式,我们可以确保即使在异步操作中出现错误,我们的程序也能继续运行而不会崩溃。

异步迭代器和 for...await...of 语句为 JavaScript 带来了处理异步数据流的新方法。它们提供了一种强大而灵活的方式来控制程序的执行流程,特别是在处理 I/O 密集型或数据流密集型的应用时。随着异步编程在 JavaScript 中变得越来越重要,掌握这些概念对于每一个 JavaScript 开发者来说都是至关重要的。

ES2019

  • Array.prototype.flat 和 Array.prototype.flatMap
    Array.prototype.flat 在不影响原数组的基础上,返回一个「拍平」了的新数组。
    Array.prototype.flatMap 方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。它等价于在调用 map() 方法后再调用深度为 1 的 flat() 方法(arr.map(…args).flat()),但比分别调用这两个方法稍微更高效一些。

尝试

// Array.prototype.flat
const arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

const arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


// Array.prototype.flatMap
const arr1 = [1, 2, 1];

const result = arr1.flatMap((num) => (num === 2 ? [2, 2] : 1));

console.log(result);
// Expected output: Array [1, 2, 2, 1]

  • Object.fromEntries:此方法接受一个实现了 iterable 接口的对象作为入参,比如 Map 、Array。您也可以让自己的自定义对象实现 iterable 接口,同样可以使用此方法。
    Object.fromEntries 和 Object.entrie 作用正好是相反的,把它俩放在一块看一下:
const entries = new Map([
  ['key1', 'val1'],
  ['key2', 'val2']
])
const obj = Object.fromEntries(entries)
console.log(obj)// {key1: "val1", key2: "val2"}
const map = Object.entries(obj)
console.log(map) // 和 entries 的值一样
  • String.prototype.trimStart 和 String.prototype.trimEnd
const str = '    Hello World    '
str.trimStart() // "Hello World    "
str.trimEnd() // "    Hello World"
  • Symbol.prototype.description:description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。如果不存在,则返回 undefined。
Symbol("desc").toString(); // "Symbol(desc)"
Symbol("desc").description; // "desc"
Symbol("").description; // ""
Symbol().description; // undefined

// 内置通用(well-known)symbol
Symbol.iterator.toString(); // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"

// global symbols
Symbol.for("foo").toString(); // "Symbol(foo)"
Symbol.for("foo").description; // "foo"
  • Optional Catch Binding(可选的 catch 参数):以前我们写 try…catch 需要这么写,不管我们需不需要变量 e,都得写上。现在,catch 绑定变量是可选的了,可以简写。
// 以前
try {
   ...
} catch(e) {
    console.log(e)
}

// 现在
try {
   ...
} catch {
    // catch 简写了
}

ES2020

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

与逻辑或运算符(||)不同,逻辑或运算符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,‘’ 或 0)时。见下面的例子。

const nullValue = null;
const emptyText = ""; // 空字符串,是一个假值,Boolean("") === false
const someNumber = 42;

const valA = nullValue ?? "valA 的默认值";
const valB = emptyText ?? "valB 的默认值";
const valC = someNumber ?? 0;

console.log(valA); // "valA 的默认值"
console.log(valB); // ""(空字符串虽然是假值,但不是 null 或者 undefined)
console.log(valC); // 42
  • 可选链操作符(Optional Chaining)
    可选链运算符(?.)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 运算符的功能类似于 . 链式运算符,不同之处在于,在引用为空 (nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。

当尝试访问可能不存在的对象属性时,可选链运算符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链运算符也是很有帮助的。

// 空值合并运算符可以在使用可选链时设置一个默认值
let customer = {
  name: "Carl",
  details: { age: 82 },
};
let customerCity = customer?.city ?? "暗之城";
console.log(customerCity); // “暗之城”
  • BigInt 类型

BigInt 是一种内置对象,它提供了一种方法来表示大于 2^53 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。实践中,可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()(但不包含 new 运算符)并传递一个整数值或字符串值。

const theBiggestInt = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n

const hugeString = BigInt("9007199254740991");
// ↪ 9007199254740991n

const hugeHex = BigInt("0x1fffffffffffff");
// ↪ 9007199254740991n

const hugeBin = BigInt(
  "0b11111111111111111111111111111111111111111111111111111",
);
// ↪ 9007199254740991n

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

  • globalThis:全局属性 globalThis 包含全局的 this 值,类似于全局对象(global object)。
// 以前
var getGlobal = function () {
  if (typeof self !== "undefined") {
    return self;
  }
  if (typeof window !== "undefined") {
    return window;
  }
  if (typeof global !== "undefined") {
    return global;
  }
  throw new Error("unable to locate global object");
};

var globals = getGlobal();

if (typeof globals.setTimeout !== "function") {
  // 此环境中没有 setTimeout 方法!
}


// 现在
if (typeof globalThis.setTimeout !== "function") {
  //  此环境中没有 setTimeout 方法!
}

ES2021

  • Promise.allSettled:我们知道 Promise.all 具有并发执行异步任务的能力。但它的最大问题就是如果参数中的任何一个promise为reject的话,则整个Promise.all 调用会立即终止,并返回一个reject的新的 Promise 对象。而Promise.allSettled 与 Promise.all的不同在于, 它不会进行短路, 也就是说当Promise全部处理完成后,我们可以拿到每个Promise的状态, 而不管是否处理成功。
Promise.allSettled([
  Promise.resolve(33),
  new Promise((resolve) => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error("一个错误")),
]).then((values) => console.log(values));

// [
//   { status: 'fulfilled', value: 33 },
//   { status: 'fulfilled', value: 66 },
//   { status: 'fulfilled', value: 99 },
//   { status: 'rejected', reason: Error: 一个错误 }
// ]

  • Logical Assignment Operators(逻辑赋值运算符,如 ||=, &&=, ??=)
// 逻辑或赋值(x ||= y)运算仅在 x 为假值时为其赋值。
const a = { duration: 50, title: '' };

a.duration ||= 10;
console.log(a.duration);
// Expected output: 50

a.title ||= 'title is empty.';
console.log(a.title);
// Expected output: "title is empty"

// 逻辑与赋值(x &&= y)运算仅在 x 为真值时为其赋值。
let a = 1;
let b = 0;

a &&= 2;
console.log(a);
// Expected output: 2

b &&= 2;
console.log(b);
// Expected output: 0


// 逻辑空赋值运算符(x ??= y)仅在 x 是空值(null 或 undefined)时对其赋值。
const a = { duration: 50 };

a.duration ??= 10;
console.log(a.duration);
// Expected output: 50

a.speed ??= 25;
console.log(a.speed);
// Expected output: 25
  • Numeric separators
const a = 1_000 
console.log(a) // 1000

const b = 1_000_000
console.log(b) // 1000000
  • WeakRef:WeakRef 对象允许你保留对另一个对象的弱引用,但不会阻止垃圾回收(GC)清理被弱引用的对象。

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

var foo = () => {console.log('hi')};
var weakFoo = new WeakRef(foo);
console.log(weakFoo.deref()) // () => {console.log('hi')}

ES2022

  • 类的私有属性(包括类字段、类方法等)
    在这种语法出现之前,JavaScript 语言本身并没有原生支持私有属性。在原型继承中,可以通过使用 WeakMap 对象或者闭包的方式来模拟私有属性的行为,但就易用性而言,它们无法与 # 语法相提并论。
class ClassWithPrivate {
  #privateField;
  #privateFieldWithInitializer = 42;

  #privateMethod() {
    // …
  }

  static #privateStaticField;
  static #privateStaticFieldWithInitializer = 42;

  static #privateStaticMethod() {
    // …
  }
}
  • Array.prototype.at:该方法接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数。
// 数组及数组元素
const cart = ["apple", "banana", "pear"];

// 一个函数,用于返回给定数组的最后一个元素
function returnLast(arr) {
  return arr.at(-1);
}

// 获取 'cart' 数组的最后一个元素
const item1 = returnLast(cart);
console.log(item1); // 输出:'pear'

// 在 'cart' 数组中添加一个元素
cart.push("orange");
const item2 = returnLast(cart);
console.log(item2); // 输出:'orange'
  • Object.hasOwn:如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false。
const example = {};
example.prop = "exists";

// `hasOwn` 静态方法只会对目标对象的直接属性返回 true:
Object.hasOwn(example, "prop"); // 返回 true
Object.hasOwn(example, "toString"); // 返回 false
Object.hasOwn(example, "hasOwnProperty"); // 返回 false

// `in` 运算符对目标对象的直接属性或继承属性均会返回 true:
"prop" in example; // 返回 true
"toString" in example; // 返回 true
"hasOwnProperty" in example; // 返回 true
  • Top-level Await
    在ES2017中,引入了 async 函数和 await 关键字,以简化 Promise 的使用,但是 await 关键字只能在 async 函数内部使用。尝试在异步函数之外使用 await 就会报错:SyntaxError - SyntaxError: await is only valid in async function。

顶级 await 在以下场景中将非常有用:

// 动态加载模块
const strings = await import(`/i18n/${navigator.language}`);

// 资源初始化
const db = await  connectIndexDB();

// 依赖回退
let translations;
try {
    translations = await import('https://app.fr.json');
} catch {
    translations = await import('https://fallback.en.json');
}
  • 正则表达式匹配索引:该特性允许我们利用 d 字符来表示我们想要匹配字符串的开始和结束索引。以前,只能在字符串匹配操作期间获得一个包含提取的字符串和索引信息的数组。在某些情况下,这是不够的。因此,在这个规范中,如果设置标志 /d,将额外获得一个带有开始和结束索引的数组。
const matchObj = /(a+)(b+)/d.exec('aaaabb');

console.log(matchObj[1]) // 'aaaa'
console.log(matchObj[2]) // 'bb'

ES2023

  • 由后往前查找数组的方法
    • Array.prototype.findLast():从数组末尾开始向前查找符合条件的元素,并返回该元素的值。如果没有找到符合条件的元素,返回undefined。
    • Array.prototype.findLastIndex():从数组末尾开始向前查找符合条件的元素,并返回该元素的索引。如果没有找到符合条件的元素,返回-1。
const arr = [10, 20, 30, 40, 50];
arr.findLast(item => item > 30); // 50
arr.findLastIndex(item => item > 30); // 4
arr.findLast(item => item > 50); // undefined
arr.findLastIndex(item => item > 50); // -1
  • 新增4个不改动到原数组的操作方法:
    • toReversed():返回数组元素的反转副本,而不改变原数组。
    • toSorted():返回数组元素的排序副本,而不改变原数组。
    • toSpliced():从数组中指定位置开始删除指定数量的元素,并可选择在删除后新增新元素,返回新数组。
    • with():将数组索引处的值替换为新值,返回新数组。
// toReversed()
const arr = ['a', 'b', 'c'];
const result = arr.toReversed();
console.log(result); // ['c', 'b', 'a']
console.log(arr);    // ['a', 'b', 'c']


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


// toSpliced()
const arr = ['a', 'b', 'c', 'd'];
const result = arr.toSpliced(1, 2, 'X');
console.log(result); // ['a', 'X', 'd']
console.log(arr);    // ['a', 'b', 'c', 'd'], 无法再得到已经删除的元素


// with()
const arr = ['a', 'b', 'c'];
const result = arr.with(1, 'X');
console.log(result);  // ['a', 'X', 'c']
console.log(arr);     // ['a', 'b', 'c']
  • Hashbang 语法:

Hashbang注释是一种特殊的注释语法,以#!开头,后面跟着解释器的路径,只在脚本或模块的最开始有效。例如,在Node.js环境中,可以使用#!/usr/bin/env node来指定使用Node.js来执行脚本。

// 写在脚本文件第一行
#!/usr/bin/env node
'use strict';
console.log(1);


// 写在模块文件第一行
#!/usr/bin/env node
export {};
console.log(1);
# 以前执行脚本
node demo.js


# 有了 hashbang 之后执行脚本
./demo.js
  • WeakMap 新增支援 Symbol 作为键名:

在ES2023之前,WeakMap的键名必须是对象。ES2023提出了一项新提案,允许WeakMap使用Symbol作为键名,这增加了WeakMap的灵活性。

const weakMap = new WeakMap();


// 更具象征意义的key
const key = Symbol('my ref');
const someObject = { /* data */ };


weakMap.set(key, someObject)
  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值