快速了解 ES2022 新特性

前端技术江湖

共 2516字,需浏览 6分钟

 ·

2021-11-07 11:03

点击上方关注 前端技术江湖一起学习,天天进步



作为一个后端的菜菜,其实应该多写点后端的东西,奈何同学们都喜欢看点前端的文章。这就没法子了🙄,前两天看到掘金群里有位同学不知道在哪儿看ES的新特性和ES规范与新特性的对应关系。我觉得我的机会来了🤣,忙了两天,今天抽点时间给大家科普一下,顺便讲讲 ES2016-ES2022 到底有啥内容,顺便复习一下,加深印象。

ES是啥?

ES的全称是ECMAScript,由 ECMA国际[3] (前身为欧洲计算机制造商协会)在标准 ECMA-262[4] 中定义的脚本语言规范,从2015年起,每年一个版本,到 ES2022 已经是第十三个版本了。我们常用的 JavaScript 就是 ECMA-262[5] 标准的实现和拓展。现在我直接贴一个官网的地址 ECMAScript[6] 吧,详细的内容大家可以直接查看官网的介绍。我就不过多介绍这个东西了。水字数没必要。。。🤥

尽管 ES2022 的还没有正式发布,但是提案已经完成,板上钉钉。下面我就按照大概的情况列一下各个ES版本提案的全称、发布年份、缩写信息等,这样大家就不会迷惑ES的命名了。

全称发布年份缩写 / 简称
ECMAScript 20152015ES2015 / ES6
ECMAScript 20162016ES2016 / ES7
ECMAScript 20172017ES2017 / ES8
ECMAScript 20182018ES2018 / ES9
ECMAScript 20192019ES2019 / ES10
ECMAScript 20202020ES2020 / ES11
ECMAScript 20212021ES2021 / ES12
ECMAScript 20222022ES2022 / ES13

有的同学可能会有这样的疑问,为啥 ES2015 对应的是 ES6 ,而不是 ES5 呢? 这里说明一下,这两个数字不是同一个东西,2015 代表的是发布的日期,而 6 表示的是ECMAScript语言规范的第几个版本。这样不懂的同学可能就明白了,再回头一看上面的表格,这样印象就更加深刻了。

ES2016 - ES2022的特性

骚话不多说,下面我们直接开始吧。我会将 ES2016 - ES2022 的内容分成一段段举例演示,内容主要以 Github已完成 提案[7] 上的顺序讲解。(PS: 没有 ES2015 / ES6 ,直接 ES2016 / ES7 起步)

ES2016 / ES7

Array.prototype.includes 数组是否包含某个元素

这个特性很多小伙伴肯定都用过,这个主要的作用是查找一个元素是否在数组中。在这个方法没出来之前,偷懒同学可能是通过数组的 indexOf 方法来做校验的

[123].indexOf(1) >= 0

// 结果: true
复制代码

正常情况下,indexOf 方法并没有啥毛病,但是这个方法存在一个漏洞,当需要查看的元素是 NaN 时,这个 indexOf 方法将不能够准确的判断出元素是否被包含在数组中

[12NaN].indexOf(NaN) >= 0

// 结果: false
复制代码

另外一个问题是,indexOf 这个方法主要想表明的是一个元素在数组中的 索引位置 而不是确定一个元素是否包含在数组中,所以勤快的同学一般会通过手写一个循环方法来处理这个问题,但是引用这个方法又比较麻烦,直接套在原型上?不敢不敢。。。

