# ES2016 - ES2022

ES全称ECMAScript,ECMAScript是ECMA制定的标准化脚本语言。目前JavaScript使用的ECMAScript版本为ECMA-417 (opens new window)。关于ECMA的最新资讯可以浏览 ECMA news (opens new window)查看。

ES2016 - ES2022 对应的是 ES7 - ES13

# ES2016

  • Array.prototype.includes
  • 求幂运算符

# Array.prototype.includes

const arr = [1, 3, 5, 2, '8', NaN, -0]
arr.includes(1) // true
arr.includes(1, 2) // false 该方法的第二个参数表示搜索的起始位置,默认为0
arr.includes('1') // false
arr.includes(NaN) // true
arr.includes(+0) // true

# 求幂运算符

console.log(2**10);// 输出1024
console.log(Math.pow(2, 10)) // 输出1024

# ES2017

  • Object.values 与 Object.entries
  • Object.getOwnPropertyDescriptors
  • String.prototype.padStart 与 Sting.prototype.padEnd
  • 函数参数中添加尾逗号
  • async/await

# Object.values 与 Object.entries

const obj = {
    foo: 'value1',
    bar: 'value2'
}

console.log(Object.keys(obj))   // 键
console.log(Object.values(obj)) // 值

console.log(Object.entries(obj))
// [["foo", "value1"], ["bar", "value2"]]

// 可以用这种方法来遍历对象
for(const [key, value] of Object.entries(obj)) {
    console.log(key, value)
}

// 对象转换为Map
new Map(Object.entries(obj))

# Object.getOwnPropertyDescriptors

该函数返回指定对象(参数)的所有自身属性描述符。所谓自身属性描述符就是在对象自身内定义,不是通过原型链继承来的属性。

描述符:

  • writable:表示该属性的值是否可以被修改

  • enumerable:表示该属性会是否出现在对象的枚举属性中。(for...in 和 Object.keys)

  • configurable:表示对象的属性是否可以被删除,以及除 valuewritable 特性外的其他特性是否可以被修改。

const obj = {foo: 'value1', bar: 'value2'}
Object.getOwnPropertyDescriptors(obj)

// 获取对象的完整描述信息
{
  foo: {
    value: "value1",
    writable: true,
    enumerable: true,
    configurable: true
  },
  bar: {
    value: "value2",
    writable: true,
    enumerable: true,
    configurable: true
  }
}

# padStart 与 padEnd

// padEnd和padStart可以使输出更工整
const books = {
    html: 5,
    css: 16,
    javascript: 128
}
for(const [name, count] of Object.entries(books)) {
    console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}

// html------------|005
// css-------------|016
// javascript------|128

# 尾逗号

const arr = [
    100,
    200,
    300,
]

# ES2018

  • 异步迭代 for await...of
  • Promise.prototype.finally
  • 对象 rest/spread
  • Regexp
    • dotAll
    • 命名分组捕获

# 异步迭代

for...of 内部是迭代器的应用,而 for await...of 对应的是异步迭代器。

function fetchData(d) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(d)
    }, 1000)
  })
}
const dataList = [1, 2, 3];

const promiseList = [];
dataList.forEach(d => {
    promiseList.push(fetchData(d)); // 此处 fetchData 已经开始执行了
});

for await (p of promiseList) {
  console.log(p) // 隔一秒同时且按顺序输出 1,2,3
}

For await...of 将会按迭代顺序收到响应。除非当前迭代的计算取决于之前的一些迭代,否则大部分业务场景下用 promise.all 即可解决问题。

const promiseList = [];
dataList.forEach(d => {
    promiseList.push(fetchData(d)); // 此处已经开始执行了
});

Promise.all(promiseList).then(res => {
  console.log(res) // [1, 2, 3]
})

# Promise.finally

ES6为我们带来了 Promise,但是它的结果要么成功 then 要么失败 catch,使得我们的一些逻辑,如执行状态修改,结束回调都得在两边写一遍。有了 finally(),逻辑只可以放在一个地方了。

new Promise((reslove, reject) => {
  // ...
}).then((res) => {
  // reslove
}).catch((err) => {
  // reject
}).finally(() => {
  // complete
});

# rest/spread

ES2015 引入了三个点(...)运算符,这个运算符既能用来收集函数的剩余参数,也可以用来扩展数组。

ES2018 对其进行了增强,将扩展和收集参数的能力扩大到了对象。使得 ... 运算符也可以用来收集对象的“剩余属性”。

// rest
const obj = {
  a: 1,
  b: 2,
  c: 3
};

const { a, ...x } = obj; // a = 1, x = { b: 2, c: 3 }

