ECMAScript2022新增特性总结

1.at()

at()方法读取this的length属性并计算需要访问的索引。at方法的参数为一个整数,正整数时从开始位置查找,负整数时从末尾开始查找。主要用于查找数组或字符串中某个索引对应的元素,也可以查找特殊对象上的属性。

1.1at在字符串上的应用

let str='at_str.js';

console.log(str.at(2));//_
console.log(str.at(-2));//j

console.log(str.charAt(2));//_
console.log(str.charAt(-2));//
console.log(str.charAt(str.length - 2));//j

通过上面的代码可以看出,at相较于charAt的优势在于charAt并不支持传负数(反向查找),因此使用at代码更加简洁。

1.2at在数组上的应用

let arr=[0,1,2,3,4,5,6,7,8,9];

console.log(arr[0]);//0
console.log(arr[-3]);//undefined
console.log(arr[arr.length - 3]);//7
console.log(arr.at(0));//0
console.log(arr.at(-3));//7

//修改arr的length属性
arr.length=5;//此时的arr变成了[ 0, 1, 2, 3, 4 ]
console.log(arr[0]);//0
console.log(arr[-3]);//undefined
console.log(arr[arr.length - 3]);//2
console.log(arr.at(0));//0
console.log(arr.at(-3));//2
企业微信截图_20221026140621.png

和字符串一样,at相较于直接使用[]查找元素时的优势也在反向查找。因为方括号内的所有值都会被视为字符串属性,因此arr[-3]最终读取的是arr["-3"],而arr数组的键名是0~length-1的字符串,并没有"-3",因此arr[-3]是undefined。

1.3at在特殊对象上的应用

let arrayLike_1 = {
    length: 2,
    0: "a",
    1: "b",
};
console.log(Array.prototype.at.call(arrayLike_1, -1)); // b

let arrayLike_2 = {
    length: 3,
    0: "a",
    1: "b",
};
console.log(Array.prototype.at.call(arrayLike_2, -1));// undefined
console.log(Array.prototype.at.call(arrayLike_2, -2)); // b

这里为两个arrayLike对象都设置一个length属性,并将Array的at方法的this指向arrayLike。由于arrayLike_2的length为3,at传入-1时相当于查找arrayLike_2[2],但是arrayLike_2并没有2这个属性,因此是undefined。

2.Object.hasOwn()

与Object.prototype.hasOwnProperty()类似,Object.hasOwn(obj,key)用于判断obj对象本身是否具有key属性,如果有则返回true,没有则返回false,但是使用Object.hasOwn()更加可靠。

先来看看下面的代码:

let obj={
    a:'value_a',
    b:'value_b',
}

console.log(Object.hasOwn(obj, 'a'));//true
console.log(obj.hasOwnProperty('a'));//true

console.log(obj.__proto__===Object.prototype);//true

console.log('hasOwnProperty' in obj);//true
console.log(Object.hasOwn(obj, 'hasOwnProperty'));//false
console.log(obj.hasOwnProperty('hasOwnProperty'));//false

从上面的代码可以看出,Object.hasOwn()的功能确实与Object.prototype.hasOwnProperty()一样。当然这是在obj对象能够正常访问hasOwnProperty的前提下,那么再看看下面两种情况呢:

let obj={
    a:'value_a',
    hasOwnProperty:function (key) {
        return true;
    }
}

console.log(obj.hasOwnProperty('DFQWER'));//true
console.log(Object.hasOwn('DFQWER'));//false

当重写hasOwnProperty时,obj在自身能够找到hasOwnProperty属性,所以就不会再去原型链上找hasOwnProperty了。
再看看下面这个:

let obj=Object.create(null,{
    a:{
        value:'value_a',
        enumerable:true,
    },
})

console.log(Object.hasOwn(obj, 'a'));//true
console.log(obj.hasOwnProperty('a'));//TypeError: obj.hasOwnProperty is not a function

当使用Object.create(null)创建obj时,obj的原型为null,调用hasOwnProperty将报错。
以上两种情况都与obj的原型相关,使用静态方法Object.hasOwn()可以完美的避开这两个问题。

3.Error.prototype.cause

在ECMAScript2022规范中,new Error() 新增了一个配置项,可以通过cause属性指定导致错误的原因。但是我在16.17.1版本的node环境和Chrome106中尝试使用该属性都无法生效,后续能够使用再更新吧。这是MDN对该特性的介绍。https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause

