ECMAScript 2022(ES13)初体验

2022-10-31 15:28:59 浏览数 (1)

ECMAScript 2022(ES13)初体验

2022 年 6 月 22 日,第 123 届 ECMA 大会批准了 ECMAScript 2022 语言规范,这意味着它现在正式成为标准。下面就来看看 ECMAScript 2022 有哪些新特性!

新特性总览

  • Top-level Await
  • Object.hasOwn()
  • at()
  • error.cause
  • 正则表达式匹配索引
  • ES14: Array.prototype.findLast 和 Array.prototype.findLastIndex 的提案。

Top-level Await(顶级 await)

async 和 await 在 ES2017(ES8)中引入用来简化 Promise 操作,但是却有一个问题,就是 await 只能在 async 内部使用, 当我们直接在最外层使用 await 的时候就会报错: Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules

没有顶级 await 之前,当我们导入一个外部promise.js文件的时候,因为需要等待这个外部 js 执行完成再执行别的操作

代码语言:javascript复制
// promise.js
let res = { name: "" },
  num;
const np = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(456);
    }, 100);
  });
};
const p = async () => {
  const res1 = await np();
  res.name = res1;
  num = res1;
};
p();
export default res;


//validate.js
import res from "./p.js";
console.log("res", res, num);
// 这时 res 和 num 都是 undefined

因为 res 和 num 需要在异步执行完成之后才能访问它,所以我们可以加个定时器来解决

代码语言:javascript复制
setTimeout(() => {
 console.log("res3000", res, num);
}, 1000);
// res 可以正确输出 {name: 456}
// num 还是 undefined

为什么 res 可以正常输出,而 num 不行?

这是因为 res 时对象,是一个引用类型,当过了 100 毫秒后,异步操作以及执行完成并且赋值了,而导出的res 和 p.js 里面的res指向同一个地址,所以能监听到改变,但是 num 是基本数据类型,导出的和p.js里面的不是同一个,所以无法监听到,故而一直是 undefined,而且在实际项目中,异步时间是不确定,所以这种方法存在一定缺陷,这时就可以使用 顶级 await 来实现

代码语言:javascript复制
// p.js
let res = { name: "" },
  num;
const np = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(456);
    }, 100);
  });
};
// 这里进行改造
const res1 = await np();
res.name = res1;
num = res1;
export { res, num };


//validate.js
import { res, num } from "./p.js";
console.log("res adn num", res, num);

// 全部正常输出

代码自上而下执行,遇到 await 进入等待,np 函数执行完成之后进行赋值,赋值完成后导出。

顶级 await 使用部分场景

  • 资源初始化:例如,等待某个文件(图片、js(初始化变量的js)等)加载完成之后再渲染
  • 依赖回退:
代码语言:javascript复制
let depVersion;
try {
  depVersion = await import(xxx/depVersion-2.0.0.min.js)
}catch {
  depVersion = await import(xxx/depVersion-1.5.0.min.js)
}
  • 模块动态加载:
代码语言:javascript复制
let myModule = 'await-module'
const module = await import(`./${myModule}`)

兼容性

Object.hasOwn()

ES5:当我们检查一个属性时候属于对象的时候可以使用

常用例子:

代码语言:javascript复制
object = {firstName: '四', lastName: '李'}
for (const key in object) {
  if (Object.hasOwnProperty.call(object, key)) {
    const element = object[key];
    console.log(element)
  }
}

ES6:Object.hasOwn 特性是一种更简洁、更可靠的检查属性是否直接设置在对象上的方法

常用例子:

代码语言:javascript复制
object = {firstName: '四', lastName: '李'}
for (const key in object) {
  if (Object.hasOwn(object, key)) {
    const element = object[key];
    console.log(element)
  }
}

at()

一个 TC39 提案,向所有基本可索引类(Array、String、TypedArray)添加 .at() 方法

ES13 之前,要从可索引对象的末尾访问值,通常的做法是写入 arr[arr.length - N] 或者使用 arr.slice(-N)[0]

ES13:可以使用 at() 方法

代码语言:javascript复制
// 数组
const array = [0,1,2,3,4,5];
array.at(-1) // 5
array.at(-2) // 4

// 字符串
string= 'abcdefg'
string.at(-2) // f

兼容性

Error Cause

有时,对于代码块的错误需要根据其原因进行不同的处理,但错误的原因又较为相似(例如:错误的类型和消息均相同)。

代码语言:javascript复制
// ES13 之前通常用以下几种方式处理错误
async function errFunc() {
  const rawResource = await axios('/testError')
    .catch(err => {
      // 第一种
      throw new Error('我的错误信息:', err.message);
      // 第二种,需要连接错误信息
      const wrapErr = new Error('Download raw resource failed');
      // 自己创建 一个 cause 属性来接收错误上下文
      wrapErr.cause = '错误原因:'   err;
      throw wrapErr;
      // 第三种,需要连接错误信息
      class CustomError extends Error {
         constructor(msg, cause) {
           super(msg);
            // 自己创建 一个 cause 属性来接收错误上下文
           this.cause = cause;
         }
       }
       throw new CustomError('Download raw resource failed', err);
    });
}
try {
    const res = await errFunc()
}catch (err) {
    console.log(err)
    console.log(err.cause)
}
// 第一种输出:Uncaught Error: 我的错误信息:Failed to fetch
// 第一种输出:undefined

// 第二种输出:Uncaught Error: 我的错误信息
 // 第二种输出:错误原因: err 

// 第三种:Uncaught Error: 我的错误信息
// 第三种输出:错误原因: err

正则表达式匹配索引

