第六章: 行为委托 - 更好的语法
更好的语法
一个使 ES6 class
看似如此诱人的更好的东西是(见附录A来了解为什么要避免它!),声明类方法的速记语法:
class Foo {
methodName() { /* .. */ }
}
我们从声明中扔掉了单词 function
,这使所有的 JS 开发者欢呼!
你可能已经注意到,而且为此感到沮丧:上面推荐的 OLOO 语法出现了许多 function
,这看起来像是对 OLOO 简化目标的诋毁。但它不必是!
在 ES6 中,我们可以在任何字面对象中使用 简约方法声明,所以一个 OLOO 风格的对象可以用这种方式声明(与 class
语法中相同的语法糖):
var LoginController = {
errors: [],
getUser() { // 看,没有 `function`!
// ...
},
getPassword() {
// ...
}
// ...
};
唯一的区别是字面对象的元素间依然需要 ,
逗号分隔符,而 class
语法不必如此。这是在整件事情上很小的让步。
还有,在 ES6 中,一个你使用的更笨重的语法(比如 AuthController
的定义中):你一个一个地给属性赋值而不使用字面对象,可以改写为使用字面对象(于是你可以使用简约方法),而且你可以使用 Object.setPrototypeOf(..)
来修改对象的 [[Prototype]]
,像这样:
// 使用更好的字面对象语法 w/ 简约方法!
var AuthController = {
errors: [],
checkAuth() {
// ...
},
server(url,data) {
// ...
}
// ...
};
// 现在, 链接 `AuthController` 委托至 `LoginController`
Object.setPrototypeOf( AuthController, LoginController );
ES6 中的 OLOO 风格,与简明方法一起,变得比它以前 友好得多(即使在以前,它也比经典的原型风格代码简单好看的多)。 你不必非得选用类(复杂性)来得到干净漂亮的对象语法!
没有词法
简约方法确实有一个缺点,一个重要的细节。考虑这段代码:
var Foo = {
bar() { /*..*/ },
baz: function baz() { /*..*/ }
};
这是去掉语法糖后,这段代码将如何工作:
var Foo = {
bar: function() { /*..*/ },
baz: function baz() { /*..*/ }
};
看到区别了?bar()
的速记法变成了一个附着在 bar
属性上的 匿名函数表达式(function()..
),因为函数对象本身没有名称标识符。和拥有词法名称标识符 baz
,附着在 .baz
属性上的手动指定的 命名函数表达式(function baz()..
)做个比较。
那又怎么样?在 “你不懂 JS” 系列的 “作用域与闭包” 这本书中,我们详细讲解了 匿名函数表达式 的三个主要缺点。我们简单地重复一下它们,以便于我们和简明方法相比较。
一个匿名函数缺少 name
标识符:
- 使调试时的栈追踪变得困难
- 使自引用(递归,事件绑定等)变得困难
- 使代码(稍稍)变得难于理解
第一和第三条不适用于简明方法。
虽然去掉语法糖使用 匿名函数表达式 一般会使栈追踪中没有 name
。简明方法在语言规范中被要求去设置相应的函数对象内部的 name
属性,所以栈追踪应当可以使用它(这是依赖于具体实现的,所以不能保证)。
不幸的是,第二条 仍然是简明方法的一个缺陷。 它们不会有词法标识符用来自引用。考虑:
var Foo = {
bar: function(x) {
if (x < 10) {
return Foo.bar( x * 2 );
}
return x;
},
baz: function baz(x) {
if (x < 10) {
return baz( x * 2 );
}
return x;
}
};
在这个例子中上面的手动 Foo.bar(x*2)
引用就足够了,但是在许多情况下,一个函数不一定能够这样做,比如使用 this
绑定,函数在委托中被分享到不同的对象,等等。你将会想要使用一个真正的自引用,而函数对象的 name
标识符是实现的最佳方式。
只要小心简明方法的这个注意点,而且如果当你陷入缺少自引用的问题时,仅仅为这个声明 放弃简明方法语法,取代以手动的 命名函数表达式 声明形式:baz: function baz(){..}
。