No prefix! operator is Ok!
栏目: JavaScript · 发布时间: 7年前
内容简介:所有在我对原有提案的修改中,核心不是“不用原有提案:修改的提议:
所有在我对原有提案的修改中,核心不是“不用 #
字符”,而是将它从一个前缀字符,变成了一个操作符。这一方面是使“声明语法”与“表达式运算”分开,另一方面也让这些修改与ECMAScript规范保持在语法上的一致性。
原有提案: https://github.com/tc39/proposal-class-fields
修改的提议: #issuecomment-429533532
1. 为什么是":“而不是”="?
在所有类、对象等声明性质的语法中,":"是表明"key/value pair"的,既然这里的私有字段仍然是“key/value pair”,那么仍然建议使用该符号。而原提案建议使用 =
并且与TypeScript保持一致,却忽略了TypeScript中完整的语法 x: number = 100
中, :
是指示类型的,而不是用于指示值。——这与ECMAScript的一般规则并不一致。
ECMAScript规范事实上是沿用了“旧式的对象字面量”的属性声明与初值的语法,亦即是:
obj = {
x: 100,
y: 100
}
注意在这个语法中”有或没有 ,
号都是接受的,但如果有 ,
号则称为一个List,且整个声明是以一个“没有 ,
号的单个属性声明”结束的。——这与传统的对象字面量声明语法一致。
在ECMAScript中,类声明一定程度上沿用和扩展了这一语法。一方面,把“方法声明”给到了对象字面量声明;另一方面,从对象字面量那里把“get/setter声明”拿了过来。而与TypeScript类似的 = xxxx
语法,尽管也是一个称为 Initializer
的语法组件,也的确出现在了对象初始化语法中,但是作为错误语法来识别的(CoverInitializedName):
PropertyDefinition: CoverInitializedName
Always throw a Syntax Error if code matches this production.
所以,回到最前面的说明,推荐的语法设计是:
// Because
obj = {
data: 100,
foo() {
....
}
}
// YES
class f {
data: 100,
foo() {
...
}
}
// NO!!!
class f {
data = 100;
...
}
这样使用 f()
类构造出来的对象实例,与用相似语法声明的对象字面量是类似的。不存在语法设计上的“例外(unexpected)”。
2. 为什么是"private x"而不是"#"?
在现有类、对象等声明语法中是使用限定词来指示成员性质的,例如 static
、 get
等。除了生成器函数之外,并不使用限定符号或“前缀(prefix)”,因此我建议用限定词 private
来扩展类声明语法,而反对原提案中使用 #
作为限定符来声明类私有字段(private field of class)。例如:
// YES
class f {
private data: 100
}
// NO!
class f {
#data = 100;
}
3.为什么是",“而不是”;"?
在所有的列表(List)中,ECMAScript采用的分隔符都是 ,
号,例如参数列表、对象/数组成员列表,以及导入导出的名字列表,还有变量 var
声明的名字列表等等。而 ;
在语法中惯例是用作语句结束符或分隔符,包括你所见的各种(任意的)语句,以及 for(;;)
中的子句等等。我们既然是在声明类或对象的“成员列表”,那么显然应该是按“列表(List)”的规则处理成 ,
为好,怎么会用到 ;
号了呢?
TypeScript中在这个位置是这样声明的:
class f {
data: string = 'in typescript';
...
}
留意这里的语法特点:符号 :
是用于指示类型的,因此初值声明使用了 =
。这个声明与 var
语句声明类似,TypeScript将这里处理成了 ;
是可以理解的。但ECMAScript何必要绕开现成的 ,
号不用,去使用在这里毫无意义的 ;
号呢?
4.最后一个","号的问题
ECMAScript既然已经接受了对象字面量声明的末尾逗号(object literal trailing commas),那么下面两种声明都可以是合法的了:
// 推荐
class f {
data: 100,
foo() {
...
}
}
// (可接受)
class f {
..., // more, a list with ','
data: 100
foo() {
...
}
}
5.其它的声明示例
包括:
var data = 'outer';
class f {
// reference
data, // outer reference, no computed
// public
data: 100, // for object, equ: f.prototype.data = 100
static data: 100, // for class, equ: f.data = 100
["da"+"ta"]: 100, // computed
static ["da"+"ta"]: 100, // computed
// private
private data: 100, // normal private object properties
private ["da"+"ta"]: 100, // computed private object properties, and symbol keys
private static data: 100, // for class static field
private static ["da" + "ta"]: 100, // computed
// get&setter, etc
private get data() { ... }
...
}
6.私有属性的存取
私有属性的存取是一个大问题,也是 #
语法争议的焦点。首先必须确定的是:私有属性存取的语义是什么?
在原提案中,私有属性存取是将 #
作为一个前缀(prefix),而存取运算仍然是 .
或 []
运算符。因此,本质上来说,存取运算的操作没变,但是需要在存取中判断属性名的前缀是否是 #
字符。如下例:
// 原提案
class f {
#data = 100;
foo() {
console.log(this.#data); // "#"是前缀,而"."是存取运算符
}
}
根本原因出在 .
运算检测 data
是否是私有成员的成本过高。例如:
// 示例:一个不太可行的方案
class f{
data = 100; // 假设这里是私有成员
foo() {
console.log(this.data); // 假设这里的this.data指向私有成员
}
}
x = new f;
x.data = '200';
f(); // 应该显示200还是100?
那么在上面这个例子中, x.data
添加了一个公开的属性时,foo()方法是无法识别用户代码的意图的。
所以在旧的提案中需要使用 #
前缀。但是,仔细思考这个问题:
- 私有字段列表与自有成员列表必须是同一个吗?
当然不需要。那么为什么不为私有字段列表安排一个专门的运算符呢?只要“像使用super一样”限定它使用的上下文就好。因此,新的语法设计如下:
class f{
data: 100,
foo() {
console.log(this#data);
}
}
也就是说 #
现在是当作一个运算符(operator )在用,而不是一个前缀(prefix)。
但为什么是 #
?
答案是:老实说,我也想不到更好的了。如果你能找一个大家都满意的,我接受。
NOTE:
一个备选的运算符可以是 ->
,但老实说我认为比 #
更差劲。
7.其它
-
在本方案中,是默认用“对象或类”的自有成员列表来实现的,这意味着总是需要用类似
this#xxx的语法来存取它。不过这并非唯一的方案。 -
关于类似于”采用词法上下文“来实现私有成员的问题,我另写文章来讨论吧。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective Java
Joshua Bloch / Addison-Wesley Professional / 2018-1-6 / USD 54.99
The Definitive Guide to Java Platform Best Practices—Updated for Java 9 Java has changed dramatically since the previous edition of Effective Java was published shortly after the release of Jav......一起来看看 《Effective Java》 这本书的介绍吧!