这篇文章上次修改于 250 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

ES9~ES10新特性

ES9(ES2018)

异步迭代(遍历)器

同步迭代器

原理:

  • Iterable:一个对象(数组,Set, Map,字符串,类数组),表示可以通过Symbol.iterator方法进行迭代。
  • Iterator:通过调用iterable [Symbol.iterator] 函数返回的对象。它将每个迭代元素包装在一个对象中,并通过其next()方法一次返回一个。
  • IteratorResult:next()返回的对象{value: xxx, done: Boolean}。属性value包含一个迭代的元素,属性done表示是否遍历到了最后一个数据,即为true时。

下面创建一个迭代器

const createIterator = items => {
  const len = items.length;
  let nextIdx = 0;
  return {
    next() {
      const done = nextIdx >= len;
      const value = !done ? items[nextIdx++] : undefined;
      return {
        done,
        value
      }
    }
  }
}

const iterator = createIterator([1, 2, '34']);
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: '34', done: false}
console.log(iterator.next()); // {value: undefined, done: true}

纯对象类型无迭代iterator接口,所以不能创建遍历器进行遍历,即不能使用for...of遍历,但可以通过给对象定义一个[Symbol.iterator] 属性方法来实现,可以简单实现下

const obj = {
  a: 1,
  b: 2
}
obj[Symbol.iterator] = function() {
  const _this = this;
  const keys = Object.keys(_this)
  const len = keys.length;
  let nextIdx = 0;
  return {
    next() {
      const done = nextIdx >= len;
      const value = !done ? _this[keys[nextIdx++]] : undefined;
      return {
        done,
        value
      }
    }
  }
}

for(const it of obj) {
  console.log(it); 
} // 1 2

异步迭代器

其实就是在使用迭代器遍历时的next方法返回的是个Promise对象,下面实现一个

const createAsyncIterable = items => {
  const len = items.length;
  let nextIdx = 0;
  return {
    next() {
      const done = nextIdx >= len;
      const value = !done ? items[nextIdx++] : undefined;
      return Promise.resolve({
        value,
        done
      })
    }
  }
}

async function asyncIterator() {
  const asyncIt = createAsyncIterable('123');
  await asyncIt.next(); // {value: '1', done: false}
  await asyncIt.next(); // {value: '2', done: false}
  await asyncIt.next(); // {value: '3', done: false}
  await asyncIt.next(); // {value: undefined, done: true}
}
asyncIterator();

ES9是通过for await...of来遍历异步迭代器的

const promises = [
  new Promise(resolve => resolve(1)),
  new Promise(resolve => resolve(2)),
  new Promise(resolve => resolve(3)),
];

async function test() {
  for await (const p of promises) {
      console.log(p);
  }
}
test(); // 1 2 3

同样可以给对象添加异步迭代接口

const obj = {
  a: 1,
  b: 2
}
obj[Symbol.asyncIterator] = function() {
  const _this = this;
  const keys = Object.keys(_this)
  const len = keys.length;
  let nextIdx = 0;
  return {
    next() {
      const done = nextIdx >= len;
      const value = !done ? _this[keys[nextIdx++]] : undefined;
      return new Promise(resolve => {
        resolve({value, done});
      })
    }
  }
}
async function _asyncObj() {
  for await (const p of obj) {
      console.log(p);
  }
}
_asyncObj(); // 1 2

Rest/Spread 属性

这个就是我们通常所说的rest参数和扩展运算符,这项特性在ES6中已经引入,但是ES6中的作用对象仅限于数组:

restParam(1, 2, 3, 4, 5);

function restParam(p1, p2, ...p3) {
  // p1 = 1
  // p2 = 2
  // p3 = [3, 4, 5]
}

const values = [99, 100, -1, 48, 16];
console.log( Math.max(...values) ); // 100

在ES9中,为对象提供了像数组一样的rest参数和扩展运算符:

const obj = {
  a: 1,
  b: 2,
  c: 3
}
const { a, ...param } = obj;
  console.log(a)     //1
  console.log(param) //{b: 2, c: 3}

function foo({a, ...param}) {
  console.log(a);    //1
  console.log(param) //{b: 2, c: 3}
}

可用于对象的浅拷贝

const obj = {a: 1, b: 2, c: {z: 1}};
const _obj = {...obj};
_obj.c.z = 5;
_obj.a = 3;
console.log(obj.c, obj.a); // {z: 5} 1
console.log(_obj.c, _obj.a); // {z: 5} 3

正则表达式命名捕获组

编号捕获组

捕获的结果存放在数组里,通过数组索引遍历

const str = '2020-01-16';
const reg1 = /([0-9]{4})-([0-9]{2})-([0-9]{2})/g;
const res = reg1.exec(str); // ["2020-01-16", "2020", "01", "16", index: 0, input: "2020-01-16", groups: undefined]
const year = res[1];

命名捕获组

通过?<xxx>来定每个捕获数据的名称,并按顺序保存到groups对象中

