ECMAScript 中的新特性

记录从 ES6 发布至今 ECMAScript 中出现的新特性,计划每年更新。

CC-BY-SA-4.0

ES 2021

String.prototype.replaceAll

替换字符串中所有匹配项。

const newStr = str.replaceAll(regexp | substr, newSubstr);
  • regexp|substr 匹配的正则表达式(必须为全局模式,即 /abc/g)或者字符串。
  • newSubstr 用于替换的字符串。
  • 返回值 替换后的字符串。
'123456123'.replaceAll('123', '000') === '000456000';
'abcdab'.replaceAll(/ab/g, '000') === '000cd000';
'abcdab'.replaceAll(/ef/g, '000') === 'abcdab';

在搜索的字符串为空的情况下,replacereplaceAll 的区别如下。

'x'.replace('', '_');
// → '_x'
'xxx'.replace(/(?:)/g, '_');
// → '_x_x_x_'
'xxx'.replaceAll('', '_');
// → '_x_x_x_'

参考资料:

Promise.anyAggregateError

当传入的所有 Promise 中有一个成功时,就返回那个成功的 Promise,然后终止操作,不会等待其他 Promise。如果所有 Promise 都被拒绝,则会返回 AggregateError

Promise.any(iterable);

iterable 可迭代对象,比如 Array。

try {
  const first = await Promise.any(promises);
  // Any of the promises was fulfilled.
} catch (error) {
  // All of the promises were rejected.
}

Or, without async/await:

Promise.any(promises).then(
  first => {
    // Any of the promises was fulfilled.
  },
  error => {
    // All of the promises were rejected.
  }
);

下面有一个例子。

Promise.any([
  fetch('https://v8.dev/').then(() => 'home'),
  fetch('https://v8.dev/blog').then(() => 'blog'),
  fetch('https://v8.dev/docs').then(() => 'docs'),
])
  .then(first => {
    // Any of the promises was fulfilled.
    console.log(first);
    // → 'home'
  })
  .catch(error => {
    // All of the promises were rejected.
    console.log(error);
  });

参考资料:

逻辑赋值符号 ||= &&= ??=

把逻辑运算符和赋值表达式结合起来。

// "Or Or Equals" (or, the Mallet operator :wink:)
a ||= b;
a || (a = b);

// "And And Equals"
a &&= b;
a && (a = b);

// "QQ Equals"
a ??= b;
a ?? (a = b);

参考资料:tc39/proposal-logical-assignment: A proposal to combine Logical Operators and Assignment Expressions

数字分隔符 _

就是英文里用于分隔很长的数字的 , 在这里以下划线 _ 的形式出现。

1_000_000_000; // Ah, so a billion
101_475_938.38; // And this is hundreds of millions

let fee = 123_00; // $123 (12300 cents, apparently)
let fee = 12_300; // $12,300 (woah, that fee!)
let amount = 12345_00; // 12,345 (1234500 cents, apparently)
let amount = 123_4500; // 123.45 (4-fixed financial)
let amount = 1_234_500; // 1,234,500
0.000_001; // 1 millionth
1e10_000; // 10^10000 -- granted, far less useful / in-range...

更多用法可以看提案里的 Examples

参考资料:tc39/proposal-numeric-separator: A proposal to add numeric literal separators in JavaScript.

WeakRefs

用于保留对一个对象的弱引用,不会阻止 GC(JavaScript 引擎的垃圾回收机制)。

下面的例子创建了一个计时器,计时器元素不存在时停止。

class Counter {
  constructor(element) {
    // 记录一个弱引用到 DOM 元素
    this.ref = new WeakRef(element);
    this.start();
  }

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

    this.count = 0;