function contains(array, val{
    for (var i = 0; i < array.length; i++) {
        if (array[i] === val) {
            return true;
        }
    }
    return false;
}
复制代码

现在我们有了 includes 方法,上述的问题都将解决,并且更加好使,我们来测试一下以下代码,大家可以脑补一下下面的结果

console.log([123].includes(2) === true);
console.log([123].includes(4) === false);
console.log([12NaN].includes(NaN) === true);

console.log([12-0].includes(+0) === true);
console.log([12, +0].includes(-0) === true);

console.log(["a""b""c"].includes("a") === true);
console.log(["a""b""c"].includes("a"1) === false);
复制代码

我们现在公布答案,上面的七个结果均为 true 。毋庸置疑。。。

Exponentiation operator 幂运算

这个没有啥好讲的,直接看下面的代码片段就明白了

let squared = 2 ** 2;
// same as: 2 * 2

let cubed = 2 ** 3;
// same as: 2 * 2 * 2

let a = 2;
a **= 2;
// same as: a = a * a;

let b = 3;
b **= 3;
// same as: b = b * b * b;
复制代码

ES2017 / ES8

Object.values / Object.entries 对象值、对象对

这个也没啥好说的,Object.values 方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同(区别在于 for-in 循环枚举原型链中的属性),看代码

let point = {x12y6};
Object.values(point);

// 结果: [12, 6]
复制代码

Object.entries 方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。

听起来有点绕,咱们直接看代码

let point = {x12y6};
Object.entries(point);

// 结果: [["x", 12], ["y", 6]]
复制代码

字符串也同样ok

Object.entries("hello world");

// 结果: [["0","h"],["1","e"],["2","l"],["3","l"],["4","o"],["5"," "],["6","w"],["7","o"],["8","r"],["9","l"],["10","d"]]
复制代码

来试试顺序

let object = {
    3"z",
    1"x",
    2"y",
    z3,
    x1,
    y2
};

for (var key in object) {
    console.log(key);
}
// 1
// 2
// 3
// z
// x
// y

Object.entries(object);

// 结果: [["1","x"],["2","y"],["3","z"],["z",3],["x",1],["y",2]]
复制代码

我们可以看到,输出结果和上面我们的方法描述一致(其排列与使用 for...in 循环遍历该对象时返回的顺序一致)。同时,这儿还有个隐藏的 bufffor...in 会对数字的类型的 key 升序放在前面,不相信的同学自己也可以尝试一下哦。

String.prototype.padStart / String.prototype.padEnd 填充字符串

这两个方法还是比较常见的,我可以指定一个字符最大的宽度,当字符串超过最大长度则截断替换字符,保留左边(padStart)或者右边(padEnd)的内容当字符串宽度没有达到的时候,将会在开始(padStart )或者结尾(padEnd )填充指定的字符,填充字符未指定默认是一个 空格(U+0020) ,它主要用来格式化字符串,当然你也可以把它玩儿出花来。我们先来看一下定义

interface String {
    /**
     * 用给定字符串(可能重复)填充当前字符串,以使生成的字符串达到给定长度。填充从当前字符串的开始(左)开始应用。
     *
     * @param maxLength 填充当前字符串后所得字符串的长度。如果此参数小于当前字符串的长度,则当前字符串将按原样返回。
     *
     * @param fillString 用于填充当前字符串的字符串。如果此字符串太长,将截断它,并应用最左边的部分。参数的默认值为' '(U+0020)。
     */

    padStart(maxLength: number, fillString?: string): string;

    /**
     * 用给定字符串(可能重复)填充当前字符串,以使生成的字符串达到给定长度。从当前字符串的末尾(右侧)应用填充。
     *
     * @param maxLength 填充当前字符串后所得字符串的长度。如果此参数小于当前字符串的长度,则当前字符串将按原样返回。
     *
     * @param fillString 用于填充当前字符串的字符串。如果此字符串太长,将截断它,并应用最左边的部分。参数的默认值为' '(U+0020)。
     */

    padEnd(maxLength: number, fillString?: string): string;
}
复制代码

有的同学可能对上面的超出长度的截断的含义比较模糊,下面我们直接上手,看下输出就明白了

'foo'.padStart(5)
// 结果: '  foo'
'foo'.padStart(5'-')
// 结果: '--foo'
'foo'.padStart(5'xyz')
// 结果: 'xyfoo' (超出长度,'z' 字符被截断去掉了,保留了左边部分的内容)
'bar'.padEnd(5)
// 结果: 'bar  '
'bar'.padEnd(5'-')
// 结果: 'bar--'
'bar'.padEnd(5'xyz')
// 结果: 'barxy' (超出长度,'z' 字符被截断去掉了,保留了左边部分的内容)

// 原字符串长度超出最大字符串长度情况下
'foo'.padStart(2'xyz')
// 结果: 'foo'
'foo'.padStart(2'xyz')
// 结果: 'foo'
复制代码

Object.getOwnPropertyDescriptors 获取自身属性描述符

这个方法我们可以根据方法含义来理解,那就是获取自身属性的描述符。它常常配合 Object.create() 这个方法,来实现拷贝对象属性/对象属性特性以及原型。在理解这个方法之前,我们得知道啥是属性描述符。我们先定义一个对象打印一手,直接获取了对象的属性的描述符

let point = {
    x12,
    y16,
    move({x, y}) {
        this.x = x;
        this.y = y;
    }
}
Object.getOwnPropertyDescriptors(point);
// {
//     x: {
//         configurable: true,
//         enumerable: true,
//         value: 12,
//         writable: true,
//         [[Prototype]]: Object
//     }, 
//     y: {
//         configurable: true,
//         enumerable: true,
//         value: 16,
//         writable: true, 
//         [[Prototype]]: Object
//     },
//     move: {
//         configurable: true,
//         enumerable: true,
//         value: f move({x,y}),
//         writable: true, 
//         [[Prototype]]: Object
//     },
//     [[Prototype]]: Object
// }

let c = {}
Object.getOwnPropertyDescriptors(c);
// {}
复制代码

我们从上面的结果中,可以发现:如果对象为空,那么获取到的描述就是个空对象,如果有属性或者方法,会对每个属性进行描述,包含

  • configurable: 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false
  • enumerable: 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false
  • value: 该属性对应的值。可以是任何有效的 JavaScript 值( 数值 , 对象 , 函数 等)。默认为 undefined
  • writable: 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。默认为 false

详情我们可以在 Object.defineProperty\(\)[8] 这儿看到,这里的属性描述符实际上是给 Object.defineProperty\(\)[9] 的第三个参数用的。从上面的例子中,我们可以看出,我们直接定义一个对象 point ,并且直接设置属性方法,对象属性默认 configurable , enumerable , writable 设置为 true 。

我们现在给上面的例子再修改一下,加深理解。我们先遍历打印一下 point 的 key

for (var key in point) {
    console.log(key);
}
// x
// y
// move
复制代码

接着使用 Object.defineProperty\(\)[10] 方法修改一下 point 的 move 方法的描述符

Object.defineProperty(point, 'move', {
    enumerablefalse
});
for (var key in point) {
    console.log(key);
}
// x
// y
复制代码

我们可以看到 move 这个方法在 for..in 中已经没有打印名称了。我们用 Object.getOwnPropertyDescriptor\(\)[11] 这个 老方法 再看看 move 的属性描述符

Object.getOwnPropertyDescriptor(point, 'move');
//{
//    configurable: true,
//    enumerable: false,
//    value: f move({x,y}),
//    writable: true, 
//    [[Prototype]]: Object
//}
复制代码

符合我们的预期。大家看明白了属性描述符,那对这个新增的方法就能理解的明明白白了

Async functions 异步方法

这个感觉没有啥好写的,懂得都懂。async 和 await 相当于一个语法糖,被 async 标记的方法将会返回一个 Promise 对象。我们可以在一个 async 标记的方法中使用 await 一个 Promise 对象,当 Promise 结束之后才执行下一语句,这让我们可以在 async 标记的方法中用同步的方法来书写异步的 Promise 。我们简单举一个例子

async function a() {
    return "a";
}
function b() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("b");
        }, 3000);
    });
}
async function c() {
    return "c";
}
async function d() {
    let a = await a();
    console.log(a);
    let b = await b();
    console.log(b);
    let c = await c();
    console.log(c);
}
console.log(d())

