你一定很熟悉用这种形式的对象字面量声明:

    如果到处说x: x总是让你感到繁冗,那么有个好消息。如果你需要定义一个名称和词法标识符一致的属性,你可以将它从x: x缩写为x。考虑如下代码:

    1. var x = 2, y = 3,
    2. o = {
    3. x,
    4. y
    5. };

    本着与我们刚刚检视的简约属性相同的精神,添附在对象字面量属性上的函数也有一种便利简约形式。

    以前的方式:

    1. var o = {
    2. x: function(){
    3. // ..
    4. },
    5. y: function(){
    6. // ..
    7. }
    8. }

    而在ES6中:

    1. var o = {
    2. x() {
    3. // ..
    4. },
    5. y() {
    6. // ..
    7. }
    8. }

    警告: 虽然x() { .. }看起来只是x: function(){ .. }的缩写,但是简约方法有一种特殊行为,是它们对应的老方式所不具有的;确切地说,是允许super(参见本章稍后的“对象super”)的使用。

    Generator(见第四章)也有一种简约方法形式:

    1. var o = {
    2. *foo() { .. }
    3. };

    简约匿名

    虽然这种便利缩写十分诱人,但是这其中有一个微妙的坑要小心。为了展示这一点,让我们检视一下如下的前ES6代码,你可能会试着使用简约方法来重构它:

    1. function runSomething(o) {
    2. var x = Math.random(),
    3. y = Math.random();
    4. return o.something( x, y );
    5. }
    6. runSomething( {
    7. something: function something(x,y) {
    8. if (x > y) {
    9. // 使用相互对调的`x`和`y`来递归地调用
    10. return something( y, x );
    11. }
    12. return y - x;
    13. }
    14. } );

    这段蠢代码只是生成两个随机数,然后用大的减去小的。但这里重要的不是它做的是什么,而是它是如何被定义的。让我把焦点放在对象字面量和函数定义上,就像我们在这里看到的:

    1. runSomething( {
    2. something: function something(x,y) {
    3. // ..
    4. }
    5. } );

    为什么我们同时说something:function something?这不是冗余吗?实际上,不是,它们俩被用于不同的目的。属性something让我们能够调用o.something(..),有点儿像它的公有名称。但是第二个something是一个词法名称,使这个函数可以为了递归而从内部引用它自己。

    你能看出来为什么return something(y,x)这一行需要名称something来引用这个函数吗?因为这里没有对象的词法名称,要是有的话我们就可以说return o.something(y,x)或者其他类似的东西。

    当一个对象字面量的确拥有一个标识符名称时,这其实是一个很常见的做法,比如:

    这是个好主意吗?也许是,也许不是。你在假设名称controller将总是指向目标对象。但它也很可能不是 —— 函数makeRequest(..)不能控制外部的代码,因此不能强制你的假设一定成立。这可能会回过头来咬到你。

    另一些人喜欢使用this定义这样的东西:

    1. var controller = {
    2. makeRequest: function(..){
    3. this.makeRequest(..);
    4. }
    5. };

    这看起来不错,而且如果你总是用controller.makeRequest(..)来调用方法的话它就应该能工作。但现在你有一个this绑定的坑,如果你做这样的事情的话:

    1. btn.addEventListener( "click", controller.makeRequest, false );

    当然,你可以通过传递controller.makeRequest.bind(controller)作为绑定到事件上的处理器引用来解决这个问题。但是这很讨厌 —— 它不是很吸引人。

    或者要是你的内部this.makeRequest(..)调用需要从一个嵌套的函数内发起呢?你会有另一个this绑定灾难,人们经常使用var self = this这种用黑科技解决,就像:

    1. var controller = {
    2. makeRequest: function(..){
    3. var self = this;
    4. btn.addEventListener( "click", function(){
    5. // ..
    6. self.makeRequest(..);
    7. }, false );
    8. }
    9. };

    注意: 更多关于this绑定规则和陷阱的信息,参见本系列的 this与对象原型 的第一到二章。

    好了,这些与简约方法有什么关系?回想一下我们的something(..)方法定义:

    1. runSomething( {
    2. something: function something(x,y) {
    3. // ..
    4. }
    5. } );

    在这里的第二个something提供了一个超级便利的词法标识符,它总是指向函数自己,给了我们一个可用于递归,事件绑定/解除等等的完美引用 —— 不用乱搞this或者使用不可靠的对象引用。

    太好了!

    那么,现在我们试着将函数引用重构为这种ES6解约方法的形式:

    1. runSomething( {
    2. something(x,y) {
    3. if (x > y) {
    4. return something( y, x );
    5. }
    6. return y - x;
    7. }
    8. } );

    第一眼看上去不错,除了这个代码将会坏掉。return something(..)调用经不会找到something标识符,所以你会得到一个ReferenceError。噢,但为什么?

    上面的ES6代码段将会被翻译为:

    1. runSomething( {
    2. something: function(x,y){
    3. if (x > y) {
    4. return something( y, x );
    5. }
    6. return y - x;
    7. }
    8. } );

    仔细看。你看出问题了吗?简约方法定义暗指something: function(x,y)。看到我们依靠的第二个something是如何被省略的了吗?换句话说,简约方法暗指匿名函数表达式。

    对,讨厌。

    注意: 你可能认为在这里=>箭头函数是一个好的解决方案。但是它们也同样不够,因为它们也是匿名函数表达式。我们将在本章稍后的“箭头函数”中讲解它们。

    一个部分地补偿了这一点的消息是,我们的简约函数something(x,y)将不会是完全匿名的。参见第七章的“函数名”来了解ES6函数名称的推断规则。这不会在递归中帮到我们,但是它至少在调试时有用处。

    那么我们怎样总结简约方法?它们简短又甜蜜,而且很方便。但是你应当仅在你永远不需要将它们用于递归或事件绑定/解除时使用它们。否则,就坚持使用你的老式something: function something(..)方法定义。

    你的很多方法都将可能从简约方法定义中受益,这是个非常好的消息!只要小心几处未命名的灾难就好。

    ES5 Getter/Setter

    技术上讲,ES5定义了getter/setter字面形式,但是看起来它们没有被太多地使用,这主要是由于缺乏转译器来处理这种新的语法(其实,它是ES5中加入的唯一的主要新语法)。所以虽然它不是一个ES6的新特性,我们也将简单地复习一下这种形式,因为它可能会随着ES6的向前发展而变得有用得多。

    考虑如下代码:

    这些getter和setter字面形式也可以出现在类中;参见第三章。

    警告: 可能不太明显,但是setter字面量必须恰好有一个被声明的参数;省略它或罗列其他的参数都是不合法的语法。这个单独的必须参数 可以 使用解构和默认值(例如,set id({ id: v = 0 }) { .. }),但是收集/剩余...是不允许的(set id(...v) { .. })。

    1. var prefix = "user_";
    2. var o = {
    3. baz: function(..){ .. }
    4. };
    5. o[ prefix + "foo" ] = function(..){ .. };
    6. o[ prefix + "bar" ] = function(..){ .. };
    7. ..

    ES6为对象字面定义增加了一种语法,它允许你指定一个应当被计算的表达式,其结果就是被赋值属性名。考虑如下代码:

    1. var prefix = "user_";
    2. var o = {
    3. baz: function(..){ .. },
    4. [ prefix + "foo" ]: function(..){ .. },
    5. [ prefix + "bar" ]: function(..){ .. }
    6. ..
    7. };

    任何合法的表达式都可以出现在位于对象字面定义的属性名位置的[ .. ]内部。

    很有可能,计算型属性名最经常与Symbol(我们将在本章稍后的“Symbol”中讲解)一起使用,比如:

    1. var o = {
    2. [Symbol.toStringTag]: "really cool thing",
    3. ..
    4. };

    Symbol.toStringTag是一个特殊的内建值,我们使用[ .. ]语法求值得到,所以我们可以将值"really cool thing"赋值给这个特殊的属性名。

    计算型属性名还可以作为简约方法或简约generator的名称出现:

    1. var o = {
    2. ["f" + "oo"]() { .. } // 计算型简约方法
    3. *["b" + "ar"]() { .. } // 计算型简约generator
    4. };

    我们不会在这里讲解原型的细节,所以关于它的更多信息,参见本系列的 this与对象原型

    有时候在你声明对象字面量的同时给它的[[Prototype]]赋值很有用。下面的代码在一段时期内曾经是许多JS引擎的一种非标准扩展,但是在ES6中得到了标准化:

    1. var o1 = {
    2. // ..
    3. };
    4. var o2 = {
    5. __proto__: o1,
    6. // ..
    7. };

    o2是用一个对象字面量声明的,但它也被[[Prototype]]链接到了o1。这里的__proto__属性名还可以是一个字符串"__proto__",但是要注意它 不能 是一个计算型属性名的结果(参见前一节)。

    客气点儿说,__proto__是有争议的。在ES6中,它看起来是一个最终被很勉强地标准化了的,几十年前的自主扩展功能。实际上,它属于ES6的“Annex B”,这一部分罗列了JS感觉它仅仅为了兼容性的原因,而不得不标准化的东西。

    警告: 虽然我勉强赞同在一个对象字面定义中将__proto__作为一个键,但我绝对不赞同在对象属性形式中使用它,就像o.__proto__。这种形式既是一个getter也是一个setter(同样也是为了兼容性的原因),但绝对存在更好的选择。更多信息参见本系列的 this与对象原型

    对于给一个既存的对象设置[[Prototype]],你可以使用ES6的工具Object.setPrototypeOf(..)。考虑如下代码:

    1. var o1 = {
    2. // ..
    3. };
    4. var o2 = {
    5. // ..
    6. };

    注意: 我们将在第六章中再次讨论Object。“Object.setPrototypeOf(..)静态函数”提供了关于Object.setPrototypeOf(..)的额外细节。另外参见“Object.assign(..)静态函数”来了解另一种将o2原型关联到o1的形式。

    super通常被认为是仅与类有关。然而,由于JS对象仅有原型而没有类的性质,super是同样有效的,而且在普通对象的简约方法中行为几乎一样。

    考虑如下代码:

    警告: super仅在简约方法中允许使用,而不允许在普通的函数表达式属性中。而且它还仅允许使用super.XXX形式(属性/方法访问),而不是super()形式。

    在方法o2.foo()中的super引用被静态地锁定在了o2,而且明确地说是o2[[Prototype]]。这里的super基本上是Object.getPrototypeOf(o2) —— 显然被解析为o1 —— 这就是他如何找到并调用的。

    关于super的完整细节,参见第三章的“类”。