围观!我国第一个推进到浏览器标准的 ECMaScript 提案!

以下文章来源于前端很美 ,作者ginkgo6


在2022年6月22日,第123届Ecma大会批准了ECMAScript 2022语言规范。一起来看看ES13给我们带来了哪些新特性吧。其中最后一个特性,是我国第一个推进到浏览器标准的EcmaScript提案。

1.私有属性和私有方法
之前js中class的一个痛点就是没有私有属性,毕竟封装是面向对象的三大特性,没有私有属性如何实现封装。尽管我们可以用很多方法去曲线实现私有属性比如闭包proxy、weakmap、symbol等等,但是他们各自有各自的缺陷,下面的代码演示其中以_作为命名约定的方式实现私有属性的缺点。

class User {
  constructor() {
    // public 属性
    this.name = "Tom";
    // private 属性
    this._lastName = "Brown";
  }

  getFullName(){
    return `${this.name} ${this._lastName}`
  }
}

const user = new User();
user.name
// "Tom"

user._lastName
// "Brown"
// no error thrown, we can access it from outside the class

在构造函数中,我们定义了两个字段,其中一个字段是以_开头的, 这是一个命名约定用于将字段声明为私有字段,但是当我们在类外面访问该字段时,并没有引起报错,原因是因为它只是一个命名规范而已。
现在在ES13中,我们可以用更简单的方法声明公共和私有字段,第一,我们不必在构造函数中定义它们了,其次,我们可以通过在字段前面添加#号来定义一个真正的私有字段。

class User {
  name = "Tom";
  #lastName = "Brown"

  getFullName(){
    return `${this.name} ${this.#lastName}`
  }
}

const user = new User();
user.name
// "Tom"

user.getFullName();
// "Tom Brown"

user.#lastName
// SyntaxError - cannot be accessed or modified from outside the class
有了这个特性,我们终于可以简单轻松的定义一个真正的私有属性或私有方法了。

2.私有属性检测
当我们尝试访问对象上不存在的私有属性时会报错,所以就需要一种方法来检测对象是否具有某私有属性,此时,我们可以在class中使用in关键字来完成此工作。

class Person {
  #name;
  constructor(name) {
    this.#name = name;
  }
  static check(obj) {
    return #name in obj;
  }
}

Person.check(new Person()), // true
当然,你可能会发问对于具有相同名称的私有属性的类,in 关键字是否能正常工作,别担心,请参考以下例子,User类和Person类具有相同的私有属性name,in关键字可以正确的区分。

class User {
  #name;
  constructor(name) {
    this.#name = name;
  }
  static check(obj) {
    return #name in obj;
  }
}

class Person {
  #name;
  constructor(name) {
    this.#name = name;
  }
  static check(obj) {
    return #name in obj;
  }
}

User.check(new User()),     // true
User.check(new Person()),   // false
Person.check(new Person()), // true
Person.check(new User()),   // false
3. 类静态初始化块(Class Static Block)
类在初始化的时候会执行静态初始化块(Class Static Block),一个类可以拥有任意个静态初始化块,它们将会按照声明的顺序执行。静态初始块内还可以访问父类的静态属性。

class Dictionary {
 static words = [ "yes", "no", "maybe" ];
}