// spread
const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { ...obj1, z: 26 }; // { a: 1, b: 2, c: 3, z: 26 }

# Regexp

RegExp 在 ES2018 中新增dotAll (点匹配)命名分组捕获等。

// ES2018引入了dotAll模式,通过使用标记s选项,.就可以匹配换行符。
/hello.es9/.test('hello\nes9');  // false
/hello.es9/s.test('hello\nes9');  // true

// 命名分组捕获,ES2018允许命名捕获组使用符号?<name>, 可以指定小括号中匹配内容的名称放在groups里
const reg = /(\d{4})-(\d{2})-(\d{2})/u;
const matched = reg.exec('2018-12-31');
matched[0];  // 2018-12-12
matched[1];  // 2018
matched[2];  // 12
matched[3];  // 31

const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const matched = reg.exec('2018-12-31');
matched.groups.year;   // 2018
matched.groups.month;  // 12
matched.groups.day;    // 31

# ES2019

  • Object.fromEntries
  • flat 与 flatMap
  • trimStart 与 trimEnd

# Object.fromEntries

通常用于将数组转化为对象,通常可以作为 Object.entries() 的反操作。

const obj = {name: '小米', skin: 'white', age: 2}
console.log(Object.entries(obj)); 
// [['name','小米'],['skin','white'],['age',2]]

const arr = [['name','小米'],['skin','white'],['age',2]];
console.log(Object.fromEntries(arr));
// {name: '小米', skin: 'white', age: 2}

# flat 与 flatMap

flat 常用于将多维数组降维为一维数组,可以传参数,表示降维的深度,默认是1

flatMap 相当于先对数组进行map操作,然后进行flat操作

[[1,2],[3,4]].flat(); // [1, 2, 3, 4]
[[1,2],[3,4]].flatMap(i => i.reverse()) //  [2, 1, 4, 3]

# trimStart 与 trimEnd

去除字符串(前)后面的空格

const str = ' Hello,ES2019! ';
console.log(str.trimStart().trimEnd()); // Hello,ES2019!

# ES2020

https://h3manth.com/ES2020/ (opens new window)

# matchAll

// match
const str = 'Dr. Smith and Dr. Anderson';
const re = /(Dr\. )\w+/g;
const res = str.match(re);  //["Dr. Smith", "Dr. Anderson"]

// matchAll
const re = /(Dr\. )\w+/g;
const str = 'Dr. Smith and Dr. Anderson';
const matches = str.matchAll(re);

// 返回的是迭代器对象需要 for...of 遍历
for (const match of matches) {
  console.log(match);
}

// ["Dr. Smith", "Dr. ", index: 0, input: "Dr. Smith and Dr. Anderson", groups: undefined]
// ["Dr. Anderson", "Dr. ", index: 14, input: "Dr. Smith and Dr. Anderson", groups: undefined]

# Promise.allSettled

Promise.all 具有并发执行异步任务的能力。但它的最大问题就是如果参数中的任何一个promise为reject的话,则整个Promise.all 调用会立即终止,并返回一个reject的新的 Promise 对象。Promise.allSettled 的出现就可以解决这个痛点

Promise.allSettled([
  Promise.reject({ code: 500, msg: '服务异常' }),
  Promise.resolve({ code: 200, list: [] }),
  Promise.resolve({ code: 200, list: [] })
]).then(res => {
  console.log(res)
  /*
        0: {status: "rejected", reason: {…}}
        1: {status: "fulfilled", value: {…}}
        2: {status: "fulfilled", value: {…}}
   */
})

# Dynamic import

动态导入表达式是 ECMAScript 的一个新功能,它允许你在程序的任意位置异步加载一个模块。

现在前端打包资源越来越大,前端应用初始化时根本不需要全部加载这些逻辑资源,为了首屏渲染速度更快,很多时候都是动态导入(按需加载)模块,比如懒加载图片等,这样可以帮助您提高应用程序的性能。

其中按需加载这些逻辑资源都一般会在某一个事件回调中去执行:

el.onclick = () => {
  import('/modules/my-module.js')
    .then(module => {
      // Do something with the module.
    })
    .catch(err => {
      // load error;
    })
}

这种使用方式也支持 await 关键字。

let module = await import('/modules/my-module.js');

# ES2021

https://h3manth.com/ES2021/ (opens new window)

  • 逻辑赋值运算符
  • replaceAll
  • Promise.any
  • 数值分隔符

# 逻辑赋值运算符

||=  // a ||= b 相当于 a = a || b
&&=  // a &&= b 相当于 a = a && b
??=  // a ??= b 相当于 a = a ?? b

