美文网首页前端开发JavaScript学习笔记首页投稿(暂停使用,暂停投稿)
[翻译] Javascript 中的对象字面量很酷,你觉得呢?

[翻译] Javascript 中的对象字面量很酷,你觉得呢?

作者: 36KrFE | 来源:发表于2016-07-31 15:31 被阅读183次

    ECMAScript 2015 之前,Javascript 中的对象字面量(又叫做对象初始化器)是相当简单的,它可以定义2种属性:

    • 成对的静态属性名和值 { name1: value1 }
    • 通过 getters { get name(){..} }setters { set name(val){..} } 定义的动态计算属性值

    说来遗憾,一个简单的例子就可以表示对象字面量的所有可能性:

        var myObject = {  
          myString: 'value 1',
          get myNumber() {
            return this.myNumber;
          },
          set myNumber(value) {
            this.myNumber = Number(value);
          }
        };
        myObject.myString; // => 'value 1'  
        myObject.myNumber = '15';  
        myObject.myNumber; // => 15  
    

    JavaScript 是一种基于原型继承的语言,所以啥都是个对象。 所以当处理对象的创建、原型的设置与访问时,它必须提供简单的构造方法。

    定义一个对象然后设置它的原型是普遍流程。我常常觉得原型的设置应该能直接在字面量里用一条语句实现。

    很不幸,字面量的限制不允许这样简单直接的实现方案。你不得不使用 Object.create() 配合字面量来设置原型:

        var myProto = {  
          propertyExists: function(name) {
            return name in this;    
          }
        };
        var myNumbers = Object.create(myProto);  
        myNumbers['array'] = [1, 6, 7];  
        myNumbers.propertyExists('array');      // => true  
        myNumbers.propertyExists('collection'); // => false  
    

    我认为这个方案很不方便。 JavaScript 是基于原型的,为什么设置对象的原型要这么痛苦?

    幸运的是 JavaScript 在进化,它许多相当令人不舒服的特性正在一步步的被解决。

    这篇文章演示了 ES2015 是如何解决以上描述的难题,并增加了哪些特性来提升对象字面量的能力:

    • 在对象构造函数中设置原型
    • 速写式方法声明
    • 进行 super 调用
    • 可计算的属性名

    还有我们可以展望一下将来,看看 (草案2) 里的新提议: 可收集可展开的属性。

    Infographic

    1. 在对象构造函数中设置原型

    正如你已知的,访问已创建对象的原型有一种方式是引用 __proto__ 这个 getter 属性:

        var myObject = {  
          name: 'Hello World!'
        };
        myObject.__proto__;                         // => {}  
        myObject.__proto__.isPrototypeOf(myObject); // => true  
    
    `myObject.__proto__` 返回 `myObject` 的原型对象。
    

    好消息是 ES2015 允许使用 __proto__ 在对象字面量 { __proto__: protoObject } 中作为属性名来设置原型。

    让我们用 __proto__ 属性为对象初始化,看它是如何改进介绍中描述的不直观方案:

        var myProto = {  
          propertyExists: function(name) {
            return name in this;    
          }
        };
        var myNumbers = {  
          __proto__: myProto,
          array: [1, 6, 7]
        };
        myNumbers.propertyExists('array');      // => true  
        myNumbers.propertyExists('collection'); // => false  
    

    myNumbers 是使用了特殊的属性名 __proto__ 创建的对象,它的原型是 myProto
    这个对象用了一个简单的声明来创建,没有使用类似 Object.create() 的附加函数。

    如你所见,使用 __proto__ 非常简洁. 我通常推荐简洁直观的解决方案。

    一些题外话,我认为有点奇怪的是简单可扩展的解决方案依赖大量的设计和工作。如果一个方案很简洁,你也许认为它是容易设计的。然而事实完全相反:

    • 让事情变得简单直接很复杂
    • 让事情变得复杂难以理解很容易

    如果一些事情看起来很复杂或者很难使用,可能它是没有被充分考虑过。
    关于返璞归真,你怎么看?(随意留言评论)

    1.1 特殊的情况下 __proto__ 的使用手册

    即使 __proto__ 看起来很简洁, 这有一些特定的场景你需要注意到。

    Infographic

    对象字面量中 __proto__ 只允许使用 一次 。重复使用 JavaScript 会抛出异常:

        var object = {  
          __proto__: {
            toString: function() {
              return '[object Numbers]'
            }
          },
          numbers: [1, 5, 89],
          __proto__: {
            toString: function() {
              return '[object ArrayOfNumbers]'
            }
          }
        };
    

    例子中的对象字面量声明了两个 __proto__ 属性,这是不允许的。这种情况会抛出 SyntaxError: Duplicate __proto__ fields are not allowed in object literals 的语法错误。

    JavaScript 有只能使用对象或 null 作为 __proto__ 属性值的约束。任何尝试使用原始类型们 (字符串,数字,布尔值) 乃至 undefined 会被忽略掉,不能改变对象的原型。
    让我们看看这个限制的例子:

        var objUndefined = {  
          __proto__: undefined
        };
        Object.getPrototypeOf(objUndefined); // => {}  
        var objNumber = {  
          __proto__: 15
        };
        Object.getPrototypeOf(objNumber);    // => {}  
    

    这个对象字面量使用了 undefined 和数字 15 来设置 __proto__ 的值。因为只有对象或 null 允许被当做原型, objUndefinedobjNumber 仍然拥有他们默认的原型: JavaScript 空对象 {}__proto__ 的值被忽略了。

    当然,尝试用原始类型去设置对象的原型会挺奇怪。这里的约束符合预期。

    2. 速写式方法声明

    我们可以在对象字面量中使用一个更短的语法来声明方法,一个能省略掉 function 关键字和 : 符号的方式。它被称之为速写式方法声明。

    让我们使用这个新的短模式来定义一些方法吧:

        var collection = {  
          items: [],
          add(item) {
            this.items.push(item);
          },
          get(index) {
            return this.items[index];
          }
        };
        collection.add(15);  
        collection.add(3);  
        collection.get(0); // => 15  
    

    add()get()collection 里用这个短模式定义的方法。

    这个方法声明的方式还一个好处是它们都是非匿名函数,这在调试的时候会很方便。 上个例子执行 collection.add.name 返回函数名 'add'
    译者注:好像非速写式声明的函数名字也是一样,调用堆栈的表现也都一样,这里不太明白。

    3. 进行 super 调用

    一个有趣的改进是可以使用 super 关键字来访问原型链中父类的属性。瞧瞧下面的这个例子:

        var calc = {  
          sumArray (items) {
            return items.reduce(function(a, b) {
              return a + b;
            });
          }
        };
        var numbers = {  
          __proto__: calc,
          numbers: [4, 6, 7],
          sumElements() {
            return super.sumArray(this.numbers);
          }
        };
        numbers.sumElements(); // => 17  
    

    calcnumbers 对象的原型。在 numberssumElements 方法中可以通过 super 关键字调用原型的 super.sumArray() 方法。

    最终, super 是调用对象原型链里父类属性的快捷方式。

    上面的例子其实可以直接用 calc.sumArray() 调用它的原型。然而因为 super 基于原型链调用,是一个更推荐的方式。并且它的存在明确得表示了父类属性即将被调用。

    3.1 super 的使用限制

    super 在对象字面量中 只能在速写式方法声明里 使用。

    如果尝试在普通的方法声明 { name: function() {} } 中使用, JavaScript 会抛出异常:

        var calc = {  
          sumArray (items) {
            return items.reduce(function(a, b) {
              return a + b;
            });
          }
        };
        var numbers = {  
          __proto__: calc,
          numbers: [4, 6, 7],
          sumElements: function() {
            return super.sumArray(this.numbers);
          }
        };
        // Throws SyntaxError: 'super' keyword unexpected here
        numbers.sumElements();  
    

    这个 sumElements 方法是通过属性: sumElements: function() {...} 定义的。 因为 super 只能在速写式方法声明中使用,这种情况下调用会抛出 SyntaxError: 'super' keyword unexpected here 的语法错误。

    这个约束不太影响对象字面量的声明方式,多数情况下因为语法更简洁,使用速写式方法声明会更好。

    4. 可计算的属性名

    在 ES2015 之前, 在对象字面量初始化中,对象的属性名大部分是静态的字符串。为了创建一个经过运算的属性名,你不得不使用访问器函数创建属性。

        function prefix(prefStr, name) {  
           return prefStr + '_' + name;
        }
        var object = {};  
        object[prefix('number', 'pi')] = 3.14;  
        object[prefix('bool', 'false')] = false;  
        object; // => { number_pi: 3.14, bool_false: false }  
    

    很明显,这种方式定义属性有点不那么友好。

    可计算的属性名优雅的解决了这个问题。
    当你要通过某个表达式计算属性名,在方括号 {[expression]: value} 里替换对应的代码。对应的表达式会把计算结果作为属性名。

    我非常喜欢这个语法:简短又简洁。

    让我们改进上面的例子:

        function prefix(prefStr, name) {  
           return prefStr + '_' + name;
        }
        var object = {  
          [prefix('number', 'pi')]: 3.14,
          [prefix('bool', 'false')]: false
        };
        object; // => { number_pi: 3.14, bool_false: false }  
    

    [prefix('number', 'pi')] 通过计算 prefix('number', 'pi') 表达式设置了 'number_pi' 这个属性名.
    相应的 [prefix('bool', 'false')] 表达式设置了另一个属性名 'bool_false'

    4.1 Symbol 作为属性名

    Symbols 运算也可以作为可计算的属性名。只需要保证把它们括在括号里: { [Symbol('name')]: 'Prop value' }

    举个栗子,让我们用 Symbol.iterator 这个特殊的属性,去遍历对象的自有属性名。如下所示:

        var object = {  
           number1: 14,
           number2: 15,
           string1: 'hello',
           string2: 'world',
           [Symbol.iterator]: function *() {
             var own = Object.getOwnPropertyNames(this),
               prop;
             while(prop = own.pop()) {
               yield prop;
             }
           }
        }
        [...object]; // => ['number1', 'number2', 'string1', 'string2']
    

    [Symbol.iterator]: function *() { } 定义了一个属性来遍历对象的自有属性。 展开操作符 [...object] 使用了迭代器来返回自有属性的数组。

    5. 对未来的一个展望: 可收集可展开的属性

    对象字面量的可收集可展开的属性 目前是草案第二阶段 (stage 2) 中的一个提议,它将被选入下一个 Javascript 版本。

    它们等价于展开和收集操作符 ,已经可以在 ECMAScript 2015 中被数组所使用。

    可收集的属性 允许收集一个对象在解构赋值后剩下的属性们。
    下面这个例子收集了 object 解构后留下的属性:

        var object = {  
          propA: 1,
          propB: 2,
          propC: 3
        };
        let {propA, ...restObject} = object;  
        propA;      // => 1  
        restObject; // => { propB: 2, propC: 3 }  
    

    可展开的属性 允许从一个源对象拷贝它的自有属性到另一个对象字面量中。这个例子中对象字面量的其它属性合集是从 source 对象中展开的:

        var source = {  
          propB: 2,
          propC: 3
        };
        var object = {  
          propA: 1,
          ...source
        }
        object; // => { propA: 1, propB: 2, propC: 3 }  
    

    6. 总结

    JavaScript 正在大步前进。

    即使一个相当小的对象字面量改进都会在 ECMAScript 2015 里考虑。以及很多草案里的新特性提议。

    你可以在对象初始化时直接通过 __proto__ 属性名设置其原型。比用 Object.create() 简单很多。

    现在方法声明有个更简洁的模式,所以你不必输入 function 关键字。而且在速写式声明里,你可以使用 super 关键字,它允许你十分容易得通过对象的原型链访问父类属性。

    如果属性名需要在运行时计算,现在你可以用可计算的属性名 [expression] 来初始化对象。

    对象字面量现在确实很酷!
    你觉得呢?随意留言评论。

    本文链接:
    http://zhangchen91.cn/post/why-object-literals-in-javascript-are-cool.html

    相关文章

      网友评论

      • Txtaonl:正在学习中,虽然现在还看不懂
        36KrFE:@Txtaonl 加油~

      本文标题:[翻译] Javascript 中的对象字面量很酷,你觉得呢?

      本文链接:https://www.haomeiwen.com/subject/pdztsttx.html