本文主要整理了截至到 2021年10月12日 为止的且处于 Stage 3->Stage 4 阶段的ECMA提案。
主要包括:
- Class Fields
- RegExp Match Indices
- Top-Level await
- Ergonomic brand checks for Private Fields
- .at
- Object.hasOwn()
- Class Static Block
Class Fields
class
相关的提案一共有三个,目前都处于 Stage 3
阶段。
Private instance methods and accessors
示例:
/**** es2015 ****/
class Counter extends HTMLElement {
get x() { return this.xValue; }
set x(value) {
this.xValue = value;
window.requestAnimationFrame(this.render.bind(this));
}
clicked() {
this.x++;
}
constructor() {
super();
this.onclick = this.clicked.bind(this);
this.xValue = 0;
}
connectedCallback() { this.render(); }
render() {
this.textContent = this.x.toString();
}
}
window.customElements.define('num-counter', Counter);
/**** now(exNext) ****/
class Counter extends HTMLElement {
xValue = 0; // 声明共有属性
get x() { return this.xValue; }
set x(value) {
this.xValue = value;
window.requestAnimationFrame(this.render.bind(this));
}
clicked() {
this.x++;
window.requestAnimationFrame(this.render.bind(this));
}
constructor() {
super();
this.onclick = this.clicked.bind(this);
}
connectedCallback() { this.render(); }
render() {
this.textContent = this.x.toString();
}
}
window.customElements.define('num-counter', Counter);
Class Public Instance Fields & Private Instance Fields
示例:
// es2015
class Counter extends HTMLElement {
clicked() {
this.x++;
window.requestAnimationFrame(this.render.bind(this));
}
constructor() {
super();
this.onclick = this.clicked.bind(this);
this.x = 0;
}
connectedCallback() { this.render(); }
render() {
this.textContent = this.x.toString();
}
}
window.customElements.define('num-counter', Counter);
// now
class Counter extends HTMLElement {
x = 0;
clicked() {
this.x++;
window.requestAnimationFrame(this.render.bind(this));
}
constructor() {
super();
this.onclick = this.clicked.bind(this);
}
connectedCallback() { this.render(); }
render() {
this.textContent = this.x.toString();
}
}
window.customElements.define('num-counter', Counter);
// 结合私有字段
class Counter extends HTMLElement {
#x = 0;
clicked() {
this.#x++;
window.requestAnimationFrame(this.render.bind(this));
}
constructor() {
super();
this.onclick = this.clicked.bind(this);
}
connectedCallback() { this.render(); }
render() {
this.textContent = this.#x.toString();
}
}
window.customElements.define('num-counter', Counter);
Static class fields and private static methods
class Test {
static instance = new Test(0);// 静态共有变量
static #value=123; // 静态私有变量
// 静态私有方法
static #print(){
return this.#value;
}
}
RegExp Match Indices
正则的匹配模式之前只有
i g m
,现在增加 d
,目前处于 Stage 4
阶段。
示例:
const re1 = /a+(?<Z>z)?/d;
// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";
m1.indices[1][0] === 4;
m1.indices[1][1] === 5;
s1.slice(...m1.indices[1]) === "z";
m1.indices.groups["Z"][0] === 4;
m1.indices.groups["Z"][1] === 5;
s1.slice(...m1.indices.groups["Z"]) === "z";
// capture groups that are not matched return `undefined`:
const m2 = re1.exec("xaaay");
m2.indices[1] === undefined;
m2.indices.groups["Z"] === undefined;
使用 \d
后给 上面代码中的 m1
增加 indices
属性。
Top-Level await
之前只能
async await
, 现在则可以直接使用 await
,目前处于 Stage 4
阶段。
示例:
/*** before ***/
// awaiting.mjs
import { process } from "./some-module.mjs";
export default (async () => {
const dynamic = await import(computedModuleSpecifier);
const data = await fetch(url);
const output = process(dynamic.default, data);
return { output };
})();
// usage.mjs
import promise from "./awaiting.mjs";
export default promise.then(({output}) => {
function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
return { outputPlusValue };
});
/*** now ***/
// awaiting.mjs
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
// usage.mjs
import { output } from "./awaiting.mjs";
export function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
Ergonomic brand checks for Private Fields
这个提案的作用是检测一个对象中是否存在私有变量,目前处于
Stage 4
阶段。
示例:
class C {
#brand;
#method() {}
get #getter() {}
static isC(obj) {
return #brand in obj && #method in obj && #getter in obj;
}
}
.at
在所有基本可索引类中添加
.at()
方法,目前处于 Stage 3
阶段。
很多时候,类似于 Python 中的数组负值索引可以非常实用。比如在 Python 中我们可以通过 arr[-1]
来访问数组中的最后一个元素,而不用通过目前 JavaScript 中的方式来访问 arr[arr.length-1]
。这里的负数是作为从起始元素(即arr[0]
)开始的反向索引。
但是现在 JavaScript 中的问题是,[]
这个语法不仅仅只是在数组中使用(当然在 Python 中也不是),而在数组中也不仅仅只可以作为索引使用。像arr[1]
一样通过索引引用一个值,事实上引用的是这个对象的 "1"
这个属性。所以 arr[-1]
已经完全可以在现在的 JavaScript 引擎中使用,只是它可能不是代表的我们想要表达的意思而已:它引用的是目标对象的 "-1"
这个属性,而不是一个反向索引。
这个提案提供了一个通用的方案,我们可以通过任意可索引的类型(Array,String,和 TypedArray)上的 .at
方法,来访问任意一个反向索引、或者是正向索引的元素。
示例:
const arr = [1,2];
arr.at(-1); // 2
arr.at(-2); // 1
Object.hasOwn()
采用一种使更易于访问的方法替代
Object.prototype.hasOwnProperty()
,目前处于 Stage 4
阶段。
其实现在我们就可以通过 Object.prototype.hasOwnProperty
来使用提案所包含的特性。但是直接通过对象自身的 hasOwnProperty
来使用 obj.hasOwnProperty('foo')
是不安全的,因为这个 obj
可能覆盖了 hasOwnProperty
的定义,MDN 上也对这种使用方式进行了警告。
JavaScript 并没有保护
hasOwnProperty
这个属性名,因此,当某个对象可能自有一个占用该属性名的属性时,就需要使用外部的 hasOwnProperty
获得正确的结果...
示例:
let hasOwnProperty = Object.prototype.hasOwnProperty
if (hasOwnProperty.call(object, "foo")) {
console.log("has property foo")
}
// 此提案将该代码简化为:
if (Object.hasOwn(object, "foo")) {
console.log("has property foo")
}
Class Static Block
类块提供了在类定义评估期间执行额外静态初始化的机制,目前处于
Stage 4
阶段
自从有了 Class Private Fields,对于类的语法是不断地有新的实践与需求。这个提案提议的 Class Static 初始化块会在类被执行、初始化时被执行。
提案中定义的初始化代码块可以获得 class 内的作用域,如同 class 的方法一样,也意味着可以访问类的 #字段
。
let getX;
export class C {
#x
constructor(x) {
this.#x = { data: x };
}
static {
// getX has privileged access to #x
getX = (obj) => obj.#x;
}
}
export function readXData(obj) {
return getX(obj).data;
}
参考资料
- ECMAScript 双月报告:Pipeline Operator 进入 Stage 2(2021/8)
- https://github.com/tc39/proposals/blob/HEAD/finished-proposals.md