在ES6之前,JS中的对象字面量(又名对象初始化器)非常基础。可以定义两类属性:key-valuepairs{name1:value1}getters{getname(){..}}andsetters{setname(val){..}}computedpropertyvaluesvarmyObject={myString:'value1',getmyNumber(){returnthis._myNumber;},setmyNumber(value){this._myNumber=Number(value);},};myObject.myString;//=>'value1'myObject.myNumber='15';myObject.myNumber;//=>15JS是一种基于原型的语言,所以一切都是对象。当涉及到对象创建、配置和访问原型时,提供一种易于构建的语言势在必行。定义一个对象并设置它的原型是一项常见的任务。最好的方法是直接在对象字面量中使用一条语句来设置原型。不幸的是,文字的局限性不允许简单的解决方案来实现这一点。必须结合对象字面量使用object.create()来设置原型。varmyProto={propertyExists:function(name){returnnameinthis;}};varmyNumbers=Object.create(myProto);myNumbers['arrat']=[1,6,7];myNumbers.propertyExists('array');//=>truemyNumbers.propertyExists('collection');//=>false我觉得这个方案不够灵活。JS基于原型。为什么使用原型创建对象这么麻烦?好在JS也在慢慢完善。JS中很多令人头疼的问题都是一步步解决的。本文演示了ES6如何解决上述问题并通过附加功能改进对象字面量。在对象构造上设置原型方法的声明supercallcomputedpropertyname1.在对象构造上设置原型如您所知,访问现有对象原型的一种方法是使用getter属性__proto__:varmyObject={name:'HelloWorld!',};myObject.__proto__;//=>{}myObject.__proto__.isPrototypeOf(myObject);//=>truemyObject.__proto__返回myObject的原型对象。请注意,不建议使用object.__proto__作为getter/setter。作为替代方案,请考虑使用Object.getPrototypeOf()和Object.setPrototypeOf()。ES6允许使用__proto__作为属性名,并在{__proto__:protoObject}中设置原型。接下来,我们使用__proto__属性进行对象初始化,并优化上面的代码:varmyProto={propertyExists:function(name){returnnameinthis;},};varmyNumbers={__proto__:myProto,array:[1,6,7],};myNumbers.propertyExists('array');//=>truemyNumbers.propertyExists('collection');//=>falsemyNumbers对象是使用特殊属性名proto和原型myProto创建的,这次我们使用语句创建,没有像上面需要的object.create()那样的附加函数。如您所见,使用__proto__编码很简单,我一直喜欢简单明了的解决方案。说点题外话。我觉得奇怪的是,一个简单灵活的解决方案需要大量的工作和设计。如果解决方案很简单,您可能会认为它很容易设计。但反之亦然:简单明了很复杂,复杂难懂很容易。如果某些东西看起来太复杂或难以使用,则可能需要进一步改进。你对简单性有什么看法?(欢迎在下面写评论)2.proto用法的特殊情况尽管__proto__看起来很简单,但也有一些特殊情况你应该注意。对象字面量中只能使用一次__proto__,否则JS会报错:varobject={__proto__:{toString:function(){return'[objectNumbers]'}},numbers:[1,5,89],__proto__:{toString:function(){return'[objectArrayOfNumbers]'}}};上例中的对象字面量中使用了两次__proto__属性,这是不允许的。在这种情况下,将抛出一个错误:SyntaxError:Duplicate__proto__fieldsarenotallowedinobjectliterals。JS约束只能取一个对象或null作为__proto__属性的值。任何原始类型(字符串、数字、布尔值)或未定义类型的使用都将被忽略,并且不会更改对象的原型。varobjUndefined={__proto__:undefined,};Object.getPrototypeOf(objUndefined);//=>{}varobjNumber={__proto__:15,};Object.getPrototypeOf(objNumber);//=>{}对象字面量使用undefined和数字15设置__proto__值。因为只允许对象或null作为原型,__proto__值被忽略,但objUndefined和objNumber仍然有它们的默认原型:plainJSobject{},.当然,尝试使用原始类型对对象进行原型设计也很奇怪。当对象字面量的字符串求值为'proto'{['__proto__']:protoObj}时也要小心。以这种方式创建的属性不会改变对象的原型,而只是简单地创建一个拥有键'__proto__'的属性3.速记方法定义可以使用更短的语法在对象常量中声明方法,以省略function关键字和:冒号方式.这称为速记方法定义。接下来,我们使用速记方法来定义一些方法:varcollection={items:[],add(item){this.items.push(item);},get(index){returnthis.items[index];},};collection.add(15);collection.add(3);collection.get(0);//=>15一个很好的好处是这样声明的方法是命名函数,这对于调试很有用。执行上述示例中的collection.add.name会返回函数名称“add”。(1)super的使用JS中一个有趣的改进是能够使用super关键字作为对从原型链继承的属性的访问。请参见以下示例:varcalc={numbers:null,sumElements(){returnthis.numbers.reduce(function(a,b){returna+b;});},};varnumbers={__proto__:calc,numbers:[4,6,7],sumElements(){if(this.numbers==null||this.numbers.length===0){return0;}returnsuper.sumElements();},};numbers.sumElements();//=>17calc是numbers对象的原型。在数字的sumElements方法中,可以使用super关键字从原型访问方法:super.sumElements()最后,super是从对象的原型链访问继承属性的快捷方式。在前面的例子中,可以尝试直接执行calc.sumElements()调用原型,会报错。但是,super.sumElements()可以被正确调用,因为它访问了对象的原型链。并确保原型中的sumElements()方法使用this.numbers正确访问数组。super的存在清楚地表明将使用继承的属性。(2)super的使用限制Super只能在对象字面量的速记方法定义中使用。如果您尝试从普通方法声明{name:function(){}}访问它,JS将抛出错误:varcalc={numbers:null,sumElements(){returnthis.numbers.reduce(function(a,b){return+b;});},};varnumbers={__proto__:calc,numbers:[4,6,7],sumElements:function(){if(this.numbers==null||this.numbers.length===0){return0;}returnsuper.sumElements();},};//ThrowsSyntaxError:'super'keywordunexpectedherenumbers.sumElements();方法sumElements被定义为一个属性:sumElements:function(){...}。因为super只能用在速记方法中,所以在这种情况下调用它会抛出SyntaxError:'super'keywordunexpectedhere。此限制在很大程度上不会影响对象文字的声明方式。由于语法较短,通常最好使用速记方法定义。4.计算属性名在ES6之前,对象初始化是以字面量的形式进行的,通常是静态字符串。要创建具有计算名称的属性,您必须使用属性访问器。functionprefix(prefStr,name){returnprefStr+'_'+name;}varobject={};object[prefix('number','pi')]=3.14;object[prefix('bool','false')]=false;object;//=>{number_pi:3.14,bool_false:false}当然,这种定义属性的方式令人愉快。然后使用速记修改上面的例子:functionprefix(prefStr,name){returnprefStr+'_'+name;}varobject={[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'。(1)symbol作为属性名symbol也可以作为计算属性名。只需确保将它们括在方括号中:{[Symbol('name')]:'Propvalue'}例如,使用特殊属性Symbol.iterator并迭代对象自己的属性名称。如下例所示:varobject={number1:14,number2:15,string1:'hello',string2:'world',[Symbol.iterator]:function*(){varown=Object.getOwnPropertyNames(this),支柱;while(prop=own.pop()){yieldprop;}}}[...object];//=>['number1','number2','string1','string2'][Symbol.iterator]:function*(){}定义用于迭代对象自身属性的属性。传播运算符[...object]采用迭代器并返回自身属性的列表(2)remnant和spread属性remnant属性允许从分配被销毁后留下的对象收集属性。下面的例子收集了对象解构后剩余的属性:varobject={propA:1,propB:2,propC:3,};let{propA,...restObject}=object;propA;//=>1restObject;//=>{propB:2,propC:3}Spread属性允许将源对象自身的属性复制到对象字面量中。在这个例子中,对象字面量从源对象收集对象的其他属性:varsource={propB:2,propC:3,};varobject={propA:1,...source,};object;//=>{propA:1,propB:2,propC:3}6.总结即使像对象字面量这样相对较小的结构在ES6中也得到了相当大的改进。可以使用__proto__属性名称直接从初始化程序设置对象的原型。这比使用Object.create()更容易。请注意,__proto__是ES6标准附件B的一部分,不鼓励使用它。此附加组件实现对于浏览器是必需的,但对于其他环境是可选的。NodeJS4、5和6支持此功能。方法声明现在具有更短的形式,因此不必键入function关键字。在简化方法中,您可以使用super关键字,它可以轻松访问对象原型链中的继承属性。如果属性名称是在运行时计算的,您现在可以使用计算的属性名称[expression]来初始化对象。
