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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容