    const tick = () => {
      // 从弱引用中获取元素,如果存在就执行操作
      const element = this.ref.deref();
      if (element) {
        element.textContent = ++this.count;
      } else {
        // 元素不存在时
        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);

参考资料:

ES 2020

String.prototype.matchAll

匹配正则表达式,并返回结果的迭代器。

str.matchAll(regexp);
  • regexp 要匹配的正则表达式,必须为 g 全局模式。
  • 返回值 一个可迭代对象,不可重用,可以使用 [...data] 语法转换成数组。
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

参考资料:

BigInt

在数字后面加字母 n 表示大整数。

ECMAScript 中 Number 类型的最大值为 Number.MAX_SAFE_INTEGER === 2^53 - 1 ,即 9007199254740991,而 BigInt 可以表示任意大的整数。

typeof 1n === 'bigint'; // true

const theBiggestInt = 9007199254740991n;

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

const hugeButString = BigInt('9007199254740991');
// ↪ 9007199254740991n

BigInt 可以使用 + * - **% 符号,但符号两边必须都是 BigInt 类型,否则会报错。

const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// ↪ 9007199254740991

const maxPlusOne = previousMaxSafe + 1n;
// ↪ 9007199254740992n

const theFuture = previousMaxSafe + 2n;
// ↪ 9007199254740993n, this works now!

const multi = previousMaxSafe * 2n;
// ↪ 18014398509481982n

const subtr = multi – 10n;
// ↪ 18014398509481972n

const mod = multi % 10n;
// ↪ 2n

const bigN = 2n ** 54n;
// ↪ 18014398509481984n

bigN * -1n
// ↪ –18014398509481984n
1n + 2;
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions

1n * 2;
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions

除号 / 会向零取整。

const expected = 4n / 2n;
// ↪ 2n

const rounded = 5n / 2n;
// ↪ 2n, not 2.5n

BigInt 在比较的时候不严格等于 Number,两者可以比较大小,也可以混合排序。

0n === 0; // ↪ false
0n == 0; // ↪ true

1n < 2; // ↪ true
2n > 1; // ↪ true
2 > 2; // ↪ false
2n > 2; // ↪ false
2n >= 2; // ↪ true

const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
// ↪  [4n, 6, -12n, 10, 4, 0, 0n]

mixed.sort();
// ↪ [-12n, 0, 0n, 10, 4n, 4, 6]

BigInt 在转换成 Boolean 的情况下的行为类似 Number。

if (0n) {
  console.log('Hello from the if!');
} else {
  console.log('Hello from the else!');
}

// ↪ "Hello from the else!"

0n || 12n; // ↪ 12n
0n && 12n; // ↪ 0n

Boolean(0n); // ↪ false
Boolean(12n); // ↪ true

!12n; // ↪ false
!0n; // ↪ true

BigInt 需要使用 Number() 手动转换类型到 Number,不能使用隐形类型转换。

+1n;
// ↪ TypeError: Cannot convert a BigInt value to a number

Number(1n);
// ↪ 1

不过字符串没这个限制。

1n + '2';
// ↪ "12"

'2' + 1n;
// ↪ "21"

试图把 BigInt 直接转换成 Number 可能会造成精度丢失。

const largeFriend = 900719925474099267n;
const alsoLarge = largeFriend + 2n;

const sendMeTheBiggest = (n, m) => Math.max(Number(n), Number(m));

sendMeTheBiggest(largeFriend, alsoLarge);
// ↪900719925474099300  // This is neither argument!
Number(151851850485185185047n);
// ↪ 151851850485185200000

parseInt(900719925474099267n);
// ↪900719925474099300

可以使用 BigInt(number|string) 来生成一个大整数,不过同样需要注意精度的问题,传入的如果是 Number,则要小心不要超出范围,建议直接传入字符串作为参数或者直接数字后面加 n

const badPrecision = BigInt(9007199254740993);
// ↪9007199254740992n

const goodPrecision = BigInt('9007199254740993');
// ↪9007199254740993n

const alsoGoodPrecision = 9007199254740993n;
// ↪9007199254740993n

BigInt() 的参数不能是小数,字符串表示的小数也不行。

BigInt(1.5);
// ↪ RangeError: The number 1.5 is not a safe integer and thus cannot be converted to a BigInt

BigInt('1.5');
// ↪ SyntaxError: Cannot convert 1.5 to a BigInt

BigInt 不能使用 Math 中的方法。

Math.round(1n);
// ↪ TypeError: Cannot convert a BigInt value to a number

Math.max(1n, 10n);
// ↪ TypeError: Cannot convert a BigInt value to a number

1n | 0;
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions

还有一点需要注意的是,JSON.stringify 不能序列化 BigInt。

const bigObj = { a: BigInt(10n) };
JSON.stringify(bigObj);
// ↪TypeError: Do not know how to serialize a BigInt

参考资料:

for-in

遍历对象中的可枚举属性。

var obj = { a: 1, b: 2, c: 3 };

for (var prop in obj) {
  console.log('obj.' + prop + ' = ' + obj[prop]);
}

// Output:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"

参考资料:for...in - JavaScript | MDN

空值合并运算符 ??

当左侧为 undefined 或者 null 时,返回右侧值。

主要用来解决 || 操作符将 '' 0 false 判断为假的情况,?? 会把以上这些判断为真,并返回右侧的值。

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false,
  },
};