class Words extends Dictionary {
  static englishWords = [];
  static #localWord = 'ok';
  // 第一个静态初始化块
  static {
   
    const words = super.words;
    this.englishWords.push(...words);
  }
  // 第二个静态初始化块
  static {
      this.englishWords.push(this.#localWord);
  }
}

console.log(Words.englishWords)
//输出 -> ["yes", "no", "maybe", "ok"]
下面这个例子,我们还可以通过静态初始化块将私有属性暴露出去。

let getClassPrivateField;

class Person {
  #privateField;
  constructor(value) {
    this.#privateField = value;
  }
  static {
    getClassPrivateField = (obj) => obj.#privateField;
  }
}

getClassPrivateField(new Person('private value'));
4.Regexp Match Indices
之前 ECMAScript 中的 「RegExp.prototype.exec」 方法的返回值已经提供了对于匹配的捕获组 文本与对应的捕获组在正则表达式中的索引。尽管它已经包含输入字符串,模式匹配的索引以及包含任何已命名捕获组的子字符串的group对象,但是对于复杂情况,此信息可能不足。






const fruits = 'Fruits: apple, banana, orange'
const regex = /(banana)/g;

const matchObj = regex.exec(fruits);

console.log(matchObj);
// [
//   'banana',
//   'banana',
//   index: 15,
//   input: 'Fruits: apple, banana, orange',
//   groups: undefined
// ]
所以ES13通过新增/d修饰符,向匹配结果添加一个属性 .indices,此属性是一个索引数组,其中包含每个捕获的子字符串的一对开始索引和结束索引。

const fruits = 'Fruits: apple, banana, orange'
const regex = /(banana)/gd;

const matchObj = regex.exec(fruits);

console.log(matchObj);
// [
//   'banana',
//   'banana',
//   index: 15,
//   indices:[
//      [15, 21],
//      [15, 21]
//   ]
//   input: 'Fruits: apple, banana, orange',
//   groups: undefined
// ]
5.Await operator at the top-level
之前await关键词只能在aysnc function里进行使用,想要在顶层使用await就必须要加个aysnc自执行函数,这样十分的不方便, 所以ES13中,引入了可以直接在顶层使用Await关键字的特性。
动态加载模块:

const strings = await import(`./example.mjs`); |
依赖回退:

let jQuery;
try {
  jQuery = await import('https://cdn-a.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.com/jQuery');
}
加载最快的资源

const resource = await Promise.any([
  fetch('http://example1.com'),
  fetch('http://example2.com'),
]);
6 .at()方法
之前,我们如果想要获取一个数组的元素,如果是正序获取一个元素,我们可以采取arr[0]这样的方式, 但是如果我们要倒序访问一个数组的元素,就需要采取arr[arr.length - 2]这样的方式,数组的名称arr我们写了两次,而且还要写个length,这很不优雅,复杂的情况下就更是难受。

const arr = [100,200,300,400]
arr[0]              // 100
arr[arr.length - 2] // 300
arr.slice(-2)[0]    // 300
为了解决这个问题, ES13引入了at()方法,无论正序还是倒序获取元素都非常的优雅,有了这个方法,我们可以在数组、字符串、TypedArray上通过索引值方便的获取元素。

const arr = [100,200,300,400]
arr.at(0)     // 100
arr.at(-2)    // 300

const str = "ABCD"
str.at(-1)    // 'D'
str.at(0)     // 'A'
7.Object.hasOwn(obj, propKey)
之前我们如果想要判断某对象是否具有某属性,我们会通过in和obj.hasOwnProperty,但是如果指定的属性位于原型链中,“in”运算符也会返回true。所以之前要想判断对象自身是否具有某属性我们一般都通过obj.hasOwnProperty来判断。
但是obj.hasOwnProperty也有自身的缺点:因为hasOwnProperty是不受保护的属性,所以,人们可能会在对象上定义个自己的hasOwnProperty,如下所示,自定义的hasOwnProperty永远返回false,这是一个坑。

const person = {
    name: "Roman",
    hasOwnProperty:()=> {
        return false
    }
}

person.hasOwnProperty('name');  // false
另一个问题是使用Object.create(null) 创建一个不继承自 Object.prototype 的对象,使 hasOwnProperty 方法时会报错。

Object.create(null).hasOwnProperty("name")
// Uncaught TypeError: Object.create(...).hasOwnProperty is not a function
所以,为了解决上面的问题,ES13引入了**Object.hasOwn(obj, propKey)**。用法如下所示:

const object = { name: "Mark" };
Object.hasOwn(object, "name");  // true

const object2 = Object.create({ name: "Roman" });
Object.hasOwn(object2, "name"); // false
Object.hasOwn(object2.__proto__, "name"); // true

const object3 = Object.create(null);
Object.hasOwn(object3, "name"); // false
8.Error Cause
Error Cause提案由阿里巴巴的「昭朗」同学负责,是我国第一个成为浏览器标准的EcmaScript提案。
以前因为 Error Cause 没有标准化的参数定义及官方实现,所以容易丢失 error 的属性或需要写比较多的代码自定义等,并且开发者工具也难以依赖于非语言特性的自定义方案。

  try {
    apiCallThatCanThrow();
  } catch (err) {
    throw new Error('New error message', { cause: err });
  }
有了这个新特性,借助cause属性,我们可以记下导致这个Error的前一个Error对象,错误对象就可以以一种简单优雅的方式链接起来。

9.总结
以上就是ES13的新特性,新特性会给js生态带来更多好的东西,提高开发者的效率和体验。




作者:ginkgo6

欢迎关注微信公众号 :前端印象