# 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:表示对象的属性是否可以被删除,以及除
value
和writable
特性外的其他特性是否可以被修改。
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