文章目錄
- 關於作者(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)
簡介(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
。
false.toString() // 'false'
[1, 2, 3].toString(); // '1,2,3'
function Foo(){}
Foo.bar = 1;
Foo.bar; // 1
一個常見的誤解是數位字面值(literal)不是對象。這是因為JavaScript解析器的一個錯誤,
它試圖將點操作符解析為浮點數字面值的一部分。
2.toString(); // 出錯:SyntaxError
有很多變通方法可以讓數位字面值看起來像對象。
2..toString(); // 第二個點號可以正常解析
2 .toString(); // 注意點號前面的空格
(2).toString(); // 2先被計算
對象作為資料類型(Objects as a data type)
JavaScript的對象可以作為雜湊表使用,主要用來儲存命名的鍵與值的對應關係。
使用對象的字面文法 - {}
- 可以建立一個簡單對象。這個新建立的對象從Object.prototype
繼承下面,沒有任何自訂屬性。
var foo = {}; // 一個Null 物件
// 一個新對象,擁有一個值為12的自訂屬性'test'
var bar = {test: 12};
訪問屬性(Accessing properties)
有兩種方式來訪問對象的屬性,點操作符或者中括弧操作符。
var foo = {name: 'Kitten'}
foo.name; // kitten
foo['name']; // kitten
var get = 'name';
foo[get]; // kitten
foo.1234; // SyntaxError
foo['1234']; // works
兩種文法是等價的,但是中括弧操作符在下面兩種情況下依然有效
- 動態設定屬性
- 屬性名稱不是一個有效變數名(譯者註:比如屬性名稱中包含空格,或者屬性名稱是JS的關鍵詞)
(譯者註:在JSLint文法偵查工具中,點操作符是推薦做法)
刪除屬性(Deleting properties)
刪除屬性的唯一方法是使用delete
操作符;設定屬性為undefined
或者null
並不能真正的刪除屬性,
而僅僅是移除了屬性和值的關聯。
var obj = {
bar: 1,
foo: 2,
baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;
for(var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i, '' + obj[i]);
}
}
上面的輸出結果有bar undefined
和foo null
- 只有baz
被真正的刪除了,所以從輸出結果中消失。
屬性名稱的文法(Notation of keys)
var test = {
'case': 'I am a keyword so I must be notated as a string',
delete: 'I am a keyword too so me' // 出錯:SyntaxError
};
對象的屬性名稱可以使用字串或者一般字元聲明。但是由於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
將會導致兩個對象共用相同的原型。
因此,改變任意一個對象的原型都會影響到另一個對象的原型,在大多數情況下這不是希望的結果。
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 設定Bar的prototype屬性為Foo的執行個體對象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor為Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 建立Bar的一個新執行個體
// 原型鏈
test [Bar的執行個體]
Bar.prototype [Foo的執行個體]
{ foo: 'Hello World' }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
上面的例子中,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 constructor
of 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的操作將會被忽略。
function Foo() {}
Foo.prototype = 1; // no effect
而將對象賦值給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引擎相容。
本中文翻譯由三上石上原創,部落格園首發,轉載請註明出處。
JavaScript Garden - 中文翻譯