ES2022一些新特性

2022 年 6 月 22 日,第 123 届 ECMA 大会批准了 ECMAScript 2022 语言规范,这意味着它现在正式成为标准。

1、Object.hasOwn

在ES2022之前,可以使用 Object.prototype.hasOwnProperty() 来检查一个属性是否属于对象。
Object.hasOwn 特性是一种更简洁、更可靠的检查属性是否直接设置在对象上的方法:

const example = {
  property: 'string'
};

console.log(Object.prototype.hasOwnProperty.call(example, 'property'))
console.log(Object.hasOwn(example, 'property'))

2、at()

用于通过给定索引来获取数组元素。当给定索引为正时,这种新方法与使用括号表示法访问具有相同的行为。当给出负整数索引时,就会从数组的最后一项开始检索:

const array = [0,1,2,3,4,5]

console.log(array[array.length-1])  // 5
console.log(array.at(1)) // 1

console.log(array[array.lenght-2])  // 4
console.log(array.at(0)) // 0

除了数组,字符串也可以使用at()方法进行索引:

const str = "string"

console.log(str[str.length - 1])  // g
console.log(str.at(-1))  // g

3、error.cause

在 ECMAScript 2022 规范中,new Error() 中可以指定导致它的原因:

function readFiles(filePaths) {
  return filePaths.map(
    (filePath) => {
      try {
        // ···
      } catch (error) {
        throw new Error(
          `While processing ${filePath}`,
          {cause: error}
        )
      }
    })
}

4、正则表达式匹配索引

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

const matchObj = /(a+)(b+)/d.exec('aaaabb');

console.log(matchObj[1]) // 'aaaa'
console.log(matchObj[2]) // 'bb'

由于 /d 标识的存在,matchObj还有一个属性.indices,它用来记录捕获的每个编号组:

console.log(matchObj.indices[1])  // [0, 4]
console.log(matchObj.indices[2])  // [4, 6]

还可以使用命名组:

const matchObj = /(?<as>a+)(?<bs>b+)/d.exec('aaaabb');

console.log(matchObj.groups.as);  // 'aaaa'
console.log(matchObj.groups.bs);  // 'bb'

这里给两个字符匹配分别命名为as和bs,然后就可以通过groups来获取到这两个命名分别匹配到的字符串。

console.log(matchObj.indices.groups.as);  // [0, 4]
console.log(matchObj.indices.groups.bs);  // [4, 6]

匹配索引的一个重要用途就是指向语法错误所在位置的解析器。下面的代码解决了一个相关问题:它指向引用内容的开始和结束位置。

const reQuoted = /“([^”]+)”/dgu;
function pointToQuotedText(str) {
  const startIndices = new Set();
  const endIndices = new Set();
  for (const match of str.matchAll(reQuoted)) {
    const [start, end] = match.indices[1];
    startIndices.add(start);
    endIndices.add(end);
  }
  let result = '';
  for (let index=0; index < str.length; index++) {
    if (startIndices.has(index)) {
      result += '[';
    } else if (endIndices.has(index+1)) {
      result += ']';
    } else {
      result += ' ';
    }
  }
  return result;
}

console.log(pointToQuotedText('They said “hello” and “goodbye”.'));
// '           [   ]       [     ]  '

5、类

(1)公共实例字段
公共类字段允许我们使用赋值运算符 (=) 将实例属性添加到类定义中。下面是一个计数器的例子:

import React, { Component } from "react";

export class Incrementor extends Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.increment}>Increment: {this.state.count}</button>
    );
  }
}

在这个例子中,在构造函数中定义了实例字段和绑定方法,通过新的类语法,可以使代码更加直观。新的公共类字段语法允许我们直接将实例属性作为属性添加到类上,而无需使用构造函数方法。这样就简化了类的定义,使代码更加简洁、可读:

import React from "react";

export class Incrementor extends React.Component {
  state = { count: 0 };

  increment = () => this.setState({ count: this.state.count + 1 });

  render = () => (
    <button onClick={this.increment}>Increment: {this.state.count}</button>
  );
}

有些小伙伴可能就疑问了,这个功能很早就可以使用了呀。但是它现在还不是标准的 ECMAScript,默认是不开启的,如果使用 create-react-app 创建 React 项目,那么它默认是启用的,否则我们必须使用正确的babel插件才能正常使用(@babel/preset-env)。
(2)私有实例字段、方法和访问器
默认情况下,ES6 中所有属性都是公共的,可以在类外检查或修改。下面来看一个例子:

class TimeTracker {
  name = 'zhangsan';
  project = 'blog';
  hours = 0;

  set addHours(hour) {
    this.hours += hour;
  }

  get timeSheet() {
    return `${this.name} works ${this.hours || 'nothing'} hours on ${this.project}`;
  }
}

let person = new TimeTracker();
person.addHours = 2; // 标准 setter
person.hours = 4;    // 绕过 setter 进行设置
person.timeSheet;

可以看到,在类中没有任何措施可以防止在不调用 setter 的情况下更改属性。