4.正则表达式匹配索引

该特性允许我们利用d字符来表示我们想要匹配字符串的开始和结束索引。以前,只能在字符串匹配操作期间获得一个包含提取的字符串和索引信息的数组。在某些情况下,这是不够的。因此,在这个规范中,如果设置标志/d,将额外获得一个带有开始和结束索引的数组。
以下这段代码将分别匹配字符串中的数字和字母

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

</body>
<script>
    let str = '12345QWERdf';
    let match = /([0-9]+)([a-z]+)/dgi.exec(str);
    console.log(match[1]);//12345
    console.log(match[2]);//QWERdf
    console.log(match);
</script>
</html>

以下是输出结果,从图中可以看到匹配到的索引信息在indices属性中


企业微信截图_20221031101813.png

还可以为每个匹配项进行命名:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

</body>
<script>
    let str = '12345QWERdf';
    let match = /(?<number>[0-9]+)(?<letter>[a-z]+)/dgi.exec(str);
    console.log(match[1]);//12345
    console.log(match[2]);//QWERdf
    console.log(match);
</script>
</html>

从输出结果可以看到,匹配项groups属性中,其索引信息在indices属性下的groups属性中:


企业微信截图_20221031102138.png

5.Top-level Await(顶层await)

在ES2017中,引入了 async 函数和 await 关键字,以简化 Promise 的使用,但是 await 关键字只能在 async 函数内部使用。尝试在异步函数之外使用 await 就会报错:SyntaxError - SyntaxError: await is only valid in async function。
因此,在顶层await出现之前,如果要引入的模块中存在异步任务,为了保证在模块引入之后确保能够拿到异步数据,我们往往这样做:

//export.js
export default (async function () {
    return await new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve('3秒后输出的数据');
        },3000)
    })
})()

//html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>top level await</title>
</head>
<body>

</body>
<script type="module">
    import data from './export.js';
    data.then(res=>{
        //这里才能确保拿到导入的模块中的异步数据
        console.log(res);//3秒后输出的数据
    })
</script>
</html>

这种方法确实可以解决模块中存在异步数据的问题,但是模块使用者必须了解这种模式才能正确地使用。而顶层await的出现就完美的解决了这个问题。

顶层 await 允许我们在 async 函数外面使用 await 关键字。它允许模块充当大型异步函数,通过顶层 await,这些 ECMAScript 模块可以等待资源加载。这样其他导入这些模块的模块在执行代码之前要等待资源加载完再去执行。

使用顶层await,以上的代码就可以改写成这样:

//export.js
export default  await new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('3秒后输出的数据');
    },3000)
})

//html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>top level await</title>
</head>
<body>

</body>
<script type="module">
    import data from './export.js';
    //在等待3s后才会执行之后的代码
    console.log(data);//3秒后输出的数据
</script>
</html>

通过上面的代码可以看出,模块在被使用时,导入的方式和普通的模块导入的写法没有区别,模块的使用者不再需要去了解模块内部是如何导出的。
由于import()方法返回的是一个Promise实例,因此顶层await在动态导入时很有用:

//export_test_1.js
let person={
    name:'张三',
    age:18,
};

export {
    person
}

//html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>top level await</title>
</head>
<body>
</body>
<script type="module">
    let {person}=await import('./export_test_1.js');
    console.log(person);//{name: '张三', age: 18}
</script>
</html>

6.5类

6.1公共实例字段

公共类字段允许我们使用赋值运算符 (=) 将实例属性添加到类定义中。

  • 公共实例字段存在于每个创建的类实例上
  • 未初始化的字段会自动设置为 undefined
  • 可以进行字段的计算
//公共实例字段
const attributeName = 'isMale';

class Male {
    gender = '男';
    name;
    age;
    [attributeName] = true;

    getInfo() {
        return "姓名:" + this.name + "," + "年龄:" + this.age + "," + "性别:" + this.gender +
            "可变的属性名:" + attributeName + "," + "值:" + this[attributeName];
    }
}

let male = new Male();
console.log(male.getInfo());//姓名:undefined,年龄:undefined,性别:男可变的属性名:isMale,值:true
male.name = '张三';
male.age = 18;
console.log(male.getInfo());//姓名:张三,年龄:18,性别:男可变的属性名:isMale,值:true

6.2私有实例字段、方法和访问器

