文章目錄
- 關於作者(The authors)
- 貢獻者(Contributors)
- 許可(License)
- 中文翻譯(Chinese Translation)
- 對象作為資料類型(Objects as a data type)
- 訪問屬性(Accessing properties)
- 刪除屬性(Deleting properties)
- 屬性名稱的文法(Notation of keys)
- 屬性尋找(Property lookup)
- 原型屬性(The prototype property)
- 效能(Performance)
- 擴充內建類型的原型(Extension of native prototypes)
- 總結(In conclusion)
http://bonsaiden.github.com/JavaScript-Garden/zh/#object.forinloop
簡介(Intro)
JavaScript秘密花園是一個不斷更新的文檔,主要關心JavaScript一些古怪用法。 對於如何避免常見的錯誤,難以發現的問題,以及效能問題和不好的實踐給出建議, 初學者可以籍此深入瞭解JavaScript的語言特性。
JavaScript秘密花園不是用來教你JavaScript。為了更好的理解這篇文章的內容, 你需要事先學習JavaScript的基礎知識。在Mozilla開發人員網路中有一系列非常棒的JavaScript學習嚮導。
關於作者(The authors)
這篇文章的作者是兩位Stack Overflow的使用者, Ivo Wetzel(寫作) 和 Zhang Yi Jiang (設計)。
貢獻者(Contributors)
- Caio Rom?o (拼字檢查)
- Andreas Blixt (語言修正)
許可(License)
JavaScript花園在MIT license許可協議下發布,並存放在開源社區GitHub。 如果你發現錯誤或者打字錯誤,請file an issue或者pull request。 你也可以在Stack Overflow的聊天室JavaScript room找到我們。
中文翻譯(Chinese Translation)
- JavaScript Garden - 原文
- JavaScript Garden - 中文翻譯
- 譯作者:三生石上
本中文翻譯由三上石上原創,部落格園首發,轉載請註明出處。
對象(Objects) #top
JavaScript中所有變數都是對象,除了兩個例外null
和 undefined
。
?
1 |
<code> false .toString() // 'false'<br>[1, 2, 3].toString(); // '1,2,3'<br><br>function Foo(){}<br>Foo.bar = 1;<br>Foo.bar; // 1<br></code> |
一個常見的誤解是數位字面值(literal)不是對象。這是因為JavaScript解析器的一個錯誤, 它試圖將點操作符解析為浮點數字面值的一部分。
?
1 |
<code>2.toString(); // 出錯:SyntaxError<br></code> |
有很多變通方法可以讓數位字面值看起來像對象。
?
1 |
<code>2..toString(); // 第二個點號可以正常解析<br>2 .toString(); // 注意點號前面的空格<br>(2).toString(); // 2先被計算<br></code> |
對象作為資料類型(Objects as a data type)
JavaScript的對象可以作為雜湊表使用,主要用來儲存命名的鍵與值的對應關係。
使用對象的字面文法 - {}
- 可以建立一個簡單對象。這個新建立的對象從Object.prototype
繼承下面,沒有任何自訂屬性。
?
1 |
<code> var foo = {}; // 一個Null 物件<br><br>// 一個新對象,擁有一個值為12的自訂屬性'test'<br>var bar = {test: 12};<br></code> |
訪問屬性(Accessing properties)
有兩種方式來訪問對象的屬性,點操作符或者中括弧操作符。
?
1 |
<code> var foo = {name: 'Kitten' }<br>foo.name; // kitten<br>foo['name']; // kitten<br><br>var get = 'name';<br>foo[get]; // kitten<br><br>foo.1234; // SyntaxError<br>foo['1234']; // works<br></code> |
兩種文法是等價的,但是中括弧操作符在下面兩種情況下依然有效 - 動態設定屬性 - 屬性名稱不是一個有效變數名(譯者註:比如屬性名稱中包含空格,或者屬性名稱是JS的關鍵詞) (譯者註:在JSLint文法偵查工具中,點操作符是推薦做法)
刪除屬性(Deleting properties)
刪除屬性的唯一方法是使用delete
操作符;設定屬性為undefined
或者null
並不能真正的刪除屬性, 而僅僅是移除了屬性和值的關聯。
?
1 |
<code> var obj = {<br> bar: 1,<br> foo: 2,<br> baz: 3<br>};<br>obj.bar = undefined;<br>obj.foo = null ;<br> delete obj.baz;<br><br> for ( var i in obj) {<br> if (obj.hasOwnProperty(i)) {<br> console.log(i, '' + obj[i]);<br> }<br>}<br></code> |
上面的輸出結果有bar undefined
和foo null
- 只有baz
被真正的刪除了,所以從輸出結果中消失。
屬性名稱的文法(Notation of keys)?
1 |
<code> var test = {<br> 'case' : 'I am a keyword so I must be notated as a string' ,<br> delete : 'I am a keyword too so me' // 出錯:SyntaxError<br>};<br></code> |
對象的屬性名稱可以使用字串或者一般字元聲明。但是由於JavaScript解析器的另一個錯誤設計, 上面的第二種聲明方式在ECMAScript 5之前會拋出SyntaxError
的錯誤。
這個錯誤的原因是delete
是JavaScript語言的一個關鍵詞;因此為了在更低版本的JavaScript引擎下也能正常運行, 必須使用字串字面值聲明方式。
原型(The prototype) #top
JavaScript不包含傳統的類繼承模型,而是使用prototypical原型模型。
雖然這經常被當作是JavaScript的缺點被提及,其實基於原型的繼承模型比傳統的類繼承還要強大。 實現傳統的類繼承模型是很簡單,但是實現JavaScript中的原型繼承則要困難的多。 (It is for example fairly trivial to build a classic model on top of it, while the other way around is a far more difficult task.)
由於JavaScript是唯一一個被廣泛使用的基於原型繼承的語言,所以理解兩種繼承模式的差異是需要一定時間的。
第一個不同之處在於JavaScript使用原型鏈的繼承方式。
注意: 簡單的使用Bar.prototype = Foo.prototype
將會導致兩個對象共用相同的原型。 因此,改變任意一個對象的原型都會影響到另一個對象的原型,在大多數情況下這不是希望的結果。
?
1 |
<code> function Foo() {<br> this .value = 42;<br>}<br>Foo.prototype = {<br> method: function () {}<br>};<br><br> function Bar() {}<br><br> // 設定Bar的prototype屬性為Foo的執行個體對象<br>Bar.prototype = new Foo();<br>Bar.prototype.foo = 'Hello World';<br><br>// 修正Bar.prototype.constructor為Bar本身<br>Bar.prototype.constructor = Bar;<br><br>var test = new Bar() // 建立Bar的一個新執行個體<br><br>// 原型鏈<br>test [Bar的執行個體]<br> Bar.prototype [Foo的執行個體] <br> { foo: 'Hello World' }<br> Foo.prototype<br> {method: ...};<br> Object.prototype<br> {toString: ... /* etc. */};<br><br></code> |
上面的例子中,test
對象從Bar.prototype
和Foo.prototype
繼承下來;因此, 它能否訪問Foo
的原型方法method
。但是它不能訪問Foo
的執行個體屬性value
, 因為這個屬性在Foo
的建構函式中定義。 (But it will not have access to the property value
of a Foo
instance, since that property gets defined in the constructorof Foo
. But this constructor has to be called explicitly.)
(譯者註:我認為這個描述是錯誤的,test.value是可以訪問的。 因為在設定Bar.prototype = new Foo();時,value
也就成為Bar.prototype上的一個屬性。 如果你有不同觀點,可以到我的部落格評論。)
注意: 不要使用Bar.prototype = Foo
,因為這不會執行Foo
的原型,而是指向函數Foo
。 因此原型鏈將會回溯到Function.prototype
而不是Foo.prototype
,因此method
將不會在Bar的原型鏈上。
屬性尋找(Property lookup)
當尋找一個對象的屬性時,JavaScript會向上遍曆原型鏈,直到找到給定名稱的屬性為止。
到尋找到達原型鏈的頂部 - 也就是Object.prototype
- 但是仍然沒有找到指定的屬性,就會返回undefined。
原型屬性(The prototype property)
當原型屬性用來建立原型鏈時,可以把任何類型的值賦給它(prototype)。 然而將原子類型賦給prototype的操作將會被忽略。
?
1 |
<code> function Foo() {}<br>Foo.prototype = 1; // no effect<br></code> |
而將對象賦值給prototype,正如上面的例子所示,將會動態建立原型鏈。
效能(Performance)
如果一個屬性在原型鏈的上端,則對於尋找時間將帶來不利影響。特別的,試圖擷取一個不存在的屬性將會遍曆整個原型鏈。
並且,當使用for-in迴圈遍曆對象的屬性時,原型鏈上的所有屬性都將被訪問。
擴充內建類型的原型(Extension of native prototypes)
一個錯誤特性被經常使用,那就是擴充Object.prototype
或者其他內建類型的原型對象。
這種技術被稱之為monkey patching並且會破壞封裝。雖然它被廣泛的應用到一些JS類庫中比如Prototype,但是我仍然不認為為內建類型添加一些非標準的函數是個好主意。
擴充內建類型的唯一理由是為了和新的JavaScript保持一致,比如Array.forEach
。 (譯者註:這是編程領域常用的一種方式,稱之為Backport,也就是將新的補丁添加到老版本中。)The only good reason for extending a built-in prototype is to backport the features of newer JavaScript engines; for example, Array.forEach
.
總結(In conclusion)
在寫複雜的JavaScript應用之前,充分理解原型鏈繼承的工作方式是每個JavaScript程式員必修的功課。 要提防原型鏈過長帶來的效能問題,並知道如何通過縮短原型鏈來提高效能。 更進一步,絕對不要擴充內建類型的原型,除非是為了和新的JavaScript引擎相容。