tc39 proposal: Classes static fields and methods
回顾
ES2015 引入了 static
关键字,在类中使用,用来创建 static public method(and getter/setters),案例如下:
class Foo {
static bar() {}
static set baz(val) {}
static get baz() {}
}
这种方式相当于在类(即这里的 SkinnedMesh
)上直接声明属性,而非在类实例上。
新语法
当然,只有一个 Static public methods 的功能显然是不够的,于是基于 class field declarations / class private methods and getter/setters 提案中的语法,该提案(处于 Stage 3 阶段)做了如下的语法补充:
- Static public fields
- Static private fields
- Static private methods(and getter/setters)
Static public fields
class Foo {
// public static method(ES2015)
static baz() {}
// public static field(Stage 3)
static bar = function () {}
}
看到上面的语法时,我有点疑惑,public static method/field 的区别在哪里?我在控制台上打印了下两个属性的描述符情况:
根据打印结果可以推测,它们的区别仅在与该静态属性是否可被遍历。public static field 是可遍历的(enumerable: true
),ES2015 中引入的 public static method 则是不可遍历的(enumerable: false
)。
这份代码,使用 Babel 转译成 ES5 代码,如下:
var Foo = /*#__PURE__*/ (function () {
function Foo() {
_classCallCheck(this, Foo);
}
// `baz` (public static method)转换后的代码
_createClass(Foo, null, [
{
key: "baz",
value: function baz() {}
}
]);
return Foo;
})();
// `bar` (public static field)转换后的代码
_defineProperty(Foo, "bar", function () {});
再分别来看看 _createClass
和 _defineProperty
函数的定义:
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
// descriptor: { key: "baz", value: function baz() {} }
// (2) descriptor 中不含包 enumerable,最终 descriptor 的 enumerable 属性值为 false
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
// (3) 如果是,数据属性,设置成可写的(writable: true),
// (4) 访问器属性原样输出,不做额外处理
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
// (1) 静态方法 `baz` 的调用方式:
// _createClass([ { key: "baz", value: function baz() {} } ])
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
// 静态成员 `bar` 的调用方式:
// _defineProperty(Foo, "bar", function () {});
function _defineProperty(obj, key, value) {
// 已有该属性,则以 [[Define]] 的方式定义
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
// 否则以 [[Set]] 的方式定义
} else {
obj[key] = value;
}
return obj;
}
结论是,两者其实本质上区别不大,仅有的一点:一个是可遍历的,一个是不可遍历的。
Static private fields
class ColorFinder {
static #red = "#ff0000";
static #green = "#00ff00";
static #blue = "#0000ff";
static colorName(name) {
switch (name) {
case "red": return ColorFinder.#red;
case "blue": return ColorFinder.#blue;
case "green": return ColorFinder.#green;
default: throw new RangeError("unknown color");
}
}
// Somehow use colorName
}
这块很好理解,静态方法内部使用了私有成员,这些成员是直接定义在 ColorFinder
这个类上的,而不是实例上。这样就有效避免了在每个实例上单独创建同一份私有变量带来的内存消耗;另外,也比单独在外面另声明一个常量,逻辑上更加紧凑。
Static private methods(and getter/setters)
export const registry = new JSDOMRegistry();
export class JSDOM {
#createdBy;
#registerWithRegistry() {
// ... elided ...
}
static async fromURL(url, options = {}) {
normalizeFromURLOptions(options);
normalizeOptions(options);
const body = await getBodyFromURL(url);
return JSDOM.#finalizeFactoryCreated(new JSDOM(body, options), "fromURL");
}
static async fromFile(filename, options = {}) {
normalizeOptions(options);
const body = await getBodyFromFilename(filename);
return JSDOM.#finalizeFactoryCreated(new JSDOM(body, options), "fromFile");
}
static #finalizeFactoryCreated(jsdom, factoryName) {
jsdom.#createdBy = factoryName;
jsdom.#registerWithRegistry(registry);
return jsdom;
}
}
上面的代码,在静态公共方法中使用静态私有方法创建 JSDOM 实例,隐藏了内部的实现细节。
总结
在我看来,本提案中提到的语法补充,在实际开发过程中,并不很常用。除了 Static public fields 我觉得会稍微用的多些,但是这部分功能,也几乎能用 static public method 方式替换。而 Static private fields/methods 的使用,应该都是在不断优化中代码中,开始注意去使用的。
(完)
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: