ES2022精讲

ECMAScript的渊源

ECMAScript是一种语言规范:ECMAScript定义了一种脚本语言的标准,规定了语法、类型、语句、关键字等等。它由ECMA国际组织制定,旨在为各种脚本语言提供一个通用的标准。平时常说的ES6,全称就是ECMAScript2015,2015年发布的第六个版本规范。

JavaScript是一种编程语言:JavaScript是一种基于ECMAScript规范的编程语言,它实现了ECMAScript规范并添加了一些额外的功能,例如DOM操作、事件处理等。

TC39(Technical Committee 39)是负责制定ECMAScript规范的技术委员会。

https://github.com/tc39/ecma262

image-20230901170622433.png

顶层设计 await(Top-level await)

作用:await可以脱离async函数,单独使用

1
2
const message = await 'hello world';
console.log(message); // hello world

注意

谨慎使用,当心阻塞后续操作

.at()

作用:可以获取索引对应的值,可支持 负数 。如下

1
2
3
const arr = [1, 2, 3];
console.log(arr.at(0)); // 1
console.log(arr.at(-1)); // 3

支持的类型

  • Array 数组
  • String 字符串
  • TypedArray 类型化数组,如: Uint8Array 等等

为什么 在之前的 [] 方法扩展,而是要用 at 方法?

[] 并非只有数组和字符串特有的方法。所有对象(引用类型的数据,如 ObjectArrayFunction 等等 )都有。而且 [] 是本来就可以用 负数的,只不过是相当于负数是对象的key值,来获取Value值的意思。

1
2
const arr = [1,2,3];
console.log(arr[-1]); // 提问

Object.hasOwn

作用:判断对象是否有指定的属性,用来取代 Object.prototype.hasOwnProperty

1
2
3
let obj = { a: 1 };
console.log(Object.hasOwn(obj, 'a')); // true
console.log(Object.hasOwn(obj, 'b')); // false

**Object.prototype.hasOwnPropertyin 的区别**

in运算符可以检查继承的属性,而hasOwnProperty方法只检查对象自身的属性

1
2
3
let obj ={a:1,b:2}
console.log('toString' in obj) // true
console.log(Object.prototype.hasOwnProperty.call(obj,'toString')) // false

现有的Object.prototype.hasOwnProperty的问题有哪些?

  1. 写法繁琐
1
2
3
4
let obj = { a: 1 };
let hasOwnProperty = Object.prototype.hasOwnProperty
console.log(hasOwnProperty.call(obj, 'a'));
console.log(hasOwnProperty.call(obj, 'b'));
  1. Object.create(null) 使用 hasOwnProperty 会报错

Object.create(null) 创建的对象不会继承 Object.prototype ,将会丢失 hasOwnProperty 方法。用 Object.prototype.hasOwnProperty ****并不会出现这样情况。

1
2
3
4
5
let obj = Object.create(null);
console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // false

console.log(obj.hasOwnProperty('a'));
// Uncaught TypeError: Object.create(...).hasOwnProperty is not a function

为什么上诉例子使用 hasOwnProperty 要从原型上使用(Object.prototype.hasOwnProperty)而不是直接使用 ?

在上诉代码中,hasOwnProperty 可以直接使用,效果是一样的,如下:

1
2
3
let obj = { a: 1 };
console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true
console.log(obj.hasOwnProperty('a')); // true

原因:

为了降低hasOwnProperty 被修改的可能性,防止不必要的bug。

