- 原文作者 : Dmitri Pavlutin
- 譯文出自 : 掘金翻譯計(jì)劃
- 譯者 : 章辰(zhangchen91)
- 校對(duì)者: rccoder, Graning
在 ECMAScript 2015 之前茫蛹,Javascript 中的對(duì)象字面量(又叫做對(duì)象初始化器)是相當(dāng)簡(jiǎn)單的,它可以定義2種屬性:
- 成對(duì)的靜態(tài)屬性名和值
{ name1: value1 }
- 通過 getters
{ get name(){..} }
和 setters{ set name(val){..} }
定義的動(dòng)態(tài)計(jì)算屬性值
說來遺憾烁挟,一個(gè)簡(jiǎn)單的例子就可以表示對(duì)象字面量的所有可能性:
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 是一種基于原型繼承的語言婴洼,所以啥都是個(gè)對(duì)象。 所以當(dāng)處理對(duì)象的創(chuàng)建撼嗓、原型的設(shè)置與訪問時(shí)柬采,它必須提供簡(jiǎn)單的構(gòu)造方法欢唾。
定義一個(gè)對(duì)象然后設(shè)置它的原型是普遍流程。我常常覺得原型的設(shè)置應(yīng)該能直接在字面量里用一條語句實(shí)現(xiàn)粉捻。
很不幸匈辱,字面量的限制不允許這樣簡(jiǎn)單直接的實(shí)現(xiàn)方案。你不得不使用 Object.create()
配合字面量來設(shè)置原型:
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
我認(rèn)為這個(gè)方案很不方便杀迹。 JavaScript 是基于原型的亡脸,為什么設(shè)置對(duì)象的原型要這么痛苦?
幸運(yùn)的是 JavaScript 在進(jìn)化树酪,它許多相當(dāng)令人不舒服的特性正在一步步的被解決浅碾。
這篇文章演示了 ES2015 是如何解決以上描述的難題,并增加了哪些特性來提升對(duì)象字面量的能力:
- 在對(duì)象構(gòu)造函數(shù)中設(shè)置原型
- 速寫式方法聲明
- 進(jìn)行
super
調(diào)用 - 可計(jì)算的屬性名
還有我們可以展望一下將來续语,看看 (草案2) 里的新提議: 可收集可展開的屬性垂谢。
1. 在對(duì)象構(gòu)造函數(shù)中設(shè)置原型
正如你已知的,訪問已創(chuàng)建對(duì)象的原型有一種方式是引用 __proto__
這個(gè) getter 屬性:
var myObject = {
name: 'Hello World!'
};
myObject.__proto__; // => {}
myObject.__proto__.isPrototypeOf(myObject); // => true
`myObject.__proto__` 返回 `myObject` 的原型對(duì)象疮茄。
好消息是 ES2015 允許使用 __proto__
在對(duì)象字面量 { __proto__: protoObject }
中作為屬性名來設(shè)置原型滥朱。
讓我們用 __proto__
屬性為對(duì)象初始化,看它是如何改進(jìn)介紹中描述的不直觀方案:
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__
創(chuàng)建的對(duì)象力试,它的原型是 myProto
徙邻。
這個(gè)對(duì)象用了一個(gè)簡(jiǎn)單的聲明來創(chuàng)建,沒有使用類似 Object.create()
的附加函數(shù)畸裳。
如你所見缰犁,使用 __proto__
非常簡(jiǎn)潔. 我通常推薦簡(jiǎn)潔直觀的解決方案。
一些題外話怖糊,我認(rèn)為有點(diǎn)奇怪的是簡(jiǎn)單可擴(kuò)展的解決方案依賴大量的設(shè)計(jì)和工作帅容。如果一個(gè)方案很簡(jiǎn)潔,你也許認(rèn)為它是容易設(shè)計(jì)的伍伤。然而事實(shí)完全相反:
- 讓事情變得簡(jiǎn)單直接很復(fù)雜
- 讓事情變得復(fù)雜難以理解很容易
如果一些事情看起來很復(fù)雜或者很難使用并徘,可能它是沒有被充分考慮過。
關(guān)于返璞歸真扰魂,你怎么看麦乞?(隨意留言評(píng)論)
1.1 特殊的情況下 __proto__
的使用手冊(cè)
即使 __proto__
看起來很簡(jiǎn)潔, 這有一些特定的場(chǎng)景你需要注意到阅爽。
對(duì)象字面量中 __proto__
只允許使用 一次 路幸。重復(fù)使用 JavaScript 會(huì)拋出異常:
var object = {
__proto__: {
toString: function() {
return '[object Numbers]'
}
},
numbers: [1, 5, 89],
__proto__: {
toString: function() {
return '[object ArrayOfNumbers]'
}
}
};
例子中的對(duì)象字面量聲明了兩個(gè) __proto__
屬性,這是不允許的付翁。這種情況會(huì)拋出 SyntaxError: Duplicate __proto__ fields are not allowed in object literals
的語法錯(cuò)誤简肴。
JavaScript 有只能使用對(duì)象或 null
作為 __proto__
屬性值的約束。任何嘗試使用原始類型們 (字符串百侧,數(shù)字砰识,布爾值) 乃至 undefined
會(huì)被忽略掉能扒,不能改變對(duì)象的原型。
讓我們看看這個(gè)限制的例子:
var objUndefined = {
__proto__: undefined
};
Object.getPrototypeOf(objUndefined); // => {}
var objNumber = {
__proto__: 15
};
Object.getPrototypeOf(objNumber); // => {}
這個(gè)對(duì)象字面量使用了 undefined
和數(shù)字 15
來設(shè)置 __proto__
的值辫狼。因?yàn)橹挥袑?duì)象或 null
允許被當(dāng)做原型初斑, objUndefined
和 objNumber
仍然擁有他們默認(rèn)的原型: JavaScript 空對(duì)象 {}
。 __proto__
的值被忽略了膨处。
當(dāng)然见秤,嘗試用原始類型去設(shè)置對(duì)象的原型會(huì)挺奇怪。這里的約束符合預(yù)期真椿。
2. 速寫式方法聲明
我們可以在對(duì)象字面量中使用一個(gè)更短的語法來聲明方法鹃答,一個(gè)能省略掉 function
關(guān)鍵字和 :
符號(hào)的方式。它被稱之為速寫式方法聲明突硝。
讓我們使用這個(gè)新的短模式來定義一些方法吧:
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
里用這個(gè)短模式定義的方法测摔。
這個(gè)方法聲明的方式還一個(gè)好處是它們都是非匿名函數(shù),這在調(diào)試的時(shí)候會(huì)很方便解恰。 上個(gè)例子執(zhí)行 collection.add.name
返回函數(shù)名 'add'
锋八。
譯者注:好像非速寫式聲明的函數(shù)名字也是一樣,調(diào)用堆棧的表現(xiàn)也都一樣护盈,這里不太明白挟纱。
3. 進(jìn)行 super
調(diào)用
一個(gè)有趣的改進(jìn)是可以使用 super
關(guān)鍵字來訪問原型鏈中父類的屬性。瞧瞧下面的這個(gè)例子:
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
calc
是 numbers
對(duì)象的原型黄琼。在 numbers
的 sumElements
方法中可以通過 super
關(guān)鍵字調(diào)用原型的 super.sumArray()
方法樊销。
最終, super
是調(diào)用對(duì)象原型鏈里父類屬性的快捷方式脏款。
上面的例子其實(shí)可以直接用 calc.sumArray()
調(diào)用它的原型。然而因?yàn)?super
基于原型鏈調(diào)用裤园,是一個(gè)更推薦的方式撤师。并且它的存在明確得表示了父類屬性即將被調(diào)用。
3.1 super
的使用限制
super
在對(duì)象字面量中 只能在速寫式方法聲明里 使用拧揽。
如果嘗試在普通的方法聲明 { name: function() {} }
中使用剃盾, JavaScript 會(huì)拋出異常:
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();
這個(gè) sumElements
方法是通過屬性: sumElements: function() {...}
定義的。 因?yàn)?super
只能在速寫式方法聲明中使用淤袜,這種情況下調(diào)用會(huì)拋出 SyntaxError: 'super' keyword unexpected here
的語法錯(cuò)誤痒谴。
這個(gè)約束不太影響對(duì)象字面量的聲明方式,多數(shù)情況下因?yàn)檎Z法更簡(jiǎn)潔铡羡,使用速寫式方法聲明會(huì)更好积蔚。
4. 可計(jì)算的屬性名
在 ES2015 之前, 在對(duì)象字面量初始化中,對(duì)象的屬性名大部分是靜態(tài)的字符串烦周。為了創(chuàng)建一個(gè)經(jīng)過運(yùn)算的屬性名尽爆,你不得不使用訪問器函數(shù)創(chuàng)建屬性怎顾。
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 }
很明顯,這種方式定義屬性有點(diǎn)不那么友好漱贱。
可計(jì)算的屬性名優(yōu)雅的解決了這個(gè)問題槐雾。
當(dāng)你要通過某個(gè)表達(dá)式計(jì)算屬性名,在方括號(hào) {[expression]: value}
里替換對(duì)應(yīng)的代碼幅狮。對(duì)應(yīng)的表達(dá)式會(huì)把計(jì)算結(jié)果作為屬性名募强。
我非常喜歡這個(gè)語法:簡(jiǎn)短又簡(jiǎn)潔。
讓我們改進(jìn)上面的例子:
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')]
通過計(jì)算 prefix('number', 'pi')
表達(dá)式設(shè)置了 'number_pi'
這個(gè)屬性名.
相應(yīng)的 [prefix('bool', 'false')]
表達(dá)式設(shè)置了另一個(gè)屬性名 'bool_false'
崇摄。
4.1 Symbol
作為屬性名
Symbols 運(yùn)算也可以作為可計(jì)算的屬性名钻注。只需要保證把它們括在括號(hào)里: { [Symbol('name')]: 'Prop value' }
。
舉個(gè)栗子配猫,讓我們用 Symbol.iterator
這個(gè)特殊的屬性幅恋,去遍歷對(duì)象的自有屬性名。如下所示:
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 *() { }
定義了一個(gè)屬性來遍歷對(duì)象的自有屬性泵肄。 展開操作符 [...object]
使用了迭代器來返回自有屬性的數(shù)組捆交。
5. 對(duì)未來的一個(gè)展望: 可收集可展開的屬性
對(duì)象字面量的可收集可展開的屬性 目前是草案第二階段 (stage 2) 中的一個(gè)提議,它將被選入下一個(gè) Javascript 版本腐巢。
它們等價(jià)于展開和收集操作符 品追,已經(jīng)可以在 ECMAScript 2015 中被數(shù)組所使用。
可收集的屬性 允許收集一個(gè)對(duì)象在解構(gòu)賦值后剩下的屬性們冯丙。
下面這個(gè)例子收集了 object
解構(gòu)后留下的屬性:
var object = {
propA: 1,
propB: 2,
propC: 3
};
let {propA, ...restObject} = object;
propA; // => 1
restObject; // => { propB: 2, propC: 3 }
可展開的屬性 允許從一個(gè)源對(duì)象拷貝它的自有屬性到另一個(gè)對(duì)象字面量中肉瓦。這個(gè)例子中對(duì)象字面量的其它屬性合集是從 source
對(duì)象中展開的:
var source = {
propB: 2,
propC: 3
};
var object = {
propA: 1,
...source
}
object; // => { propA: 1, propB: 2, propC: 3 }
6. 總結(jié)
JavaScript 正在大步前進(jìn)。
即使一個(gè)相當(dāng)小的對(duì)象字面量改進(jìn)都會(huì)在 ECMAScript 2015 里考慮胃惜。以及很多草案里的新特性提議泞莉。
你可以在對(duì)象初始化時(shí)直接通過 __proto__
屬性名設(shè)置其原型。比用 Object.create()
簡(jiǎn)單很多船殉。
現(xiàn)在方法聲明有個(gè)更簡(jiǎn)潔的模式鲫趁,所以你不必輸入 function
關(guān)鍵字。而且在速寫式聲明里利虫,你可以使用 super
關(guān)鍵字挨厚,它允許你十分容易得通過對(duì)象的原型鏈訪問父類屬性。
如果屬性名需要在運(yùn)行時(shí)計(jì)算糠惫,現(xiàn)在你可以用可計(jì)算的屬性名 [expression]
來初始化對(duì)象疫剃。
對(duì)象字面量現(xiàn)在確實(shí)很酷!
你覺得呢硼讽?隨意留言評(píng)論巢价。
本文鏈接:
http://zhangchen91.cn/post/why-object-literals-in-javascript-are-cool.html