给正则表达式添加修饰符 d,会生成匹配对象,记录每个组捕获的开始和结束索引,由于 /d 标识的存在,m1 还有一个属性 .indices,它用来记录捕获的每个编号组

代码语言:javascript复制
// ?<m>n:命名分组,m 为组名称,n 为正则表达式
const re1 = /a (?<Z>z)?/d;

// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
console.log(m1.indices)

类 class

公共实例字段

在 ES13 之前,在定义类的属性时,需要在构造函数中定义了实例字段和绑定方法

代码语言:javascript复制
class myClass {
  constructor() {
    this.count = 1
    this.increment = this.increment.bind(this);
  }
  increment() {
    this.count  = 1
  }
}

ES 13 可以使用公共实例字段,这样就简化了类的定义,使代码更加简洁、可读

代码语言:javascript复制
class myClass {
  count = 1
  increment = () => {
    this.count  = 1
  }
}

私有实例字段

默认情况下,class 中所有属性都是公共的,可以在 class 之外进行修改,例如

代码语言:javascript复制
class myClass {
  count = 1
  setCount = () => {
    this.count  = 1
  }
}

const es13 = new myClass()
es13.count = 5
// myClass {count: 5, setCount: ƒ}count: 5setCount: () => {     this.count  = 1   }[[Prototype]]: Object

通过上面的例子可以看到,当我们直接设置 count 属性的时候,是直接跳过 setCount 进行设置的,有时候我们并不想这样,所以可以使用私有实例字段,用法很简单,只需要在私有字段添加 # 就可以实现,当然了,在调用的时候我们也应该加上 # 进行调用,如下:

代码语言:javascript复制
class myClass {
  #count = 1
  setCount = () => {
    this.#count  = 1
  }
}

const es13 = new myClass()
es13.setCount() // 正常修改,每执行执行一次 setCount 方法后 #count的值每一次都加1

// 直接修改私有属性
es13.#count = 5
// 报错:Uncaught SyntaxError: Private field '#count' must be declared in an enclosing class

可以看到,当我们直接修改私有属性之后,浏览器直接抛出错误:Uncaught SyntaxError: Private field '#count' must be declared in an enclosing class

私有方法

都有私有属性了,怎么能少了私有方法呢,方法和属性一下只有加上 # 即可:

代码语言:javascript复制
class myClass {
  #count = 1
  #setCount = () => {
    this.#count  = 1
  }
    newSetCount = () => {
    this.#setCount()
  }
}
const es13 = new myClass()
es13.#setCount() 
// 直接调用私有方法报错:Uncaught SyntaxError: Private field '#setCount' must be declared in an enclosing class

//通过公共方法 newSetCount 调用
es13.newSetCount() 
//成功,#count   1

静态公共字段、静态私有字段、静态私有方法

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

代码语言:javascript复制
class myClass {
 //静态公共字段
 static color = 'blue'
 // 静态私有字段
 static #count = 1
 // 静态私有方法
 static #setCount = () => {
    this.#count  = 1
  }
  newSetCount = () => {
    this.#setCount()
  }
}
const es13 = new myClass()
实例 es13 上面只有 newSetCount() 方法
es13.newSetCount() 
// 报错:Uncaught SyntaxError: Private field '#setCount' must be declared in an enclosing class

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

代码语言:javascript复制
class myClass {
// 静态私有字段
 static #count = 1
 // 静态私有方法
 static #setCount = () => {
   // 实例化之后,this 不再指向 myClass,所有需要改成 myClass 类调用
    myClass.#count  = 1
  }
  newSetCount = () => {
    // 实例化之后,this 不再指向 myClass,所有需要改成 myClass 类调用
    myClass.#setCount()
  }
}
const es13 = new myClass()
es13.newSetCount() 
// 成功

类静态块

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

ES13之前

代码语言:javascript复制
class Person {
    static EEEOR = "error"
    static SUCCESS_TYPE = "success_type";
    constructor() {
        // ...
    }
    try {
        // ...
    } catch {
        // ...
    }
}

上面代码直接报错:Uncaught SyntaxError: Unexpected token '{'

ES13 : 直接将 try...cathc 使用 static 包裹起来即可

代码语言:javascript复制
class Person {
    static EEEOR = "error"
    static SUCCESS_TYPE = "success_type";
    constructor() {
        // ...
    }
    static {
      try {
        // ...
      } catch {
        // ...
      }
    }
}

ES14 新提案

  • • Array.prototype.findLast
  • • Array.prototype.findLastIndex

Tips:

  • Array.prototype.findLastArray.prototype.find 的行为相同,但会从最后一个迭代到第一个。
  • Array.prototype.findLastIndexArray.prototype.findIndex的行为相同,但会从最后一个迭代到第一个。

Array.prototype.findLast 和 Array.prototype.findLastIndex 的提案

与Array.prototype.find和Array.prototype.findIndex的行为相同,但会从最后一个迭代到第一个。

代码语言:javascript复制
const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];

array.find(n => n.value % 2 === 1); // { value: 1 }
array.findIndex(n => n.value % 2 === 1); // 0

// ======== Before the proposal =========== 

// find
[...array].reverse().find(n => n.value % 2 === 1); // { value: 3 }

// findIndex
array.length - 1 - [...array].reverse().findIndex(n => n.value % 2 === 1); // 2
array.length - 1 - [...array].reverse().findIndex(n => n.value === 42); // should be -1, but 4

// ======== In the proposal =========== 
// find
array.findLast(n => n.value % 2 === 1); // { value: 3 }

// findIndex
array.findLastIndex(n => n.value % 2 === 1); // 2
array.findLastIndex(n => n.value === 42);

好了,以上就是本期内容了~

0 人点赞