由于 obj.hasOwnPropertyhasOwnProperty 本质上是obj的一个属性,不受到保护,可以被人为修改,可能会出现给对象定义属性名的时候为``hasOwnProperty` 。

1
2
3
let obj = { a: 1, hasOwnProperty: 2 };
console.log(obj.hasOwnProperty('a'));
// Uncaught TypeError: obj.hasOwnProperty is not a function

可以查看MDN详情

为什么不直接在Object.hasOwnProperty(object, property) 中直接修改使用?

  • 答案

    因为Object 本质上是个函数Object() 是空对象。并没有直接挂载 hasOwnProperty 属性 ,是通过原型链向上找到的。所以Object.hasOwnProperty 等同于Object.prototype.hasOwnProperty

    1
    2
    3
    Object.prototype.hasOwnProperty = 123;
    console.log(Object.prototype.hasOwnProperty); // 123
    console.log(Object.hasOwnProperty); // 123

为什么不用 Map替换 Object

  1. Object 操作上相对于 Map 方便一些
  2. 请求发送数据需要 序列化数据 JSON 格式,Map的话需要转成Object才行,而且有些数据 (key值为引用类型)会丢失。
    1
    2
    3
    4
    const map = new Map();
    map.set({a:1},1);
    map.set('b',2)
    console.log(Object.fromEntries(map)) // {[object Object]: 1, b: 2}
    key值 {a:1} 变成了 [object Object] ,进行了隐式类型转换( toString ),因为 Objectkey值只能是 string类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    console.log(({a:1}).toString()); // '[object Object]'

    let obj ={a:1,b:2}
    obj[{c:3}] = 3
    obj[3] = 3333
    for (let key in obj){
    console.log(key,typeof key)
    }
    // 3 string
    // a string
    // b string
    // [object Object] string

Error Cause

作用:Error 类中添加了 cause 属性,用于传递错误信息。方便与错误的处理,对深层次的传递错误提供了良好的便利。

在以往的错误传递常用的有三种方式:

  1. Error 字符串拼接错误信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    async function queryHandlers() {
    try {
    JSON.parse('<')
    } catch (err) {
    throw new Error(`parse fail: ${err}`)
    }
    }

    try {
    await queryHandlers();
    } catch (e) {
    console.log(e);
    // Error: parse fail: SyntaxError: Unexpected token '<', "<" is not valid JSON
    }
  2. 手动为 Error 实例添加 cause 属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    async function queryHandlers() {
    try {
    JSON.parse('<')
    } catch (err) {
    const customError = new Error(`parse fail:`)
    customError.cause = err;
    throw customError
    }
    }

    try {
    await queryHandlers();
    } catch (e) {
    console.log(e);
    // Error: parse fail:
    console.log(e.cause);
    // SyntaxError: Unexpected token '<', "<" is not valid JSON
    }
  3. 通过继承 Error 类来实现自定义的 error 类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class CustomError extends Error {
    constructor(msg, cause) {
    super(msg);
    this.cause = cause;
    }
    }

    async function queryHandlers() {
    try {
    JSON.parse('<')
    } catch (err) {
    throw new CustomError('parse fail:', err);
    }
    }

    try {
    await queryHandlers();
    } catch (e) {
    console.log(e);
    // Error: parse fail:
    console.log(e.cause);
    // SyntaxError: Unexpected token '<', "<" is not valid JSON
    }

现在的 Error 中实例化的时候可以传入第二个参数,类型是对象,里面为 cause 属性赋值进行传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function queryHandlers() {
try {
JSON.parse('<')
} catch (err) {
throw new Error('parse fail:', { cause: err });
}
}

try {
await queryHandlers();
} catch (e) {
console.log(e);
// Error: parse fail:
console.log(e.cause);
// SyntaxError: Unexpected token '<', "<" is not valid JSON
}

Class Public Instance Fields

作用:之前要初始化公共的属性需要在 constructor 中进行初始化,现在可以在 constructor 之外去初始化公共属性。

1
2
3
4
5
6
7
8
class Person {
// 现在的方式
age = 18;
// 之前的方式
constructor() {
this.name = 'wxb';
}
}

Private Instance Fields

作用:私有字段,可以使用 # 声明变量或方法为私有的,实例化之后无法直接访问的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
#age = 100

getAge() {
return this.#age
}

#privateGetAge() {
return this.#age
}
}
console.log(new Person().getAge()); // 100
console.log(new Person().#age);
// Uncaught SyntaxError: Private field '#age1' must be declared in an enclosing class
console.log(new Person().#privateGetAge());
// Uncaught SyntaxError: Private field '#privateGetAge' must be declared in an enclosing class

Class Static Block

作用:提供了在未实例类中 关键字static 拥有计算的能力,不只是仅仅声明和赋值变量的能力。

先介绍一下Class 的 关键字 static只能在类型静态状态下访问,不可以实例化之后访问。可以定义变量或者方法。

使用场景: 工具函数常量定义 等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 工具函数
class Utils {
static parseData(str) {
return JSON.parse(str)
}
}

console.log(Utils.parseData('{}')); // {}

// 常量定义
class Constants {
static AGE = 18;
}

console.log(Constants.AGE); // 18

之前如果需要对static进行计算需要再外部去修改,如:

1
2
3
4
5
6
7
8
9
class Person {
static height = 180;
static weight = 90;
static bmi = 0;
}

Person.bmi = Person.height / Math.pow((Person.weight) / 100, 2)

console.log(Person.bmi); // 22.5

现在有了类静态代码块的功能,写法如下:

1
2
3
4
5
6
7
8
9
10
class Person {
static height = 200;
static weight = 90;
static bmi = 0;
static {
this.bmi = this.weight / Math.pow((this.height) / 100, 2)
}
}

console.log(Person.bmi); // 22.5

RegExp Match Indices

作用:通过正则匹配字符串,输出匹配到的字符串的 开始索引位置结束索引位置

处于性能的考虑,在已有的匹配模式上新增了 d 模式。之前的匹配模式有以下几种:

  • i 忽略大小写
  • g 全局匹配
  • m 多行匹配

exec 新增 indices 属性,表示匹配到的字符串的 开始索引位置结束索引位置

1
2
3
4
5
const reg = /(\d+)\.\d+/d;  // 匹配数字加小数点加数字
const str = 'a12.3a';
const result = reg.exec(str);
console.log(result);
// ['12.3', '12', index: 1, input: 'a12.3a', groups: undefined, indices: Array(2)]

输出的结果 12.3 代表匹配到的结果, 12 是正则中 (\d+) 捕获组的结果。