而私有类字段将使用哈希#前缀定义,从上面的示例中,可以修改它以包含私有类字段,以防止在类方法之外更改属性:

class TimeTracker {
  name = 'zhangsan';
  project = 'blog';
  #hours = 0;  // 私有类字段

  set addHours(hour) {
    this.#hours += hour;
  }

  get timeSheet() {
    return `${this.name} works ${this.#hours || 'nothing'} hours on ${this.project}`;
  }
}

let person = new TimeTracker();
person.addHours = 4; // 标准 setter
person.timeSheet     // zhangsan works 4 hours on blog

当尝试在 setter 方法之外修改私有类字段时,就会报错:

person.hours = 4 // Error Private field '#hours' must be declared in an enclosing class

还可以将方法或 getter/setter 设为私有,只需要给这些方法名称前面加#即可:

class TimeTracker {
  name = 'zhangsan';
  project = 'blog';
  #hours = 0;   // 私有类字段

  set #addHours(hour) {
    this.#hours += hour;
  }

  get #timeSheet() {
    return `${this.name} works ${this.#hours || 'nothing'} hours on ${this.project}`;
  }

  constructor(hours) {
    this.#addHours = hours;
    console.log(this.#timeSheet);
  }
}

let person = new TimeTracker(4); // zhangsan works 4 hours on blog

由于尝试访问对象上不存在的私有字段会发生异常,因此需要能够检查对象是否具有给定的私有字段。可以使用 in 运算符来检查对象上是否有私有字段:

class Example {
  #field

  static isExampleInstance(object) {
    return #field in object;
  }
}

(3)静态公共字段
在ES6中,不能在类的每个实例中访问静态字段或方法,只能在原型中访问。ES 2022 提供了一种在 JavaScript 中使用 static 关键字声明静态类字段的方法。下面来看一个例子:

class Shape {
  static color = 'blue';

  static getColor() {
    return this.color;
  }

  getMessage() {
    return `color:${this.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('getMessage' in Shape); // false

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

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

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

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

这里的 Shape.getMessage() 就报错了,因为 getMessage 不是一个静态函数,所以它不能通过类名 Shape 访问。可以通过以下方式来解决这个问题:

getMessage() {
  return `color:${Shape.color}` ;
}

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

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('getMessage' in Rectangle); // false

(4)静态私有字段和方法
与私有实例字段和方法一样,静态私有字段和方法也使用哈希 (#) 前缀来定义:

class Shape {
  static #color = 'blue';

  static #getColor() {
    return this.#color;
  }

  getMessage() {
    return `color:${Shape.#getColor()}` ;
  }
}
const shapeInstance = new Shape();
shapeInstance.getMessage(); // color:blue

私有静态字段有一个限制:只有定义私有静态字段的类才能访问该字段。这可能在使用 this 时导致出乎意料的情况:

class Shape {
  static #color = 'blue';
static #getColor() {
  return this.#color;
}
static getMessage() {
  return `color:${this.#color}` ;
}
getMessageNonStatic() {
  return `color:${this.#getColor()}` ;
}
}

class Rectangle extends Shape {}

console.log(Rectangle.getMessage()); // Uncaught TypeError: Cannot read private member #color from an object whose class did not declare it
const rectangle = new Rectangle();
console.log(rectangle.getMessageNonStatic()); // TypeError: Cannot read private member #getColor from an object whose class did not declare it

在这个例子中,this 指向的是 Rectangle 类,它无权访问私有字段 #color。当我们尝试调用 Rectangle.getMessage() 时,它无法读取 #color 并抛出了 TypeError。可以这样来进行修改:

class Shape {
  static #color = 'blue';
  static #getColor() {
    return this.#color;
  }
  static getMessage() {
    return `${Shape.#color}`;
  }
  getMessageNonStatic() {
    return `color:${Shape.#getColor()} color`;
  }
}

class Rectangle extends Shape {}
console.log(Rectangle.getMessage()); // color:blue
const rectangle = new Rectangle();
console.log(rectangle.getMessageNonStatic()); // color:blue

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

class Person {
    static GENDER = "Male"
    static TOTAL_EMPLOYED;
    static TOTAL_UNEMPLOYED;

    try {
        // ...
    } catch {
        // ...
    }
}

上面的代码就会引发错误,可以使用类静态块来重构它,只需将try…catch包裹在 static 中即可:

class Person {
    static GENDER = "Male"
    static TOTAL_EMPLOYED;
    static TOTAL_UNEMPLOYED;
    
  static {
   try {
        // ...
    } catch {
        // ...
    }
  }
}

此外,类静态块提供对词法范围的私有字段和方法的特权访问。这里需要在具有实例私有字段的类和同一范围内的函数之间共享信息的情况下很有用。

let getData;

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

  static {
    getData = (obj) => obj.#x;
  }
}

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

const john = new Person([2,4,6,8]);

readPrivateData(john); // [2,4,6,8]

这里,Person 类与 readPrivateData 函数共享了私有实例属性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值