// 结果:
// Promise {  } `async` 标记的方法返回了一个 `Promise` 对象
// a
// b  等待三秒之后继续输出后续内容
// c

复制代码

异步的方法同步的方式写,看起来很美好,但实际使用中还是有一点点问题。我之前写过一篇 JS中优雅的使用async await[12] ,有兴趣的小伙伴可以去瞅瞅。

Shared memory and atomics 共享内存和原子

这个是给浏览器的规范,我们可以通过 SharedArrayBuffer 和 Atomics 增强 js 的并行能力,想要了解的同学可以翻看 SharedArrayBuffer[13] 和 Atomics[14] ,因为出现的场景很少,我就不细讲了。加油啊各位。

总结

文章到这儿暂时告一段落,我一开始膨胀了,文章最开始的标题是 一文快速了解ES2016 \- ES2022新特性 。但是写下来,照我这个讲解程度,光 ES7 和 ES8 的篇幅就已经很长了,所以我现在打算将 ES2016 \- ES2022 的新特性拆分成多篇来写,目的呢,为了更好的让大家理解这些内容(才不是呢😝),给大家和我自己一点余地。嘻嘻

关于本文

作者:尽管如此世界依然美丽

https://juejin.cn/post/7021183043679289352

The End

欢迎自荐投稿到《前端技术江湖》,如果你觉得这篇内容对你挺有启发,记得点个 「在看」

点个『在看』支持下 

浏览 24
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报