const undefinedValue = response.settings.undefinedValue ?? 'some other default';
// result: 'some other default'

const nullValue = response.settings.nullValue ?? 'some other default';
// result: 'some other default'

const headerText = response.settings.headerText ?? 'Hello, world!';
// result: ''

const animationDuration = response.settings.animationDuration ?? 300;
// result: 0

const showSplashScreen = response.settings.showSplashScreen ?? true;
// result: false

需要注意的是,?? 在没有明确优先级的情况下,不能与 &&|| 并列使用。

null || undefined ?? "foo"; // 抛出 SyntaxError
true || undefined ?? "foo"; // 抛出 SyntaxError

(null || undefined ) ?? "foo"; // 返回 "foo"

参考资料:

可选链操作符 ?.

如果操作符左侧为 undefined 或者 null 则表达式为 undefined,否则正常执行操作符右侧的属性、方法和函数调用。

obj?.prop; // 调用可选的静态属性
obj?.[expr]; // 调用可选的动态属性
func?.(...args); // 调用可选的函数或者方法
arr?.[index]; // 调用可能的数组项

这其实是一种简化的写法,请看下面的例子。

a?.b; // 如果 a 是 null/undefined 则返回 undefined 否则返回 a.b
a == null ? undefined : a.b;

a?.[x]; // 如果 a 是 null/undefined 则返回 undefined 否则返回 a[x]
a == null ? undefined : a[x];

a?.b(); // 如果 a 是 null/undefined 则返回 undefined
a == null ? undefined : a.b();
// 如果 a.b 不是函数,则抛出 TypeError
// 否则执行 a.b()

a?.(); // 如果 a 是 null/undefined 则返回 undefined
a == null ? undefined : a();
// 如果 a 不是 null/undefined 也不是函数,则抛出 TypeError
// 否则调用函数 a

更多用例可以查看提案中的 Semantics 部分以及 MDN 中的 例子

参考资料:

Promise.allSettled

所有 Promise 都完成时(无论是 fulfilled 还是 reject),返回结果的数组。

const promises = [fetch('https://lifeni.life'), fetch('https://does-not-exist/')];

const results = await Promise.allSettled(promises);
const errors = results.filter(p => p.status === 'rejected').map(p => p.reason);

console.log(JSON.stringify(results));

/** output
[
  {
    "status":"fulfilled",
    "value":{...}
  },
  {
    "status":"rejected",
    "reason":{...}
  }
]
*/

参考资料:

import()

也就是动态导入,返回一个 Promise。

<!DOCTYPE html>
<nav>
  <a href="books.html" data-entry-module="books">Books</a>
  <a href="movies.html" data-entry-module="movies">Movies</a>
  <a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>

<main>Content will load here!</main>

<script>
  const main = document.querySelector('main');
  for (const link of document.querySelectorAll('nav > a')) {
    link.addEventListener('click', e => {
      e.preventDefault();

      import(`./section-modules/${link.dataset.entryModule}.js`)
        .then(module => {
          module.loadPageInto(main);
        })
        .catch(err => {
          main.textContent = err.message;
        });
    });
  }
</script>

参考资料:

import.meta

获取模块的元数据。

<script type="module" src="my-module.mjs"></script>
console.log(import.meta); // { url: "file:///home/user/my-module.mjs" }

下面是一个例子。

<script type="module" src="path/to/hamster-displayer.mjs" data-size="500"></script>
(async () => {
  // new URL() 的第二个参数是相对地址(baseUrl)
  const response = await fetch(new URL('../hamsters.jpg', import.meta.url));
  const blob = await response.blob();

  const size = import.meta.scriptElement.dataset.size || 300;

  const image = new Image();
  image.src = URL.createObjectURL(blob);
  image.width = image.height = size;

  document.body.appendChild(image);
})();

参考资料:

globalThis

在不同的运行环境下,获取全局对象的方式不同。

在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 windowself 或者 frames 取到全局对象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它们都无法获取,必须使用 global

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 方法!
}

因此为了简化代码,可以使用 globalThis 来调用全局对象。

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

参考资料:

ES 2019

Object.fromEntries

相当于 Object.entries 的反向操作,生成一个对象。

obj = Object.fromEntries([
  ['a', 0],
  ['b', 1],
]); // { a: 0, b: 1 }

可以将 Map 或者 Array 转化为 Object。

const map = new Map([
  ['foo', 'bar'],
  ['baz', 42],
]);
const obj = Object.fromEntries(map);
console.log(obj); // { foo: "bar", baz: 42 }
const arr = [
  ['0', 'a'],
  ['1', 'b'],
  ['2', 'c'],
];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }

如果键或者值不是字符串,则可能会出现意料之外的结果。

const map = new Map([
  [{}, 'a'],
  [{}, 'b'],
]);
Object.fromEntries(map);
// → { '[object Object]': 'b' }
// Note: the value 'a' is nowhere to be found, since both keys
// stringify to the same value of '[object Object]'.

参考资料:

String.prototype.{trimStart,trimEnd}

删除字符串中 开头/结尾 的连续空白符,也可以使用别名 String.prototype.{trimLeft,trimRight}

var str = '   foo  ';

console.log(str.length); // 8

str = str.trimStart(); // 等同于 str = str.trimLeft();
console.log(str.length); // 5
console.log(str); // "foo  "

str = str.trimRight(); // 或写成str = str.trimEnd();
console.log(str.length); // 6
console.log(str); // '   foo'

参考资料:

Array.prototype.{flat,flatMap}

Array.prototype.flat 用来把一个嵌套的数组展开成一个一维的数组。

var newArray = arr.flat([depth]);

depth 展开的深度,默认为 1。

var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

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

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

// flat 会去除空元素
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]

Array.prototype.flatMap 相当于把 map() 的结果进行 flat(),展开深度为 1。

var arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]

// 只会展开一层
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]

参考资料:

Function.prototype.toString

把函数的源码转换成字符串,建议直接看 MDN 上的 示例,很详细。

参考资料:Function.prototype.toString() - JavaScript | MDN

Symbol.prototype.description

返回 Symbol 的描述。

Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description; // ""
Symbol().description; // undefined

// well-known symbols
Symbol.iterator.toString(); // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"

// global symbols
Symbol.for('foo').toString(); // "Symbol(foo)"
Symbol.for('foo').description; // "foo"

参考资料:Symbol.prototype.description - JavaScript | MDN

可选的 catch 绑定

如果不需要 catch 中的 Error 绑定,则可以省略。

// 原来的写法,不需要 Error,但仍然要绑定变量到 Error
try {
  // 尝试使用可能不被支持的 Web 特性
} catch (unused) {
  // 退回到被广泛支持的 Web 特性
}
// 现在的写法
try {
  // ...
} catch {
  // ...
}

参考资料:tc39/proposal-optional-catch-binding: proposal for ECMAScript to allow omission of the catch binding

将 ECMAScript 语法拓展为 JSON 的超集

由于 JSON 中的字符串可以包含非转义的 U+2028 行分隔符 和 U+2029 段分隔符,而 ECMAScript 2018 及之前的字符串中不包含,所以在使用 JSON.parse 时会出现 SyntaxError

// A raw U+2029 character, produced by eval:
const PS = eval('"\u2029"');
// ES 2018: SyntaxError
// ES 2019: ok!

参考资料:

格式正确的 JSON.stringify

JSON 规定使用 UTF-8 进行编码,但是对于一些编码,JSON.stringify 可能会出现解析失败的问题(这个地方看文档不太明白,啥 UTF-16 什么的)。所以提案对这种情况采取转义 Unicode 的方式。

// Non-BMP characters still serialize to surrogate pairs.
JSON.stringify('𝌆');
// → '"𝌆"'
JSON.stringify('\uD834\uDF06');
// → '"𝌆"'

// Unpaired surrogate code units will serialize to escape sequences.
JSON.stringify('\uDF06\uD834');
// → '"\\udf06\\ud834"'
JSON.stringify('\uDEAD');
// → '"\\udead"'

参考资料:

ES 2018

异步迭代

这里只介绍 for-await-of 的用法,其他比如 generator 等内容可以查看下面的参考资料。

// 创建一个可迭代的异步对象
var asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return Promise.resolve({ value: this.i++, done: false });
        }

        return Promise.resolve({ done: true });
      },
    };
  },
};

// 然后可以使用 for await of 遍历这个对象
(async function () {
  for await (num of asyncIterable) {
    console.log(num);
  }
})();

// 0
// 1
// 2

参考资料:

对象属性的 SpreadRest 语法

把 ES 6 里的 ... 语法拓展到了对象的属性上。

