前言
本文作為對(duì)本書的一些知識(shí)點(diǎn)的收集
正文
1. LHS 和 RHS
當(dāng)變量出現(xiàn)在賦值操作的左側(cè)時(shí)進(jìn)行
LHS查詢,出現(xiàn)在右側(cè)時(shí)進(jìn)行RHS查詢;講得再準(zhǔn)確一點(diǎn),RHS查詢與簡(jiǎn)單地查找某個(gè)變量的值別無二致,而LHS查詢則是試圖找到變量的容器本身;從這個(gè)角度說,RHS并不是真正意義上的 “賦值操作的右側(cè)” ,更準(zhǔn)確地說是 “非左側(cè)”;可以將RHS理解成retrieve his source value(取到它的源值),這意味著 “得到某某的值”
2. ReferenceError 和 TypeError
ReferenceError同作用域判別失敗相關(guān),而TypeError則代表作用域判別成功了,但是對(duì)
結(jié)果的操作是非法或不合理的。
3. 不成功的 RHS 引用會(huì)導(dǎo)致拋出 ReferenceError 異常。不成功的 LHS 引用會(huì)導(dǎo)致自動(dòng)隱式地創(chuàng)建一個(gè)全局變量(非嚴(yán)格模式下),該變量使用 LHS 引用的目標(biāo)作為標(biāo)識(shí)符,或者拋
出 ReferenceError 異常(嚴(yán)格模式下)。
4. 小測(cè)驗(yàn)答案
function foo(a) {
var b = a;
return a + b;
}
var c = foo( 2 );
// 1. 找出所有的 LHS 查詢(這里有 3 處?。?c = ..;、a = 2(隱式變量分配)、b = ..
// 2. 找出所有的 RHS 查詢(這里有 4 處!)
foo(2..、= a;、a ..、.. b
5. 個(gè)不推薦使用 eval(..) 和 with 的原因
會(huì)被嚴(yán)格模式所影響(限制)。with 被完全禁止,而在保留核心功能的前提下,間接或非安全地使用 eval(..) 也被禁止了。
JavaScript 引擎會(huì)在編譯階段進(jìn)行數(shù)項(xiàng)的性能優(yōu)化。其中有些優(yōu)化依賴于能夠根據(jù)代碼的詞法進(jìn)行靜態(tài)分析,并預(yù)先確定所有變量和函數(shù)的定義位置,才能在執(zhí)行過程中快速找到標(biāo)識(shí)符。
但如果引擎在代碼中發(fā)現(xiàn)了 eval(..) 或 with,它只能簡(jiǎn)單地假設(shè)關(guān)于標(biāo)識(shí)符位置的判斷都是無效的,因?yàn)闊o法在詞法分析階段明確知道 eval(..) 會(huì)接收到什么代碼,這些代碼會(huì)如何對(duì)作用域進(jìn)行修改,也無法知道傳遞給 with 用來創(chuàng)建新詞法作用域的對(duì)象的內(nèi)容到底是什么。
最悲觀的情況是如果出現(xiàn)了 eval(..) 或 with,所有的優(yōu)化可能都是無意義的,因此最簡(jiǎn)單的做法就是完全不做任何優(yōu)化。
如果代碼中大量使用 eval(..) 或 with,那么運(yùn)行起來一定會(huì)變得非常慢。無論引擎多聰明,試圖將這些悲觀情況的副作用限制在最小范圍內(nèi),也無法避免如果沒有這些優(yōu)化,代碼會(huì)運(yùn)行得更慢這個(gè)事實(shí)。
6. eval(..) 和 with 小結(jié)
JavaScript 中有兩個(gè)機(jī)制可以“欺騙”詞法作用域:eval(..) 和 with。前者可以對(duì)一段包含一個(gè)或多個(gè)聲明的“代碼”字符串進(jìn)行演算,并借此來修改已經(jīng)存在的詞法作用域(在運(yùn)行時(shí))。后者本質(zhì)上是通過將一個(gè)對(duì)象的引用當(dāng)作作用域來處理,將對(duì)象的屬性當(dāng)作作用域中的標(biāo)識(shí)符來處理,從而創(chuàng)建了一個(gè)新的詞法作用域(同樣是在運(yùn)行時(shí))。
這兩個(gè)機(jī)制的副作用是引擎無法在編譯時(shí)對(duì)作用域查找進(jìn)行優(yōu)化,因?yàn)橐嬷荒苤?jǐn)慎地認(rèn)為這樣的優(yōu)化是無效的。使用這其中任何一個(gè)機(jī)制都將導(dǎo)致代碼運(yùn)行變慢。不要使用它們。
7. 函數(shù)聲明和函數(shù)表達(dá)式
區(qū)分函數(shù)聲明和表達(dá)式最簡(jiǎn)單的方法是看 function 關(guān)鍵字出現(xiàn)在聲明中的位
置(不僅僅是一行代碼,而是整個(gè)聲明中的位置)。如果 function 是聲明中
的第一個(gè)詞,那么就是一個(gè)函數(shù)聲明,否則就是一個(gè)函數(shù)表達(dá)式。
8. 在 JavaScript 中被稱為模塊的模式
function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
9. 當(dāng)只需要一個(gè)實(shí)例時(shí),可以對(duì)上面這個(gè)模式進(jìn)行簡(jiǎn)單的改進(jìn)來實(shí)現(xiàn)單例模式:
var foo = (function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
})();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
10. 現(xiàn)代的模塊機(jī)制
大多數(shù)模塊依賴加載器 / 管理器本質(zhì)上都是將這種模塊定義封裝進(jìn)一個(gè)友好的 API。這里
并不會(huì)研究某個(gè)具體的庫,為了宏觀了解我會(huì)簡(jiǎn)單地介紹一些核心概念:
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
這段代碼的核心是
modules[name] = impl.apply(impl, deps)。為了模塊的定義引入了包裝
函數(shù)(可以傳入任何依賴),并且將返回值,也就是模塊的 API,儲(chǔ)存在一個(gè)根據(jù)名字來管理的模塊列表中。
下面展示了如何使用它來定義模塊:
MyModules.define( "bar", [], function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
} );
MyModules.define( "foo", ["bar"], function(bar) {
var hungry = "hippo";
function awesome() {
console.log( bar.hello( hungry ).toUpperCase() );
}
return {
awesome: awesome
};
} );
var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );
console.log(bar.hello( "hippo" )); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO
"foo" 和 "bar" 模塊都是通過一個(gè)返回公共 API 的函數(shù)來定義的。"foo" 甚至接受 "bar" 的
示例作為依賴參數(shù),并能相應(yīng)地使用它
它們符合前面列出的模塊模式的兩個(gè)特點(diǎn):為函數(shù)定義引入包裝函數(shù),并保證它的返回值和模塊的 API 保持一致。
11. 使用 new 來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用的原理
- 舉例來說,思考一下
Number(..)作為構(gòu)造函數(shù)時(shí)的行為,ES5.1中這樣描述它:
15.7.2 Number 構(gòu)造函數(shù)
當(dāng) Number 在 new 表達(dá)式中被調(diào)用時(shí),它是一個(gè)構(gòu)造函數(shù):它會(huì)初始化新創(chuàng)建的對(duì)象。
- 所以,包括內(nèi)置對(duì)象函數(shù)(比如
Number(..),詳情請(qǐng)查看第 3 章)在內(nèi)的所有函數(shù)都可以用new來調(diào)用,這種函數(shù)調(diào)用被稱為構(gòu)造函數(shù)調(diào)用。
這里有一個(gè)重要但是非常細(xì)微的區(qū)別:實(shí)際上并不存在所謂的“構(gòu)造函數(shù)”,只有對(duì)于函數(shù)的“構(gòu)造調(diào)用”。 - 使用 new 來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時(shí),會(huì)自動(dòng)執(zhí)行下面的操作。
- 創(chuàng)建(或者說構(gòu)造)一個(gè)全新的對(duì)象。
- 這個(gè)新對(duì)象會(huì)被執(zhí)行 [[ 原型 ]] 連接。
- 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的 this。
- 如果函數(shù)沒有返回其他對(duì)象,那么 new 表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。
12. this 綁定的優(yōu)先級(jí)
- 根據(jù)優(yōu)先級(jí)來判斷函數(shù)在某個(gè)調(diào)用位置應(yīng)用的是哪條規(guī)則??梢园凑障旅娴捻樞騺磉M(jìn)行判斷:
- 函數(shù)是否在 new 中調(diào)用(new 綁定)?如果是的話 this 綁定的是新創(chuàng)建的對(duì)象。
var bar = new foo()- 函數(shù)是否通過 call、apply(顯式綁定)或者硬綁定調(diào)用?如果是的話,this 綁定的是
指定的對(duì)象。
- 函數(shù)是否通過 call、apply(顯式綁定)或者硬綁定調(diào)用?如果是的話,this 綁定的是
var bar = foo.call(obj2)- 函數(shù)是否在某個(gè)上下文對(duì)象中調(diào)用(隱式綁定)?如果是的話,this 綁定的是那個(gè)上下文對(duì)象。
var bar = obj1.foo()- 如果都不是的話,使用默認(rèn)綁定。如果在嚴(yán)格模式下,就綁定到 undefined,否則綁定到全局對(duì)象。
var bar = foo() - 在某些場(chǎng)景下
this的綁定行為會(huì)出乎意料- 如果你把
null或者undefined作為this的綁定對(duì)象傳入call、apply或者bind,這些值在調(diào)用時(shí)會(huì)被忽略,實(shí)際應(yīng)用的是默認(rèn)綁定規(guī)則:
function foo() { console.log( this.a ); } var a = 2; foo.call( null ); // 2 - 如果你把
13. 創(chuàng)建一個(gè)空對(duì)象最簡(jiǎn)單的方法
如果函數(shù)并不關(guān)心
this的話,你
仍然需要傳入一個(gè)占位值,使用這個(gè)方法比null更安全,它就是一個(gè)空的非委托的對(duì)象
// 因?yàn)?? 表示 “希望 this 是空”,這比 null 的含義更清楚。不過你可以用任何喜歡的名字來命名 DMZ(demilitarized zone) 對(duì)象
var ? = Object.create(null)
Object.create(null)和{}很 像, 但 是 并 不 會(huì) 創(chuàng) 建Object.prototype這個(gè)委托,所以它比{}“更空”:
- 代碼示例:
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我們的 DMZ 空對(duì)象
var ? = Object.create( null );
// 把數(shù)組展開成參數(shù)
foo.apply( ?, [2, 3] ); // a:2, b:3
// 使用 bind(..) 進(jìn)行柯里化
var bar = foo.bind( ?, 2 );
bar( 3 ); // a:2, b:3
14. this 全面解析 小結(jié)
-
如果要判斷一個(gè)運(yùn)行中函數(shù)的
this綁定,就需要找到這個(gè)函數(shù)的直接調(diào)用位置。找到之后就可以順序應(yīng)用下面這四條規(guī)則來判斷this的綁定對(duì)象。- 由
new調(diào)用?綁定到新創(chuàng)建的對(duì)象。
- 由
- 由
call或者apply(或者bind)調(diào)用?綁定到指定的對(duì)象。
- 由
- 由上下文對(duì)象調(diào)用?綁定到那個(gè)上下文對(duì)象。
- 默認(rèn):在嚴(yán)格模式下綁定到
undefined,否則綁定到全局對(duì)象。
- 默認(rèn):在嚴(yán)格模式下綁定到
一定要注意,有些調(diào)用可能在無意中使用默認(rèn)綁定規(guī)則。如果想“更安全”地忽略
this綁定,你可以使用一個(gè)DMZ對(duì)象,比如? = Object.create(null),以保護(hù)全局對(duì)象。ES6中的箭頭函數(shù)并不會(huì)使用四條標(biāo)準(zhǔn)的綁定規(guī)則,而是根據(jù)當(dāng)前的詞法作用域來決定this,具體來說,箭頭函數(shù)會(huì)繼承外層函數(shù)調(diào)用的this綁定(無論this綁定到什么)。這其實(shí)和ES6之前代碼中的 self = this 機(jī)制一樣。
15. 類型
- 對(duì)象是
JavaScript的基礎(chǔ)。在JavaScript中一共有六種主要類型(術(shù)語是“語言類型”):- string
- number
- boolean
- null
- undefined
- object
注意,簡(jiǎn)單基本類型(
string、boolean、number、null和undefined)本身并不是對(duì)象。
null有時(shí)會(huì)被當(dāng)作一種對(duì)象類型,但是這其實(shí)只是語言本身的一個(gè)bug,即對(duì)null執(zhí)行typeof null時(shí)會(huì)返回字符串 "object"。實(shí)際上,null本身是基本類型。原理是這樣的,不同的對(duì)象在底層都表示為二進(jìn)制,在
JavaScript中二進(jìn)制前三位都為 0 的話會(huì)被判斷為object類型,null的二進(jìn)制表示是全 0,自然前三位也是 0,所以執(zhí)行typeof時(shí)會(huì)返回“object”。
有一種常見的錯(cuò)誤說法是“JavaScript 中萬物皆是對(duì)象”,這顯然是錯(cuò)誤的。
實(shí)際上,
JavaScript中有許多特殊的對(duì)象子類型,我們可以稱之為復(fù)雜基本類型。
函數(shù)就是對(duì)象的一個(gè)子類型(從技術(shù)角度來說就是“可調(diào)用的對(duì)象”)。
JavaScript中的函數(shù)是“一等公民”,因?yàn)樗鼈儽举|(zhì)上和普通的對(duì)象一樣(只是可以調(diào)用),所以可以像操作其他對(duì)象一樣操作函數(shù)(比如當(dāng)作另一個(gè)函數(shù)的參數(shù))。
數(shù)組也是對(duì)象的一種類型,具備一些額外的行為。數(shù)組中內(nèi)容的組織方式比一般的對(duì)象要稍微復(fù)雜一些。
16. 內(nèi)置對(duì)象
-
JavaScript中還有一些對(duì)象子類型,通常被稱為內(nèi)置對(duì)象。有些內(nèi)置對(duì)象的名字看起來和簡(jiǎn)單基礎(chǔ)類型一樣,不過實(shí)際上它們的關(guān)系更復(fù)雜。- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
JavaScript中,這些內(nèi)置對(duì)象實(shí)際上只是一些內(nèi)置函數(shù)。這些內(nèi)置函數(shù)可以當(dāng)作構(gòu)造函數(shù)(由new產(chǎn)生的函數(shù)調(diào)用)來使用,從而可以構(gòu)造一個(gè)對(duì)應(yīng)子類型的新對(duì)象。舉例來說:
var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true
// 檢查 sub-type 對(duì)象
Object.prototype.toString.call( strObject ); // [object String]
從代碼中可以看到,
strObject是由String構(gòu)造函數(shù)創(chuàng)建的一個(gè)對(duì)象。
原始值 "I am a string" 并不是一個(gè)對(duì)象,它只是一個(gè)字面量,并且是一個(gè)不可變的值。
如果要在這個(gè)字面量上執(zhí)行一些操作,比如獲取長度、訪問其中某個(gè)字符等,那需要將其轉(zhuǎn)換為 String 對(duì)象。
var strPrimitive = "I am a string";
console.log( strPrimitive.length ); // 13
console.log( strPrimitive.charAt( 3 ) ); // "m"
使用以上兩種方法,我們都可以直接在字符串字面量上訪問屬性或者方法,之所以可以這樣做,是因?yàn)橐孀詣?dòng)把字面量轉(zhuǎn)換成
String對(duì)象,所以可以訪問屬性和方法。
同樣的事也會(huì)發(fā)生在數(shù)值字面量上,如果使用類似42.359.toFixed(2)的方法,引擎會(huì)把對(duì)象 42 轉(zhuǎn)換成new Number(42)。對(duì)于布爾字面量來說也是如此。
null和undefined沒有對(duì)應(yīng)的構(gòu)造形式,它們只有文字形式。相反,Date只有構(gòu)造,沒有文字形式。
對(duì)于
Object、Array、Function和RegExp(正則表達(dá)式)來說,無論使用文字形式還是構(gòu)造形式,它們都是對(duì)象,不是字面量。在某些情況下,相比用文字形式創(chuàng)建對(duì)象,構(gòu)造形式可以提供一些額外選項(xiàng)。由于這兩種形式都可以創(chuàng)建對(duì)象,所以我們首選更簡(jiǎn)單的文字形式。建議只在需要那些額外選項(xiàng)時(shí)使用構(gòu)造形式。
Error對(duì)象很少在代碼中顯式創(chuàng)建,一般是在拋出異常時(shí)被自動(dòng)創(chuàng)建。也可以使用new Error(..)這種構(gòu)造形式來創(chuàng)建,不過一般來說用不著。
17. 內(nèi)容
- 需要強(qiáng)調(diào)的一點(diǎn)是,當(dāng)我們說“內(nèi)容”時(shí),似乎在暗示這些值實(shí)際上被存儲(chǔ)在對(duì)象內(nèi)部,但是這只是它的表現(xiàn)形式。在引擎內(nèi)部,這些值的存儲(chǔ)方式是多種多樣的,一般并不會(huì)存在對(duì)象容器內(nèi)部。存儲(chǔ)在對(duì)象容器內(nèi)部的是這些屬性的名稱,它們就像指針(從技術(shù)角度來說就是引用)一樣,指向這些值真正的存儲(chǔ)位置。
var myObject = {
a: 2
};
myObject.a; // 2
myObject["a"]; // 2
如果要訪問 myObject 中 a 位置上的值,我們需要使用 . 操作符或者 [] 操作符。.a 語法通常被稱為“屬性訪問”,["a"] 語法通常被稱為“鍵訪問”。實(shí)際上它們?cè)L問的是同一個(gè)位置,并且會(huì)返回相同的值 2,所以這兩個(gè)術(shù)語是可以互換的。
這兩種語法的主要區(qū)別在于 . 操作符要求屬性名滿足標(biāo)識(shí)符的命名規(guī)范,而 [".."] 語法可以接受任意
UTF-8/Unicode字符串作為屬性名。舉例來說,如果要引用名稱為 "SuperFun!"的屬性,那就必須使用 ["Super-Fun!"] 語法訪問,因?yàn)?Super-Fun!并不是一個(gè)有效的標(biāo)識(shí)符屬性名。
- 在對(duì)象中,屬性名永遠(yuǎn)都是字符串。如果你使用
string(字面量)以外的其他值作為屬性名,那它首先會(huì)被轉(zhuǎn)換為一個(gè)字符串。即使是數(shù)字也不例外,雖然在數(shù)組下標(biāo)中使用的的確是數(shù)字,但是在對(duì)象屬性名中數(shù)字會(huì)被轉(zhuǎn)換成字符串,所以當(dāng)心不要搞混對(duì)象和數(shù)組中數(shù)字的用法:
var myObject = { };
myObject[true] = "foo";
myObject[3] = "bar";
myObject[myObject] = "baz";
myObject["true"]; // "foo"
myObject["3"]; // "bar"
myObject["[object Object]"]; // "baz"
18. 可計(jì)算屬性名
- ES6 增加了可計(jì)算屬性名,可以在文字形式中使用
[]包裹一個(gè)表達(dá)式來當(dāng)作屬性名:
var prefix = "foo";
var myObject = {
[prefix + "bar"]:"hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
19. 復(fù)制對(duì)象
- 對(duì)于
JSON安全(也就是說可以被序列化為一個(gè)JSON字符串并且可以根據(jù)這個(gè)字符串解析出一個(gè)結(jié)構(gòu)和值完全一樣的對(duì)象)的對(duì)象來說,有一種巧妙的復(fù)制方法:
var newObj = JSON.parse( JSON.stringify( someObj ) );
當(dāng)然,這種方法需要保證對(duì)象是 JSON 安全的,所以只適用于部分情況。
- ES6 定義了
Object.assign(..)方法來實(shí)現(xiàn)淺復(fù)制。
Object.assign(..)方法的第一個(gè)參數(shù)是目標(biāo)對(duì)象,之后還可以跟一個(gè)或多個(gè)源對(duì)象。它會(huì)遍歷一個(gè)或多個(gè)源對(duì)象的所有可枚舉(enumerable,參見下面的代碼)的自有鍵(owned key)并把它們復(fù)制(使用 = 操作符賦值)到目標(biāo)對(duì)象,最后返回目標(biāo)對(duì)象,就像這樣:
var newObj = Object.assign( {}, myObject );
newObj.a; // 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true
由于 Object.assign(..) 就是使用 = 操作符來賦值,所
以源對(duì)象屬性的一些特性(比如 writable)不會(huì)被復(fù)制到目標(biāo)對(duì)象。
20. 屬性描述符
- 在 ES5 之前,JavaScript 語言本身并沒有提供可以直接檢測(cè)屬性特性的方法,比如判斷屬性是否是只讀。但是從 ES5 開始,所有的屬性都具備了屬性描述符。
var myObject = {
a:2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }
這個(gè)普通的對(duì)象屬性對(duì)應(yīng)的屬性描述符(也被稱為“數(shù)據(jù)描述符”,因?yàn)樗槐4嬉粋€(gè)數(shù)據(jù)值)可不僅僅只是一個(gè) 2。它還包含另外三個(gè)特性:
writable(可寫)、enumerable(可枚舉)和configurable(可配置)。
- 可以使用
Object.defineProperty(..)來添加一個(gè)新屬性或者修改一個(gè)已有屬性(如果它是configurable)并對(duì)特性進(jìn)行設(shè)置。
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
} );
myObject.a; // 2
我們使用
defineProperty(..)給myObject添加了一個(gè)普通的屬性并顯式指定了一些特性。然而,一般來說你不會(huì)使用這種方式,除非你想修改屬性描述符。
-
writable決定是否可以修改屬性的值。
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // 不可寫!
configurable: true,
enumerable: true
} );
myObject.a = 3;
myObject.a; // 2
如你所見,我們對(duì)于屬性值的修改靜默失?。?code>silently failed)了。如果在嚴(yán)格模式下,這種方法會(huì)出錯(cuò):
"use strict";
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // 不可寫!
configurable: true,
enumerable: true
} );
myObject.a = 3; // TypeError
TypeError 錯(cuò)誤表示我們無法修改一個(gè)不可寫的屬性。
-
configurable只要屬性是可配置的,就可以使用defineProperty(..)方法來修改屬性描述符:
var myObject = {
a:2
};
myObject.a = 3;
myObject.a; // 3
Object.defineProperty( myObject, "a", {
value: 4,
writable: true,
configurable: false, // 不可配置!
enumerable: true
} );
myObject.a; // 4
myObject.a = 5;
myObject.a; // 5
Object.defineProperty( myObject, "a", {
value: 6,
writable: true,
configurable: true,
enumerable: true
} ); // TypeError
最后一個(gè)
defineProperty(..)會(huì)產(chǎn)生一個(gè)TypeError錯(cuò)誤,不管是不是處于嚴(yán)格模式,嘗試修改一個(gè)不可配置的屬性描述符都會(huì)出錯(cuò)。注意:如你所見,把configurable修改成false是單向操作,無法撤銷!
要注意有一個(gè)小小的例外:即便屬性是configurable:false,我們還是可以把writable的狀態(tài)由true改為false,但是無法由false改為true。
21. 不變性
- 對(duì)象常量(不可修改、重定義或者刪除)
var myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
} );
- 禁止擴(kuò)展(禁 止 一 個(gè) 對(duì) 象 添 加 新 屬 性 并 且 保 留 已 有 屬 性)
var myObject = {
a:2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
在非嚴(yán)格模式下,創(chuàng)建屬性 b 會(huì)靜默失敗。在嚴(yán)格模式下,將會(huì)拋出 TypeError 錯(cuò)誤。
- 密封
Object.seal(..)會(huì)創(chuàng)建一個(gè)“密封”的對(duì)象,這個(gè)方法實(shí)際上會(huì)在一個(gè)現(xiàn)有對(duì)象上調(diào)用Object.preventExtensions(..)并把所有現(xiàn)有屬性標(biāo)記為configurable:false。所以,密封之后不僅不能添加新屬性,也不能重新配置或者刪除任何現(xiàn)有屬性(雖然可以修改屬性的值)
- 凍結(jié)
Object.freeze(..)會(huì)創(chuàng)建一個(gè)凍結(jié)對(duì)象,這個(gè)方法實(shí)際上會(huì)在一個(gè)現(xiàn)有對(duì)象上調(diào)用Object.seal(..)并把所有“數(shù)據(jù)訪問”屬性標(biāo)記為writable:false,這樣就無法修改它們的值。
這個(gè)方法是你可以應(yīng)用在對(duì)象上的級(jí)別最高的不可變性,它會(huì)禁止對(duì)于對(duì)象本身及其任意直接屬性的修改(不過就像我們之前說過的,這個(gè)對(duì)象引用的其他對(duì)象是不受影響的)。
你可以“深度凍結(jié)”一個(gè)對(duì)象,具體方法為,首先在這個(gè)對(duì)象上調(diào)用
Object.freeze(..),然后遍歷它引用的所有對(duì)象并在這些對(duì)象上調(diào)用Object.freeze(..)。但是一定要小心,因
為這樣做有可能會(huì)在無意中凍結(jié)其他(共享)對(duì)象。
22. 存在性
前面我們介紹過,如
myObject.a的屬性訪問返回值可能是undefined,但是這個(gè)值有可能是屬性中存儲(chǔ)的undefined,也可能是因?yàn)閷傩圆淮嬖谒苑祷?undefined。那么如何區(qū)分這兩種情況呢?
- 可以在不訪問屬性值的情況下判斷對(duì)象中是否存在這個(gè)屬性:
var myObject = {
a:2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
in 操作符會(huì)檢查屬性是否在對(duì)象及其 [[Prototype]] 原型鏈中相比之下,
hasOwnProperty(..)只會(huì)檢查屬性是否在myObject對(duì)象中,不會(huì)檢查 [[Prototype]] 鏈。
所有的普通對(duì)象都可以通過對(duì)于
Object.prototype的 委 托 來訪問hasOwnProperty(..),但是有的對(duì)象可能沒有連接到Object.prototype(通過Object.create(null)來創(chuàng)建——參見第 5 章)。在這種情況下,形如myObejct.hasOwnProperty(..)就會(huì)失敗。
這 時(shí) 可 以 使 用 一 種 更 加 強(qiáng) 硬 的 方 法 來 進(jìn) 行 判 斷:Object.prototype.hasOwnProperty.call(myObject,"a"),它借用基礎(chǔ)的hasOwnProperty(..)方法并把它顯式綁定到 myObject 上。
看起來
in操作符可以檢查容器內(nèi)是否有某個(gè)值,但是它實(shí)際上檢查的是某個(gè)屬性名是否存在。對(duì)于數(shù)組來說這個(gè)區(qū)別非常重要,4 in [2, 4, 6]的結(jié)果并不是你期待的True,因?yàn)?[2, 4, 6]這個(gè)數(shù)組中包含的屬性名是0、1、2,沒有4。
- 枚舉(什么是“可枚舉性”)
var myObject = { };
Object.defineProperty(
myObject,
"a",
// 讓 a 像普通屬性一樣可以枚舉
{ enumerable: true, value: 2 }
);
Object.defineProperty(
myObject,
"b",
// 讓 b 不可枚舉
{ enumerable: false, value: 3 }
);
myObject.b; // 3
("b" in myObject); // true
myObject.hasOwnProperty( "b" ); // true
// .......
for (var k in myObject) {
console.log( k, myObject[k] );
}
// "a" 2
可以看到,
myObject.b確實(shí)存在并且有訪問值,但是卻不會(huì)出現(xiàn)在for..in循環(huán)中(盡管可以通過in操作符來判斷是否存在)。原因是“可枚舉”就相當(dāng)于“可以出現(xiàn)在對(duì)象屬性的遍歷中”。
在數(shù)組上應(yīng)用
for..in循環(huán)有時(shí)會(huì)產(chǎn)生出人意料的結(jié)果,因?yàn)檫@種枚舉不僅會(huì)包含所有數(shù)值索引,還會(huì)包含所有可枚舉屬性。最好只在對(duì)象上應(yīng)用for..in循環(huán),如果要遍歷數(shù)組就使用傳統(tǒng)的for循環(huán)來遍歷數(shù)值索引。
- 也可以通過另一種方式來區(qū)分屬性是否可枚舉:
var myObject = { };
Object.defineProperty(
myObject,
"a",
// 讓 a 像普通屬性一樣可以枚舉
{ enumerable: true, value: 2 }
);
Object.defineProperty(
myObject,
"b",
// 讓 b 不可枚舉
{ enumerable: false, value: 3 }
);
myObject.propertyIsEnumerable( "a" ); // true
myObject.propertyIsEnumerable( "b" ); // false
Object.keys( myObject ); // ["a"]
Object.getOwnPropertyNames( myObject ); // ["a", "b"]
propertyIsEnumerable(..)會(huì)檢查給定的屬性名是否直接存在于對(duì)象中(而不是在原型鏈上)并且滿足enumerable:true。
Object.keys(..)會(huì)返回一個(gè)數(shù)組,包含所有可枚舉屬性
Object.getOwnPropertyNames(..)會(huì)返回一個(gè)數(shù)組,包含所有屬性,無論它們是否可枚舉。
in和hasOwnProperty(..)的區(qū)別在于是否查找 [[Prototype]] 鏈,然而,Object.keys(..)和Object.getOwnPropertyNames(..)都只會(huì)查找對(duì)象直接包含的屬性。
(目前)并沒有內(nèi)置的方法可以獲取
in操作符使用的屬性列表(對(duì)象本身的屬性以及 [[Prototype]] 鏈中的所有屬性,參見第 5 章)。不過你可以遞歸遍歷某個(gè)對(duì)象的整條 [[Prototype]] 鏈并保存每一層中使用Object.keys(..)得到的屬性列表——只包含可枚舉屬性。
23. 遍歷
- 對(duì)于數(shù)值索引的數(shù)組來說,可以使用標(biāo)準(zhǔn)的 for 循環(huán)來遍歷值:
var myArray = [1, 2, 3];
for (var i = 0; i < myArray.length; i++) {
console.log( myArray[i] );
}
// 1 2 3
這實(shí)際上并不是在遍歷值,而是遍歷下標(biāo)來指向值,如
myArray[i]。
-
ES5中增加了一些數(shù)組的輔助迭代器,包括forEach(..)、every(..)和some(..)。每種輔助迭代器都可以接受一個(gè)回調(diào)函數(shù)并把它應(yīng)用到數(shù)組的每個(gè)元素上,唯一的區(qū)別就是它們對(duì)于回調(diào)函數(shù)返回值的處理方式不同。-
forEach(..)會(huì)遍歷數(shù)組中的所有值并忽略回調(diào)函數(shù)的返回值 -
every(..)會(huì)一直運(yùn)行直到回調(diào)函數(shù)返回false(或者“假”值) -
some(..)會(huì)一直運(yùn)行直到回調(diào)函數(shù)返回true(或者
“真”值)
every(..)和some(..)中特殊的返回值和普通for循環(huán)中的break語句類似,它們會(huì)提前終止遍歷使用
for..in遍歷對(duì)象是無法直接獲取屬性值的,因?yàn)樗鼘?shí)際上遍歷的是對(duì)象中的所有可枚舉屬性,你需要手動(dòng)獲取屬性值。遍歷數(shù)組下標(biāo)時(shí)采用的是數(shù)字順序(
for循環(huán)或者其他迭代器),但是遍歷對(duì)象屬性時(shí)的順序是不確定的,在不同的JavaScript引擎中可能不一樣。因此,在不同的環(huán)境中需要保證一致性時(shí),一定不要相信任何觀察到的順序,它們是不可靠的。 -
ES6增加了一種用來遍歷數(shù)組的for..of循環(huán)語法(如果對(duì)象本身定義了迭代器的話也可以遍歷對(duì)象):
var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
console.log( v );
}
// 1
// 2
// 3
for..of循環(huán)首先會(huì)向被訪問對(duì)象請(qǐng)求一個(gè)迭代器對(duì)象,然后通過調(diào)用迭代器對(duì)象的next()方法來遍歷所有返回值。數(shù)組有內(nèi)置的
@@iterator,因此for..of可以直接應(yīng)用在數(shù)組上。我們使用內(nèi)置的@@iterator來手動(dòng)遍歷數(shù)組,看看它是怎么工作的:
var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator]();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { done:true }
我們使用
ES6中的符號(hào)Symbol.iterator來獲取對(duì)象的@@iterator內(nèi)部屬性。之前我們簡(jiǎn)單介紹過符(Symbol,參見 3.3.1 節(jié)),跟這里的原理是相同的。引用類似iterator的特殊屬性時(shí)要使用符號(hào)名,而不是符號(hào)包含的值。此外,雖然看起來很像一個(gè)對(duì)象,但是@@iterator本身并不是一個(gè)迭代器對(duì)象,而是一個(gè)返回迭代器對(duì)象的函數(shù)——這點(diǎn)非常精妙并且非常重要。
如你所見,調(diào)用迭代器的
next()方法會(huì)返回形式為{ value: .. , done: .. }的值,value是當(dāng)前的遍歷值,done是一個(gè)布爾值,表示是否還有可以遍歷的值。
注意,和值“3”一起返回的是done:false,乍一看好像很奇怪,你必須再調(diào)用一次next()才能得到done:true,從而確定完成遍歷。這個(gè)機(jī)制和ES6中發(fā)生器函數(shù)的語義相關(guān),不過已經(jīng)超出了我們的討論范圍。
和數(shù)組不同,普通的對(duì)象沒有內(nèi)置的
@@iterator,所以無法自動(dòng)完成for..of遍歷。之所以要這樣做,有許多非常復(fù)雜的原因,不過簡(jiǎn)單來說,這樣做是為了避免影響未來的對(duì)象類型。
當(dāng)然,你可以給任何想遍歷的對(duì)象定義 @@iterator,舉例來說:
var myObject = {
a: 2,
b: 3
};
Object.defineProperty( myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys( o );
return {
next: function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
};
}
};
}
} );
// 手動(dòng)遍歷 myObject
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefined, done:true }
// 用 for..of 遍歷 myObject
for (var v of myObject) {
console.log( v );
}
// 2
// 3
我們使用
Object.defineProperty(..)定義了我們自己的@@iterator(主要是為了讓它不可枚舉),不過注意,我們把符號(hào)當(dāng)作可計(jì)算屬性名。此外,也可以直接在定義對(duì)象時(shí)進(jìn)行聲明,比如var myObject = { a:2, b:3, [Symbol.iterator]: function() { /* .. */ } }。
- 一個(gè)“無限”迭代器,它永遠(yuǎn)不會(huì)“結(jié)束”并且總會(huì)返回一個(gè)新值(隨機(jī)數(shù))
var randoms = {
[Symbol.iterator]: function() {
return {
next: function() {
return { value: Math.random() };
}
};
}
};
var randoms_pool = [];
for (var n of randoms) {
randoms_pool.push( n );
// 防止無限運(yùn)行!
if (randoms_pool.length === 100) break;
}
24. 混合對(duì)象 “類” 小結(jié)
類是一種設(shè)計(jì)模式。許多語言提供了對(duì)于面向類軟件設(shè)計(jì)的原生語法。
JavaScript也有類似的語法,但是和其他語言中的類完全不同。
類意味著復(fù)制。
傳統(tǒng)的類被實(shí)例化時(shí),它的行為會(huì)被復(fù)制到實(shí)例中。類被繼承時(shí),行為也會(huì)被復(fù)制到子類中。
多態(tài)(在繼承鏈的不同層次名稱相同但是功能不同的函數(shù))看起來似乎是從子類引用父類,但是本質(zhì)上引用的其實(shí)是復(fù)制的結(jié)果。
JavaScript并不會(huì)(像類那樣)自動(dòng)創(chuàng)建對(duì)象的副本。
混入模式(無論顯式還是隱式)可以用來模擬類的復(fù)制行為,但是通常會(huì)產(chǎn)生丑陋并且脆弱的語法,比如顯式偽多態(tài)(
OtherObj.methodName.call(this, ...)),這會(huì)讓代碼更加難懂并且難以維護(hù)。
此外,顯式混入實(shí)際上無法完全模擬類的復(fù)制行為,因?yàn)閷?duì)象(和函數(shù)!別忘了函數(shù)也是對(duì)象)只能復(fù)制引用,無法復(fù)制被引用的對(duì)象或者函數(shù)本身。忽視這一點(diǎn)會(huì)導(dǎo)致許多問題。
25. 原型 -- [[Prototype]]
使用
for..in遍歷對(duì)象時(shí)原理和查找[[Prototype]]鏈類似,任何可以通過原型鏈訪問到(并且是enumerable,參見第 3 章)的屬性都會(huì)被枚舉。使用in操作符來檢查屬性在對(duì)象中是否存在時(shí),同樣會(huì)查找對(duì)象的整條原型鏈(無論屬性是否可枚舉):
var anotherObject = {
a:2
};
// 創(chuàng)建一個(gè)關(guān)聯(lián)到 anotherObject 的對(duì)象
var myObject = Object.create( anotherObject );
myObject.a; // 2
26. 原型 -- Object.prototype
所有普通的
[[Prototype]]鏈最終都會(huì)指向內(nèi)置的Object.prototype。由于所有的“普通”(內(nèi)置,不是特定主機(jī)的擴(kuò)展)對(duì)象都“源于”(或者說把[[Prototype]]鏈的頂端設(shè)置為)這個(gè)Object.prototype對(duì)象,所以它包含JavaScript中許多通用的功能。
有 些 功 能 你 應(yīng) 該 已 經(jīng) 很 熟 悉 了, 比 如 說.toString()和.valueOf(), 第 3 章 還 介 紹過.hasOwnProperty(..)。稍后我們還會(huì)介紹.isPrototypeOf(..),這個(gè)你可能不太熟悉。
27. 原型 - “類”函數(shù)
new Foo()會(huì)生成一個(gè)新對(duì)象(我們稱之為a),這個(gè)新對(duì)象的內(nèi)部鏈接[[Prototype]]關(guān)聯(lián)的是Foo.prototype對(duì)象。
最后我們得到了兩個(gè)對(duì)象,它們之間互相關(guān)聯(lián),就是這樣。我們并沒有初始化一個(gè)類,實(shí)際上我們并沒有從“類”中復(fù)制任何行為到一個(gè)對(duì)象中,只是讓兩個(gè)對(duì)象互相關(guān)聯(lián)。
實(shí)際上,絕大多數(shù)JavaScript開發(fā)者不知道的秘密是,new Foo()這個(gè)函數(shù)調(diào)用實(shí)際上并沒有直接創(chuàng)建關(guān)聯(lián),這個(gè)關(guān)聯(lián)只是一個(gè)意外的副作用。new Foo()只是間接完成了我們的目標(biāo):一個(gè)關(guān)聯(lián)到其他對(duì)象的新對(duì)象。
28. 技術(shù)
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 創(chuàng)建一個(gè)新原型對(duì)象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
Object(..)并沒有“構(gòu)造”a1,對(duì)吧?看起來應(yīng)該是Foo()“構(gòu)造”了它。大部分開發(fā)者都認(rèn)為是Foo()執(zhí)行了構(gòu)造工作,但是問題在于,如果你認(rèn)為 “constructor” 表示“由……構(gòu)造”的話,a1.constructor應(yīng)該是Foo,但是它并不是Foo!
到底怎么回事?a1并沒有.constructor屬性,所以它會(huì)委托[[Prototype]]鏈上的Foo.prototype。但是這個(gè)對(duì)象也沒有.constructor屬性(不過默認(rèn)的Foo.prototype對(duì)象有這個(gè)屬性!),所以它會(huì)繼續(xù)委托,這次會(huì)委托給委托鏈頂端的Object.prototype。這個(gè)對(duì)象有.constructor屬性,指向內(nèi)置的Object(..)函數(shù)。
- 當(dāng)然,你可以給
Foo.prototype添加一個(gè).constructor屬性,不過這需要手動(dòng)添加一個(gè)符合正常行為的不可枚舉(參見第 3 章)屬性。
舉例來說:
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 創(chuàng)建一個(gè)新原型對(duì)象
// 需要在 Foo.prototype 上“修復(fù)”丟失的 .constructor 屬性
// 新對(duì)象屬性起到 Foo.prototype 的作用
// 關(guān)于 defineProperty(..),參見第 3 章
Object.defineProperty( Foo.prototype, "constructor" , {
enumerable: false,
writable: true,
configurable: true,
value: Foo // 讓 .constructor 指向 Foo
} );
修復(fù)
.constructor需要很多手動(dòng)操作。所有這些工作都是源于把 “constructor” 錯(cuò)誤地理解為“由……構(gòu)造”,這個(gè)誤解的代價(jià)實(shí)在太高了。
實(shí)際上,對(duì)象的
.constructor會(huì)默認(rèn)指向一個(gè)函數(shù),這個(gè)函數(shù)可以通過對(duì)象的.prototype引用?!癱onstructor” 和 “prototype” 這兩個(gè)詞本身的含義可能適用也可能不適用。最好的辦法是記住這一點(diǎn) “constructor 并不表示被構(gòu)造”。
.constructor并不是一個(gè)不可變屬性。它是不可枚舉(參見上面的代碼)的,但是它的值是可寫的(可以被修改)。此外,你可以給任意[[Prototype]]鏈中的任意對(duì)象添加一個(gè)名為constructor的屬性或者對(duì)其進(jìn)行修改,你可以任意對(duì)其賦值。
- ES6 添加了輔助函數(shù)
Object.setPrototypeOf(..),可以用標(biāo)準(zhǔn)并且可靠的方法來修
改關(guān)聯(lián)。
我們來對(duì)比一下兩種把Bar.prototype關(guān)聯(lián)到Foo.prototype的方法:
// ES6 之前需要拋棄默認(rèn)的 Bar.prototype
Bar.ptototype = Object.create( Foo.prototype );
// ES6 開始可以直接修改現(xiàn)有的 Bar.prototype
Object.setPrototypeOf( Bar.prototype, Foo.prototype );
如果忽略掉
Object.create(..)方法帶來的輕微性能損失(拋棄的對(duì)象需要進(jìn)行垃圾回收),它實(shí)際上比ES6及其之后的方法更短而且可讀性更高。不過無論如何,這是兩種完全不同的語法。
29. 原型 - 小結(jié)
如果要訪問對(duì)象中并不存在的一個(gè)屬性,
[[Get]]操作(參見第 3 章)就會(huì)查找對(duì)象內(nèi)部[[Prototype]]關(guān)聯(lián)的對(duì)象。這個(gè)關(guān)聯(lián)關(guān)系實(shí)際上定義了一條“原型鏈”(有點(diǎn)像嵌套的作用域鏈),在查找屬性時(shí)會(huì)對(duì)它進(jìn)行遍歷。
所有普通對(duì)象都有內(nèi)置的
Object.prototype,指向原型鏈的頂端(比如說全局作用域),如果在原型鏈中找不到指定的屬性就會(huì)停止。toString()、valueOf()和其他一些通用的功能都存在于Object.prototype對(duì)象上,因此語言中所有的對(duì)象都可以使用它們。
關(guān)聯(lián)兩個(gè)對(duì)象最常用的方法是使用
new關(guān)鍵詞進(jìn)行函數(shù)調(diào)用,在調(diào)用的 4 個(gè)步驟(第 2章)中會(huì)創(chuàng)建一個(gè)關(guān)聯(lián)其他對(duì)象的新對(duì)象。4 個(gè)步驟:
創(chuàng)建(或者說構(gòu)造)一個(gè)全新的對(duì)象。
這個(gè)新對(duì)象會(huì)被執(zhí)行 [[ 原型 ]] 連接。
這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的 this。
如果函數(shù)沒有返回其他對(duì)象,那么 new 表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。
使用
new調(diào)用函數(shù)時(shí)會(huì)把新對(duì)象的.prototype屬性關(guān)聯(lián)到“其他對(duì)象”。帶new的函數(shù)調(diào)用通常被稱為“構(gòu)造函數(shù)調(diào)用”,盡管它們實(shí)際上和傳統(tǒng)面向類語言中的類構(gòu)造函數(shù)不一樣。
雖然這些
JavaScript機(jī)制和傳統(tǒng)面向類語言中的“類初始化”和“類繼承”很相似,但
是JavaScript中的機(jī)制有一個(gè)核心區(qū)別,那就是不會(huì)進(jìn)行復(fù)制,對(duì)象之間是通過內(nèi)部的[[Prototype]]鏈關(guān)聯(lián)的。
出于各種原因,以“繼承”結(jié)尾的術(shù)語(包括“原型繼承”)和其他面向?qū)ο蟮男g(shù)語都無
法幫助你理解JavaScript的真實(shí)機(jī)制(不僅僅是限制我們的思維模式)。
相比之下,“委托”是一個(gè)更合適的術(shù)語,因?yàn)閷?duì)象之間的關(guān)系不是復(fù)制而是委托。
30. 行為委托
- 面向?qū)ο箫L(fēng)格的代碼示例
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.speak();
b2.speak();
子類
Bar繼承了父類Foo,然后生成了b1和b2兩個(gè)實(shí)例。b1委托了Bar.prototype,后者委托了Foo.prototype。
- 對(duì)象關(guān)聯(lián)風(fēng)格的代碼示例
Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();