这几天 JavaScript 的私有属性又成为了前端社区热议的话题。原因很简单,这家伙长这样:
惊不惊喜!意不意外!
而且 TC39 委员会以及对此达成了一致意见,并且该提案已经进入了 stage 3。在 es 规范阶段 stage 3 是候选提案,又很大的可能会进入到下一个标准。到目前为止,已经可以确定会进入 es2019(es10) 标准的有 optional-catch-binding 和 proposal-json-superset。而这个 private 大概也只能进入到 es2020 后的标准了。
TC39 委员会解释道,他们也是做了深思熟虑最终选择了 #
符号,而没有使用 private
关键字。其中还讨论了把 private
和 #
符号一起使用的方案。并且还打算预留了一个 @
关键字作为 protected
属性?,不过这个 @
已经被 decorator 使用了。
我们在此就不讨论这个语法到底丑不丑了,这不显而易见的吗,还用讨论吗。我们讨论一下为什么要使用 # 符号。
有人说,“如果这个进入了规范,我就再也不用 JavaScript了,以后项目都用 TypeScript 写”。
这就有点由不得你了,我们看 TypeScript 的官网介绍:
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
TypeScript 是 JavaScript 的超集,言外之意就是 js 有的 ts 也全都有。对于一个已经进入 stage 3 的提案,ts 当然不会坐视不理的。ts 也正在抓紧支持这个语法:All Bloomberg Changes for Private Fields and Methods。
也有人疑问:“为什么 ts 的私有属性就可以使用 private
关键字,而 js 就不行呢?”
因为 ts 的 private
和这个 #
语法完全是不同的东西。TypeScript 是一种静态类型语言而不是强类型语言(有争议)。private 只是在编译时做检查,最终生成的 js 代码中被 private
修饰的属性依然是公开的。
那为什么不使用下划线(_
)呢,毕竟这个已经是私有属性的非正式的最佳实践了?
之所以不选择下划线是出于兼容性考虑,另一个由于兼容性而妥协的提案是 JavaScript 社区由一个库引发的“smoosh门”事件到底怎么回事? 由于下划线是合法的 js 变量标识符所以很多的代码都使用了下划线,如果新的 js 规范把下划线开头的变量作为私有属性,那么会导致很多就代码无法运行。比如知名的 lodash 和 underscore 库就是使用的下划线,这两个库每周在 npm 的下载量是 2 千万。
“为什么不使用 private?”
这里有一个经典的例子:
class Foo {private value;equals(foo) {return this.value === foo.value;}
}
复制代码
在很多面向对象语言中都有这种写法,判断类的两个实例是否相等。如果在 js 中也这么写,会有一些问题。
在静态类型语言中,我们可以明确的知道传入的参数是 Foo
类型,因此在 Foo
的 equals
方法中我们可以访问 foo
的私有属性 value
。但是 js 是动态类型的,我们无法知道参数 foo
的类型,如果我们传入了 {value: '123'}
会,则函数的行为不符合我们的预期。这也导致了该函数有时访问的是私有属性,有时访问的是公有属性。
另一方面,私有属性只在类的内部可以访问,外部无法访问,甚至不知道此变量的存在。因此在 JavaScript 中同名的私有属性和公有属性可以同时存在,两者使用 #
做区分。
如果使用 private 进行修饰,则我们可以探测类的私有属性:
foo.bar = 1; // Error: `bar` is private! (boom... detected)
复制代码
或者:
foo.bar = 1;
foo.bar; // `undefined` (boom... detected again)
复制代码
PS:虽然社区的整体意见是使用 private
代替 #
,但是 TC39 的意见很坚决。
最后,这种引入私有属性的方式,其实隐式的为 js 引入了静态类型。