// 把剩余的属性赋值给 z
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }
// 展开 z 中的属性
let n = { x, y, ...z };
n; // { x: 1, y: 2, a: 3, b: 4 }

参考资料:

Promise.prototype.finally

无论 Promise 的结果如何,都会执行回调函数。

let isLoading = true;

fetch(myRequest)
  .then(function (response) {
    var 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(function (json) {
    /* process your JSON further */
  })
  .catch(function (error) {
    console.log(error);
  })
  .finally(function () {
    isLoading = false;
  });

参考资料:

模板字符串的修订

带标签的模版字符串应该允许嵌套支持常见转义序列的语言(例如 DSLsLaTeX)。ECMAScript 提议模版字面量修订(第 4 阶段,将要集成到 ECMAScript 2018 标准)移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。

function latex(str) {
  return { cooked: str[0], raw: str.raw[0] };
}

latex`\unicode`;
// 非法转义序列变成了 undefined
// { cooked: undefined, raw: "\\unicode" }

// 不带标签的没事
let bad = `bad escape sequence: \unicode`;

参考资料:模板字符串 - JavaScript | MDN

正则标识 s

在正则表达式后添加标识 s 可以使得 . 匹配任意字符,包含行终止符 \n,用来代替 [^] 这个写法。

// ES 2017 -

/foo.bar/.test('foo\nbar');
// → false

/foo[^]bar/.test('foo\nbar');
// → true
// ES 2018 +

/foo.bar/s.test('foo\nbar');
// → true

参考资料:tc39/proposal-regexp-dotall-flag: Proposal to add the s (dotAll) flag to regular expressions in ECMAScript.

正则反向断言

肯定的反向断言使用 (?<=...) 的格式。

const reLookbehind = /(?<=\$)\d+(\.\d*)?/;
const match1 = reLookbehind.exec('$123.89');
const match2 = reLookbehind.exec('€123.89');

console.log(match1[0]); // 123.89
console.log(match2); // null

否定的反向断言使用 (?<!...) 的格式。

const reLookbehind = /(?<!\$)\d+(?:\.\d*)/;
const match1 = reLookbehind.exec('$10.53');
const match2 = reLookbehind.exec('€10.53');

console.log(match1[0]); // 0.53
console.log(match2[0]); // 10.53

参考资料:tc39/proposal-regexp-lookbehind: RegExp lookbehind assertions

正则命名组

在正则表达式中使用 ?<name> 的形式可以给一个匹配组进行命名。

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

也可以使用解构简化代码。

let {
  groups: { one, two },
} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`); // prints one: foo, two: bar

参考资料:tc39/proposal-regexp-named-groups: Named capture groups for JavaScript RegExps

正则 Unicode 转义

在正则表达式中使用 \p{…}\P{…} 的格式来转义 Unicode。

// GreekSymbol 是希腊符号的意思

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π');
// → true

参考资料:tc39/proposal-regexp-unicode-property-escapes: Proposal to add Unicode property escapes \p{…} and \P{…} to regular expressions in ECMAScript.

ES 2017

异步函数

使用 async 定义一个返回 Promise 的函数,函数内可以使用 await

async function name([param[, param[, ... param]]]) {
   statements
}

更多资料可以去 Google 的 文档 看看。

参考资料:

Object.{values,entries}

  • Object.values 返回一个对象所有可枚举的 属性值 的数组。
  • Object.entries 返回一个对象所有可枚举的 键值对 组成的数组。
var obj = { foo: 'bar', baz: 42 };
Object.values(obj); // ['bar', 42]
Object.entries(obj); // [ ['foo', 'bar'], ['baz', 42] ]

// 类似数组的对象
var obj = { 0: 'a', 1: 'b', 2: 'c' };
Object.values(obj); // ['a', 'b', 'c']
Object.entries(obj); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]

// 乱序的类似数组的对象
// 当使用数字作为键时,值将会按照键的数字顺序返回
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(an_obj); // ['b', 'c', 'a']
Object.entries(an_obj); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]

// getFoo 是一个不可枚举的属性
var my_obj = Object.create(
  {},
  {
    getFoo: {
      value: function () {
        return this.foo;
      },
    },
  }
);
my_obj.foo = 'bar';
Object.values(my_obj); // ['bar']
Object.entries(my_obj); // [ ['foo', 'bar']

// 非对象的参数将会强制转换成对象
Object.values('foo'); // ['f', 'o', 'o']
Object.entries('foo'); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]

Object.entries 还有很多用法。

// 优雅地遍历 key-value
const obj = { a: 5, b: 7, c: 9 };
for (const [key, value] of Object.entries(obj)) {
  console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
}

// 或者使用数组遍历对象
Object.entries(obj).forEach(([key, value]) => {
  console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
});

// 将 Object 转换成 Map
var obj = { foo: 'bar', baz: 42 };
var map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }

参考资料:

String.prototype.{padStart,padEnd}

用于 从头/从尾 部填充字符串到指定长度。

str.padStart(targetLength [, padString])
  • targetLength 要填充到的长度,如果小于当前字符串的长度,则返回当前字符串。
  • padString 要填充的字符串,超出的部分会被截断。默认填充空格
'abc'.padStart(10); // "       abc"
'abc'.padStart(10, 'foo'); // "foofoofabc"
'abc'.padStart(6, '123465'); // "123abc"
'abc'.padStart(8, '0'); // "00000abc"
'abc'.padStart(1); // "abc"

String.prototype.padEnd 同理。

'abc'.padEnd(10); // "abc       "
'abc'.padEnd(10, 'foo'); // "abcfoofoof"
'abc'.padEnd(6, '123456'); // "abc123"
'abc'.padEnd(1); // "abc"

参考资料:

允许在函数参数中添加末尾逗号

如果函数有很多参数,那么经过格式化之后,参数会垂直排列。如果要添加一个参数,那么在代码管理软件(例如 Git)看来,实际上修改了两行:上一个参数后添加逗号、新的参数。

function clownPuppiesEverywhere(
  param1,
  param2, // updated to add a comma
  param3 // updated to add new parameter
) {
  /* ... */
}

clownPuppiesEverywhere(
  'foo',
  'bar', // updated to add a comma
  'baz' // updated to add new parameter
);

但是如果允许默认情况下在最后一个参数后加上逗号,那么未来添加新的参数时,要修改的代码就只有一行。

function clownPuppiesEverywhere(
  param1,
  param2 // 添加下一个参数时只会修改下面这一行,而不会修改这一行
) {
  /* ... */
}

clownPuppiesEverywhere(
  'foo',
  'bar' // 添加下一个参数时只会修改下面这一行,而不会修改这一行
);

// 这样也是可以的
obj(1, 2, 3);

其实不止函数的参数可以这样做,数组、对象的末尾逗号也是可以的,并且在 ECMAScript 5 中就已经得到了支持。但是在 JSON 中是不行的。

参考资料:

Object.getOwnPropertyDescriptors

返回指定对象的所有自身属性的描述符。

Object.getOwnPropertyDescriptors(Date);

/** output
UTC: {writable: true, enumerable: false, configurable: true, value: ƒ}
length: {value: 7, writable: false, enumerable: false, configurable: true}
name: {value: "Date", writable: false, enumerable: false, configurable: true}
now: {writable: true, enumerable: false, configurable: true, value: ƒ}
parse: {writable: true, enumerable: false, configurable: true, value: ƒ}
prototype: {value: {…}, writable: false, enumerable: false, configurable: false}
__proto__: Object
*/

可以用来进行浅拷贝。

Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

参考资料:

共享内存和 Atomics

这里的共享内存指的是 SharedArrayBuffer 对象,用于 Web Worker 和主线程之间内存的共享。

关于 SharedArrayBufferAtomics 的更多信息可以查看下面的参考资料。

参考资料:

ES 2016

Array.prototype.includes

用于判断数组中是否包含某个值。

arr.includes(value[, fromIndex])
  • value 要查找的值。
  • fromIndex 开始查找的位置,默认为 0。
  • 返回值 Boolean。
[1, 2, 3].includes(2) === true;
[1, 2, 3].includes(4) === false;

[1, 2, NaN].includes(NaN) === true;

[1, 2, -0].includes(+0) === true;
[1, 2, +0].includes(-0) === true;

['a', 'b', 'c'].includes('a') === true;
['a', 'b', 'c'].includes('a', 1) === false;

String.prototype.includes 与这个方法类似。

参考资料:

指数运算符 x ** y

表示 x 的 y 次方,相当于 Math.pow(x, y)

// x ** y

let cubed = 2 ** 3;
// 相当于 2 * 2 * 2
// x **= y

let a = 2;
a **= 2;
// 相当于 a = a * a;

参考资料:tc39/proposal-exponentiation-operator: Progress tracking for ES7 exponentiation operator

其他参考资料