# replaceAll

// ES2021 之前
'1 2 1 2 1 2'.replace(/2/g, '0'); // '1 0 1 0 1 0'

// 现在
'1 2 1 2 1 2'.replaceAll('2', '0'); // '1 0 1 0 1 0'

# Promise.any

  • Promise.all()中的Promise序列会全部执行通过才认为是成功,否则认为是失败;
  • Promise.race()中的Promise序列中第一个执行完毕的是通过,则认为成功,如果第一个执行完毕的Promise是拒绝,则认为失败;
  • Promise.any()中的Promise序列只要有一个执行通过,则认为成功,如果全部拒绝,则认为失败;

Promise.any()适合用在通过不同路径请求同一个资源的需求上。

例如,Vue3.0在unpkg和jsdelivr都有在线的CDN资源,都是国外的CDN,国内直接调用不确定哪个站点会抽风,加载慢,这时候可以两个资源都请求,哪个请求先成功就使用哪一个。

比方说unpkg的地址是:https://unpkg.com/vue@3.0.11/dist/vue.global.js jsdelivr的地址是:https://cdn.jsdelivr.net/npm/vue@3.0.11/dist/vue.global.js

let startTime = +new Date();
let importUnpkg = import('https://unpkg.com/vue@3.0.11/dist/vue.runtime.esm-browser.js');
let importJsdelivr = import('https://cdn.jsdelivr.net/npm/vue@3.0.11/dist/vue.runtime.esm-browser.js');
Promise.any([importUnpkg, importJsdelivr]).then(vue => {
  console.log('加载完毕,时间是:' + (+new Date() - startTime) + 'ms');
  console.log(vue.version);
});

# 数值分隔符

const a = 785_00;
const b = 1_000_000_000;
console.log(a);                // 78500
console.log(b);                  // 1000000000
console.log(a === 78500);      // true
console.log(b === 1000000000);   // true

# ES2022

https://h3manth.com/ES2022/ (opens new window)

  • 顶层 await
  • at() 方法
  • Object.hasOwn()
  • class 私有字段

# 顶层 await

允许开发者在 async 函数外部使用 await 字段。它就像巨大的 async 函数,原因是 import 它们的模块会等待它们开始执行它的代码。

// 过去
await Promise.resolve(console.log('🎉'));
// → SyntaxError: await is only valid in async function

(async function () {
  await Promise.resolve(console.log('🎉'));
  // → 🎉
})();

// 现在
await Promise.resolve(console.log('🎉'));

以下例子尝试加载一个来自 first.com 的 JavaScript 模块,加载失败会有回退方案:

//module.mjs
let module;

try {
  module = await import('https://first.com/libs.com/module1');
} catch {
  module = await import('https://second.com/libs/module1');
}

这里 res 变量的初始值由最先结束的下载请求决定。

//module.mjs
const resPromises = [    
    donwloadFromResource1Site,
    donwloadFromResource2Site
];

const res = await Promise.any(resPromises);

# at() 方法

在此之前我们使用方括号 ([]) 来访问数组的第 N 个元素。但是,如果我们想使用方括号访问数组末尾的第 N 个项目,我们必须使用 arr.length - N 的索引。at 方法可以解决这个问题。

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

# Object.hasOwn()

在此之前,我们判断对象自身是否拥有某属性时,可以使用 hasOwnProperty,但是会有一些问题。

// 对于使用 null 原型创建的对象尝试对其调用此方法会导致错误。
const obj = Object.create(null);
obj.color = 'green';
// TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty('color'));

// hasOwnProperty() 方法不受保护, 可以被覆盖
const car = {
 color: 'green',
 hasOwnProperty: function() {
   return false;
 }
}
console.log(car.hasOwnProperty('age')); // false

当然以上问题都可以使用 Object.prototype.hasOwnProperty 来解决,但是我们以后可以直接用 Object.hasOwn 来替换。

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

const object1 = {
  prop: 'exists'
};

console.log(Object.hasOwn(object1, 'prop'));
// expected output: true

console.log(Object.hasOwn(object1, 'toString'));
// expected output: false

# class 私有字段

class Foo {
  #iteration = 0;
  
  increment() {
    this.#iteration++;
  }
  
  #auditIncrement() {
    console.log('auditing');
  }

  logIteration() {
    console.log(this.#iteration);
  }
}

const x = new Foo();

x.#iteration // error
x.#auditIncrement() // error
x.increment(); // ✅ works
x.logIteration(); // ✅ works
最后更新时间: 12/26/2022, 6:16:52 PM