默认情况下,ES6 中所有属性都是公共的,可以在类外检查或修改。而私有类字段将使用哈希#前缀定义,私有类字段无法在类的外部进行访问。

class Counter{
    #count=0;
    constructor(count) {
        this.#setCount=count;
    }

    getCountValue(){
        return this.#getCount;
    }

    set #setCount(currentCount){
        this.#count=currentCount;
    }
    get #getCount(){
        return this.#count;
    }
}
let counter=new Counter(10);
console.log(counter.getCountValue());//10
//counter.#count=10;//Private field '#count' must be declared in an enclosing class
//counter.#setCount=10;//Private field '#setCount' must be declared in an enclosing class
//console.log(counter.#getCount);//Private field '#getCount' must be declared in an enclosing class

6.3静态公共字段

在ES6中,不能在类的每个实例中访问静态字段或方法,只能在原型中访问。ES 2022 提供了一种在 JavaScript 中使用 static 关键字声明静态类字段的方法。

class Shape {
    static color='blue';

    static getColor(){
        return this.color;
    }

    getInfo(){
        return 'color:'+Shape.color;
    }

}

可以从类本身访问静态字段和方法:

console.log(Shape.color); // blue
console.log(Shape.getColor()); // blue
console.log('color' in Shape); // true
console.log('getColor' in Shape); // true
console.log('getInfo' in Shape); // false

静态字段只能通过静态方法访问:

console.log(Shape.getInfo()); //TypeError: Shape.getMessage is not a function

实例不能访问静态字段和方法:

const shapeInstance = new Shape();
console.log(shapeInstance.color); // undefined
console.log(shapeInstance.getColor); // undefined
console.log(shapeInstance.getInfo());// color:undefined

静态字段和方法是从父类继承的:

class Rectangle extends Shape { }

console.log(Rectangle.color); // blue
console.log(Rectangle.getColor()); // blue
console.log('color' in Rectangle); // true
console.log('getColor' in Rectangle); // true
console.log('getInfo' in Rectangle); // false

6.4静态私有字段和方法

与私有实例字段和方法一样,静态私有字段和方法也使用哈希 (#) 前缀来定义。私有静态字段有一个限制:只有定义私有静态字段的类才能访问该字段。

class Shape {
    static #color='blue';

    static getColor(){
        return this.#color;//将这里的this改成Shape就不会报错了
    }

    getInfo(){
        return 'color:'+Shape.#color;
    }

}
class Rectangle extends Shape {}
let rect=new Rectangle();
console.log(rect.getInfo());// color:blue
console.log(Rectangle.getColor()); // TypeError: Cannot read private member #color from an object whose class did not declare it

6.5类静态初始化块

静态私有和公共字段只能让我们在类定义期间执行静态成员的每个字段初始化。如果我们需要在初始化期间像 try…catch 一样进行异常处理,就不得不在类之外编写此逻辑。该规范就提供了一种在类声明/定义期间评估静态初始化代码块的优雅方法,可以访问类的私有字段。

class Person {
    static GENDER = "Male"
    static TOTAL_EMPLOYED;
    static TOTAL_UNEMPLOYED;
    console.log('定义类时调用');
}

上面的代码就会引发错误,可以使用类静态块来重构它。此外,类静态块提供对词法范围的私有字段和方法的特权访问,比如下面的例子中,静态块中的实例就能访问类私有字段和方法:

let getData;

class Person {
    #x

    constructor(data) {
        this.#x = { data: data };
    }

    #getInfo(){
        return this.#x;
    }

    static {
        getData = function (obj) {
            //这里的obj就是一个Person的实例,在静态块内的实例可以访问类私有的字段和方法
            console.log(obj.#getInfo());//{ data: '共享的数据' }
            return obj.#x;
        }
    }

}

function readPrivateData(obj) {
    return getData(obj).data;
}

const john = new Person('共享的数据');

let data=readPrivateData(john);
console.log(data);// 共享的数据

参考文献:

https://mp.weixin.qq.com/s?__biz=MzAxODE4MTEzMA==&mid=2650101326&idx=1&sn=4d40e56ef9ee74223173307b382d6435&chksm=83dbcb2bb4ac423df31a7fa2f0807d0a7bd1b4e3ea2d546ba047e98f2650678d233f695c1b56&scene=27
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/at
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/at
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
https://juejin.cn/post/6878441223951122446

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容