const str = '2020-01-16';
const reg = /(?<years>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
const res = reg.exec(str); 

/*
["2020-01-16", "2020", "01", "16", index: 0, input: "2020-01-16",
  groups: {
    {years: "2020", month: "01", day: "16"}
  }
]
*/
const year = res.groups.years; // '2020'

// 可通过replace改变str顺序结构
const newStr = str.replace(reg, `${month}-${years}-${day}`);
conaole.log(newStr); // '01-2020-16'

正则Unicode 转义

该特性允许您使用\p{}通过提及大括号内的Unicode字符属性来匹配字符,在正则表达式中使用标记 u (unicode) 设置。通过Unicode 转义可以实现汉字匹配,

oldReg=/[\u4e00-\u9fa5]/
newReg=/\p{Script=Han}/u

oldReg.test('abc') // false
newReg.test('abc') // false

oldReg.test('地平线') // true
newReg.test('地平线') // true

oldReg.test('哈') // false
newReg.test('哈') // true

反向断言

先行断言 (?=pattern)

const str = '$123';
const res = /\D(?=\d+)/.exec(str);  // 捕获  $
console.log(res[0]); // $

反向断言

const str = '$123';
const res = /(?<=\D)\d+/.exec(str);  // 捕获  数字
console.log(res[0]); // 123

正则表达式dotAll模式

正则表达式中.匹配除回车外的任何单字符,标记s改变这种行为,允许行终止符的出现,例如:

/hello.world/.test('hello\nworld');  // false
/hello.world/s.test('hello\nworld'); // true

模板字符串修改

ES2018 移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。
之前,u开始一个 unicode 转义,x开始一个十六进制转义,后跟一个数字开始一个八进制转义。这使得创建特定的字符串变得不可能,例如Windows文件路径 C:uuuxxx111。

要取消转义序列的语法限制,可在模板字符串之前使用标记函数String.raw:

`\u{54}`;  // "T"
String.raw`\u{54}`; // "\u{54}"

Promise.finally()

不管Promise是成功态还是失败态,Promise.finally()都会执行,语法是这样

promise
  .then(result => {···})
  .catch(error => {···})
  .finally(() => {···});

ES10(ES2019)

Array.prototype.flat() 、Array.prototype.flatMap()

Array.prototype.flat() 拉平数组,默认值为1

let arr = [1, 2, [3, 4, [5, [6]]]];
console.log(arr.flat()); // [1, 2, 3, 4, [5, [6]]]
console.log(arr.flat(0)); // [1, 2, [3, 4, [5, [6]]]]
console.log(arr.flat(1)); // [1, 2, 3, 4, [5, [6]]]
console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

Array.prototype.flatMap() 可以看成 flat(1)与 map() 的组合体

let arr1 = [1, 2, 3, 4];
console.log(arr1.map(current => [1 * current])); // [[1], [2], [3], [4]]
console.log(arr1.flatMap(current => [1 * current])); // [1, 2, 3, 4]
console.log(arr1.flatMap(current => [[1 * current]])); // [[1], [2], [3], [4]]

Object.fromEntries()

参数传入对象类型数据会报错,只接收含迭代器的数组列表,返回的是一个纯对象,可以认为与 Object.entries() 相反

const obj = {
    a: 1,
    b: 2
}
const list = [
    ['a', 1],
    ['b', 2]
];

const mapList = new Map([
    ['a', 1],
    ['b', 2]
])

console.log(Object.fromEntries(obj)); // object is not iterable (cannot read property Symbol(Symbol.iterator))
console.log(Object.fromEntries(list)); // {a: 1, b: 2}
console.log(Object.fromEntries(mapList)); // {a: 1, b: 2}  map对象接受的参数也必须是一个含迭代器的数组列表

String.prototype.matchAll()

返回正则匹配结果的迭代器

const str1 = 'xiaoliu a xiaoliu xiao';
const reg1 = /xiao*/g;
console.log(str1.match(reg1)); // ["xiao", "xiao", "xiao"]
console.log(str1.matchAll(reg1)); // 返回的是一个迭代器  RegExpStringIterator {}

所以可以通过for...of扩展运算符(...)遍历

const it = str1.matchAll(reg1);

console.log(it.next()); // {value: ["xiao", index: 0, input: "xiaoliu a xiaoliu xiao", groups: undefined], done: false}
console.log(it.next()); // {value: ["xiao", index: 10, input: "xiaoliu a xiaoliu xiao", groups: undefined], done: false}
console.log(it.next()); // {value: ["xiao", index: 18, input: "xiaoliu a xiaoliu xiao", groups: undefined], done: false}
console.log(it.next()); // {value: undefined, done: true}

// for....of
for(const item of it) {
    console.log(item);
}
/*
    ["xiao", index: 0, input: "xiaoliu a xiaoliu xiao", groups: undefined]
    ["xiao", index: 10, input: "xiaoliu a xiaoliu xiao", groups: undefined]
    ["xiao", index: 18, input: "xiaoliu a xiaoliu xiao", groups: undefined]
*/

// ...
 console.log([...it])
 /**
  [
    ["xiao", index: 0, input: "xiaoliu a xiaoliu xiao", groups: undefined],
    ["xiao", index: 10, input: "xiaoliu a xiaoliu xiao", groups: undefined],
    ["xiao", index: 18, input: "xiaoliu a xiaoliu xiao", groups: undefined]
  ]
 */

trimStart() trimEnd() 去除首尾空格

let greeting = "     Space around     ";
greeting.trimEnd();   // "     Space around";
greeting.trimStart(); // "Space around     ";

Symbol.prototype.description

返回Symbol对象的描述,就是第一个参数

const sym = Symbol('xiaoliu');

console.log(String(sym)); // Symbol(xioaliu)
console.log(sym.description); // xiaoliu
console.log(Symbol('').description); // ""
console.log(Symbol().description); // undefined
console.log(Symbol.iterator.description); // "Symbol.iterator"
console.log(Symbol('foo').description + 'bar'); // 'foobar'

try{}catch{}

catch的参数是可选的,不写参数时,只进行错误兼容处理,不会捕获到错误信息

行分隔符(U + 2028)和段分隔符(U + 2029)符号现在允许在字符串文字中,与JSON匹配 草案阶段

console.log('asss\ndadasd');
/* asss
   dadasd */
const json = '{"name": "xiao\nliu"}';
console.log(json);
/* xiao
   liu */
console.log(JSON.parse(json));
/* {"name": "xiao
   liu"} */
console.log(JSON.stringify(json)); // 报错

更加友好的JSON.stringify()

修复了字符 U+D800U+DFFF 的处理,有时可以进入 JSON 字符串。 这可能是一个问题,因为 JSON.stringify 可能会将这些数字格式化为没有等效 UTF-8 字符的值, 但 JSON 格式需要 UTF-8 编码。

// 期待转义后  ""\\UDEAD""
JSON.stringify('\UDEAD'); // ""UDEAD""

ES10 之前的 EcmaScript 实际上并不完全支持 JSON 格式。前 ES10 时代不接受未转义行分隔符 U+2028 和段落分隔符 U+2029 字符,对于 U+D800 - U+DFFF 之间的所有字符也是如此。

稳定的 Array.prototype.sort()

v8之前是一种不稳定的快排算法,何为不稳定就是当用于比较的数字相等时,排序结果会不按照初始时的顺序进行排序。

let arr = [
  {name: 'xiao', age: 2},
  
];

arr.sort((a, b) => a.age - b.age);

/* 之前的V8  相等的会反向排列成员
[
    {name: 'aa', age: 1},
    {name: 'a', age: 1},
    {name: 'liu', age: 1},
  {name: 'xiao', age: 2},
]
*/

/* 现在的V8  相等不会改变成员的排列顺序
[
    {name: 'liu', age: 1},
  {name: 'a', age: 1},
  {name: 'aa', age: 1},
  {name: 'xiao', age: 2},
]
*/

Function.toString()

Function可认为是一个可执行的特殊对象,之前函数调用toString时,调用的是Object.prototype.toString方法。

function test() { // acas
  //ascas
}
test.toString(); 

/* 去掉了注释
f test() {
}
*/

ES10为Function.prototype添加了toString方法,覆盖了Object提供的toString方法。该方法会返回定义函数的源文片段,不会删除不需要的内容,如注释

function test() { // acas
  //ascas
}
test.toString(); 

/* 保留了注释
ƒ test() { // acas
  //ascas
}
*/

BigInt

ES10之前有六种基本的数据类型 ==> String Number Boolean undefined null Symbol,现在又多了个基本的数据类型BigInt,它是一个数字类型的数据,可以表示任意精度的整数,即大于2^53 - 1 的整数亦能表示。

// 有自己的类型
typeof BigInt(12); // 'bigint'

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

const maxPlusOne = previousMaxSafe + 1n;
// 9007199254740992n
 
const theFuture = previousMaxSafe + 2n;
// 9007199254740993n, this works now!

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

当使用Object包裹时,会被认为是一个普通的对象。远算的结果只会是一个整数,若为小数就向下取整。

typeof Object(1n) === 'object'; // true

标准化globalThis

这在ES10之前, globalThis 还没有标准化。

在产品代码中,你可以自己编写这个怪物,在多个平台上“标准化”它:

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');
};

但即使这样也不总是奏效。因此,ES10 添加了 globalThis 对象,从现在开始,该对象用于在任何平台上访问全局作用域,如在chrome上打印

console.log(globalThis); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}

ES10类:private、static 和 公共成员

新的语法字符 #octothorpe(hash tag)现在用于直接在类主体的范围内定义变量,函数,getter 和 setter ......以及构造函数和类方法。

下面是一个毫无意义的例子,它只关注新语法:

class Raven extends Bird {
#state = { eggs: 10};
// getter
    get #eggs() { 
        return state.eggs;
    }
// setter
    set #eggs(value) {
        this.#state.eggs = value;
    }
    #lay() {
        this.#eggs++;
    }
    constructor() {
        super();
        this.#lay.bind(this);
    }
    #render() {
        /* paint UI */
    }
}