ECMAScript關(guān)鍵字
delete
do
else
finally
function
in
instanceof
this
throw
try
typeof
var
with
保留字
abstract
debugger
extends
final
goto
implements
native
package
synchronized
throws
transient
volatile
ECMAScript有5種原始類(lèi)型(primitive type),即Undefined、Null、Boolean、Number和String。ECMA-262把術(shù)語(yǔ)類(lèi)型(type)定義為值的一個(gè)集合,每種原始類(lèi)型定義了它包含的值的范圍及其字面量表示形式。
原始值
存儲(chǔ)在棧(stack)中的簡(jiǎn)單數(shù)據(jù)段,也就是說(shuō),它們的值直接存儲(chǔ)在變量訪問(wèn)的位置。
引用值
存儲(chǔ)在堆(heap)中的對(duì)象,也就是說(shuō),存儲(chǔ)在變量處的值是一個(gè)指針(point),指向存儲(chǔ)對(duì)象的內(nèi)存處。
typeof運(yùn)算符
typeof運(yùn)算符有一個(gè)參數(shù),即要檢查的變量或值。例如:
var sTemp = "test string";
alert (typeof sTemp);//輸出"string"
alert (typeof 86);//輸出"number"
對(duì)變量或值調(diào)用typeof運(yùn)算符將返回下列值之一:
undefined -如果變量是Undefined類(lèi)型的
boolean -如果變量是Boolean類(lèi)型的
number -如果變量是Number類(lèi)型的
string -如果變量是String類(lèi)型的
object -如果變量是一種引用類(lèi)型或Null類(lèi)型的
Object對(duì)象具有下列屬性:
constructor
對(duì)創(chuàng)建對(duì)象的函數(shù)的引用(指針)。對(duì)于Object對(duì)象,該指針指向原始的Object()函數(shù)。
Prototype
對(duì)該對(duì)象的對(duì)象原型的引用。對(duì)于所有的對(duì)象,它默認(rèn)返回Object對(duì)象的一個(gè)實(shí)例。
Object對(duì)象還具有幾個(gè)方法:
hasOwnProperty(property)
判斷對(duì)象是否有某個(gè)特定的屬性。必須用字符串指定該屬性。(例如,o.hasOwnProperty("name"))
IsPrototypeOf(object)
判斷該對(duì)象是否為另一個(gè)對(duì)象的原型。
PropertyIsEnumerable
判斷給定的屬性是否可以用for...in語(yǔ)句進(jìn)行枚舉。
ToString()
返回對(duì)象的原始字符串表示。對(duì)于Object對(duì)象,ECMA-262沒(méi)有定義這個(gè)值,所以不同的ECMAScript實(shí)現(xiàn)具有不同的值。
ValueOf()
返回最適合該對(duì)象的原始值。對(duì)于許多對(duì)象,該方法返回的值都與ToString()的返回值相同。
注釋?zhuān)?/b>上面列出的每種屬性和方法都會(huì)被其他對(duì)象覆蓋。
instanceof運(yùn)算符
在使用typeof運(yùn)算符時(shí)采用引用類(lèi)型存儲(chǔ)值會(huì)出現(xiàn)一個(gè)問(wèn)題,無(wú)論引用的是什么類(lèi)型的對(duì)象,它都返回"object"。ECMAScript引入了另一個(gè)Java運(yùn)算符instanceof來(lái)解決這個(gè)問(wèn)題。
instanceof運(yùn)算符與typeof運(yùn)算符相似,用于識(shí)別正在處理的對(duì)象的類(lèi)型。與typeof方法不同的是,instanceof方法要求開(kāi)發(fā)者明確地確認(rèn)對(duì)象為某特定類(lèi)型。例如:
var oStringObject = new String("hello world");
alert(oStringObject instanceof String);//輸出"true"
這段代碼問(wèn)的是“變量oStringObject是否為String對(duì)象的實(shí)例?”oStringObject的確是String對(duì)象的實(shí)例,因此結(jié)果是"true"。盡管不像typeof方法那樣靈活,但是在typeof方法返回"object"的情況下,instanceof方法還是很有用的。
全等號(hào)由三個(gè)等號(hào)表示(===),只有在無(wú)需類(lèi)型轉(zhuǎn)換運(yùn)算數(shù)就相等的情況下,才返回true。
例如:
var sNum = "66";
var iNum = 66;
alert(sNum == iNum);//輸出"true"
alert(sNum === iNum);//輸出"false"
var sNum = "66";
var iNum = 66;
alert(sNum != iNum);//輸出"false"
alert(sNum !== iNum);//輸出"true"
for (sProp in window) {
alert(sProp);
}
start : i = 5;
在這個(gè)例子中,標(biāo)簽start可以被之后的break或continue語(yǔ)句引用。
with語(yǔ)句用于設(shè)置代碼在特定對(duì)象中的作用域。
它的語(yǔ)法:
with (expression)statement
例如:
var sMessage = "hello";
with(sMessage) {
alert(toUpperCase());//輸出"HELLO"
}
arguments對(duì)象
在函數(shù)代碼中,使用特殊對(duì)象arguments,開(kāi)發(fā)者無(wú)需明確指出參數(shù)名,就能訪問(wèn)它們。
function sayHi() {
if (arguments[0] == "bye") {
return;
}
alert(arguments[0]);
}
檢測(cè)參數(shù)個(gè)數(shù)
還可以用arguments對(duì)象檢測(cè)函數(shù)的參數(shù)個(gè)數(shù),引用屬性arguments.length即可。
下面的代碼將輸出每次調(diào)用函數(shù)使用的參數(shù)個(gè)數(shù):
function howManyArgs() {
alert(arguments.length);
}
howManyArgs("string", 45);
howManyArgs();
howManyArgs(12);
上面這段代碼將依次顯示"2"、"0"和"1"。
模擬函數(shù)重載
用arguments對(duì)象判斷傳遞給函數(shù)的參數(shù)個(gè)數(shù),即可模擬函數(shù)重載:
function doAdd() {
if(arguments.length == 1) {
alert(arguments[0] + 5);
} else if(arguments.length == 2) {
alert(arguments[0] + arguments[1]);
}
}
doAdd(10);//輸出"15"
doAdd(40, 20);//輸出"60"
Function對(duì)象(類(lèi))
ECMAScript最令人感興趣的可能莫過(guò)于函數(shù)實(shí)際上是功能完整的對(duì)象。
Function類(lèi)可以表示開(kāi)發(fā)者定義的任何函數(shù)。
用Function類(lèi)直接創(chuàng)建函數(shù)的語(yǔ)法如下:
var function_name = new function(arg1,arg2, ...,argN,function_body)
function sayHi(sName, sMessage) {
alert("Hello " + sName + sMessage);
}
還可以這樣定義它:
var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");
function doAdd(iNum) {
alert(iNum + 20);
}
function doAdd(iNum) {
alert(iNum + 10);
}
doAdd(10);//輸出"20"
如你所知,第二個(gè)函數(shù)重載了第一個(gè)函數(shù),使doAdd(10)輸出了"20",而不是"30"。
如果以下面的形式重寫(xiě)該代碼塊,這個(gè)概念就清楚了:
var doAdd = new Function("iNum", "alert(iNum + 20)");
var doAdd = new Function("iNum", "alert(iNum + 10)");
doAdd(10);
請(qǐng)觀察這段代碼,很顯然,doAdd的值被改成了指向不同對(duì)象的指針。函數(shù)名只是指向函數(shù)對(duì)象的引用值,行為就像其他對(duì)象一樣。甚至可以使兩個(gè)變量指向同一個(gè)函數(shù):
var doAdd = new Function("iNum", "alert(iNum + 10)");
var alsodoAdd = doAdd;
doAdd(10);//輸出"20"
alsodoAdd(10);//輸出"20"
function callAnotherFunc(fnFunction, vArgument) {
fnFunction(vArgument);
}
var doAdd = new Function("iNum", "alert(iNum + 10)");
callAnotherFunc(doAdd, 10);//輸出"20"
Function對(duì)象的length屬性
如前所述,函數(shù)屬于引用類(lèi)型,所以它們也有屬性和方法。
ECMAScript定義的屬性length聲明了函數(shù)期望的參數(shù)個(gè)數(shù)。例如:
function doAdd(iNum) {
alert(iNum + 10);
}
function sayHi() {
alert("Hi");
}
alert(doAdd.length);//輸出"1"
alert(sayHi.length);//輸出"0"
函數(shù)doAdd()定義了一個(gè)參數(shù),因此它的length是1;sayHi()沒(méi)有定義參數(shù),所以length是0。
記住,無(wú)論定義了幾個(gè)參數(shù),ECMAScript可以接受任意多個(gè)參數(shù)(最多25個(gè)),這一點(diǎn)在《函數(shù)概述》這一章中講解過(guò)。屬性length只是為查看默認(rèn)情況下預(yù)期的參數(shù)個(gè)數(shù)提供了一種簡(jiǎn)便方式。
Function對(duì)象的方法
Function對(duì)象也有與所有對(duì)象共享的valueOf()方法和toString()方法。這兩個(gè)方法返回的都是函數(shù)的源代碼,在調(diào)試時(shí)尤其有用。例如:
function doAdd(iNum) {
alert(iNum + 10);
}
document.write(doAdd.toString());//function doAdd(iNum) { alert(iNum + 10); }
閉包,指的是詞法表示包括不被計(jì)算的變量的函數(shù),也就是說(shuō),函數(shù)可以使用函數(shù)之外定義的變量。
簡(jiǎn)單的閉包實(shí)例
在ECMAScript中使用全局變量是一個(gè)簡(jiǎn)單的閉包實(shí)例。請(qǐng)思考下面這段代碼:
var sMessage = "hello world";
function sayHelloWorld() {
alert(sMessage);
}
sayHelloWorld();
復(fù)雜的閉包實(shí)例
在一個(gè)函數(shù)中定義另一個(gè)會(huì)使閉包變得更加復(fù)雜。例如:
var iBaseNum = 10;
function addNum(iNum1, iNum2) {
function doAdd() {
return iNum1 + iNum2 + iBaseNum;
}
return doAdd();
}
面向?qū)ο笳Z(yǔ)言的要求
一種面向?qū)ο笳Z(yǔ)言需要向開(kāi)發(fā)者提供四種基本能力:
封裝-把相關(guān)的信息(無(wú)論數(shù)據(jù)或方法)存儲(chǔ)在對(duì)象中的能力
聚集-把一個(gè)對(duì)象存儲(chǔ)在另一個(gè)對(duì)象內(nèi)的能力
繼承-由另一個(gè)類(lèi)(或多個(gè)類(lèi))得來(lái)類(lèi)的屬性和方法的能力
多態(tài)-編寫(xiě)能以多種方法運(yùn)行的函數(shù)或方法的能力
對(duì)象的構(gòu)成
在ECMAScript中,對(duì)象由特性(attribute)構(gòu)成,特性可以是原始值,也可以是引用值。如果特性存放的是函數(shù),它將被看作對(duì)象的方法(method),否則該特性被看作對(duì)象的屬性(property)。
聲明和實(shí)例化
對(duì)象的創(chuàng)建方式是用關(guān)鍵字new后面跟上實(shí)例化的類(lèi)的名字:
var oObject = new Object();
var oStringObject = new String();
第一行代碼創(chuàng)建了Object類(lèi)的一個(gè)實(shí)例,并把它存儲(chǔ)到變量oObject中。第二行代碼創(chuàng)建了String類(lèi)的一個(gè)實(shí)例,把它存儲(chǔ)在變量oStringObject中。如果構(gòu)造函數(shù)無(wú)參數(shù),括號(hào)則不是必需的。因此可以采用下面的形式重寫(xiě)上面的兩行代碼:
var oObject = new Object;
var oStringObject = new String;
對(duì)象廢除
ECMAScript擁有無(wú)用存儲(chǔ)單元收集程序(garbage collection routine),意味著不必專(zhuān)門(mén)銷(xiāo)毀對(duì)象來(lái)釋放內(nèi)存。當(dāng)再?zèng)]有對(duì)對(duì)象的引用時(shí),稱(chēng)該對(duì)象被廢除(dereference)了。運(yùn)行無(wú)用存儲(chǔ)單元收集程序時(shí),所有廢除的對(duì)象都被銷(xiāo)毀。每當(dāng)函數(shù)執(zhí)行完它的代碼,無(wú)用存儲(chǔ)單元收集程序都會(huì)運(yùn)行,釋放所有的局部變量,還有在一些其他不可預(yù)知的情況下,無(wú)用存儲(chǔ)單元收集程序也會(huì)運(yùn)行。
把對(duì)象的所有引用都設(shè)置為null,可以強(qiáng)制性地廢除對(duì)象。例如:
var oObject = new Object;
// do something with the object here
oObject = null;
早綁定和晚綁定
所謂綁定(binding),即把對(duì)象的接口與對(duì)象實(shí)例結(jié)合在一起的方法。
早綁定(early binding)是指在實(shí)例化對(duì)象之前定義它的屬性和方法,這樣編譯器或解釋程序就能夠提前轉(zhuǎn)換機(jī)器代碼。在Java和Visual Basic這樣的語(yǔ)言中,有了早綁定,就可以在開(kāi)發(fā)環(huán)境中使用IntelliSense(即給開(kāi)發(fā)者提供對(duì)象中屬性和方法列表的功能)。ECMAScript不是強(qiáng)類(lèi)型語(yǔ)言,所以不支持早綁定。
另一方面,晚綁定(late binding)指的是編譯器或解釋程序在運(yùn)行前,不知道對(duì)象的類(lèi)型。使用晚綁定,無(wú)需檢查對(duì)象的類(lèi)型,只需檢查對(duì)象是否支持屬性和方法即可。ECMAScript中的所有變量都采用晚綁定方法。這樣就允許執(zhí)行大量的對(duì)象操作,而無(wú)任何懲罰。
--------------------------------
一般來(lái)說(shuō),可以創(chuàng)建并使用的對(duì)象有三種:本地對(duì)象、內(nèi)置對(duì)象和宿主對(duì)象。
本地對(duì)象
ECMA-262把本地對(duì)象(native object)定義為“獨(dú)立于宿主環(huán)境的ECMAScript實(shí)現(xiàn)提供的對(duì)象”。簡(jiǎn)單來(lái)說(shuō),本地對(duì)象就是ECMA-262定義的類(lèi)(引用類(lèi)型)。它們包括:
Object
Function
Array
String
Boolean
Number
Date
RegExp
Error
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
--------------
內(nèi)置對(duì)象
ECMA-262把內(nèi)置對(duì)象(built-in object)定義為“由ECMAScript實(shí)現(xiàn)提供的、獨(dú)立于宿主環(huán)境的所有對(duì)象,在ECMAScript程序開(kāi)始執(zhí)行時(shí)出現(xiàn)”。這意味著開(kāi)發(fā)者不必明確實(shí)例化內(nèi)置對(duì)象,它已被實(shí)例化了。ECMA-262只定義了兩個(gè)內(nèi)置對(duì)象,即Global和Math(它們也是本地對(duì)象,根據(jù)定義,每個(gè)內(nèi)置對(duì)象都是本地對(duì)象)。
相關(guān)頁(yè)面
JavaScript參考手冊(cè):Global對(duì)象
JavaScript參考手冊(cè):Math對(duì)象
--------------------
宿主對(duì)象
所有非本地對(duì)象都是宿主對(duì)象(host object),即由ECMAScript實(shí)現(xiàn)的宿主環(huán)境提供的對(duì)象。
所有BOM和DOM對(duì)象都是宿主對(duì)象。
相關(guān)頁(yè)面
JavaScript高級(jí)教程:JavaScript實(shí)現(xiàn)
W3School參考手冊(cè):JavaScript參考手冊(cè)
W3School教程:HTML DOM教程
-------------------------------
作用域指的是變量的適用范圍。
公用、私有和受保護(hù)作用域
概念
在傳統(tǒng)的面向?qū)ο蟪绦蛟O(shè)計(jì)中,主要關(guān)注于公用和私有作用域。公用作用域中的對(duì)象屬性可以從對(duì)象外部訪問(wèn),即開(kāi)發(fā)者創(chuàng)建對(duì)象的實(shí)例后,就可使用它的公用屬性。而私有作用域中的屬性只能在對(duì)象內(nèi)部訪問(wèn),即對(duì)于外部世界來(lái)說(shuō),這些屬性并不存在。這意味著如果類(lèi)定義了私有屬性和方法,則它的子類(lèi)也不能訪問(wèn)這些屬性和方法。
受保護(hù)作用域也是用于定義私有的屬性和方法,只是這些屬性和方法還能被其子類(lèi)訪問(wèn)。
ECMAScript只有公用作用域
對(duì)ECMAScript討論上面這些作用域幾乎毫無(wú)意義,因?yàn)镋CMAScript中只存在一種作用域-公用作用域。ECMAScript中的所有對(duì)象的所有屬性和方法都是公用的。因此,定義自己的類(lèi)和對(duì)象時(shí),必須格外小心。記住,所有屬性和方法默認(rèn)都是公用的!
建議性的解決方法
許多開(kāi)發(fā)者都在網(wǎng)上提出了有效的屬性作用域模式,解決了ECMAScript的這種問(wèn)題。
由于缺少私有作用域,開(kāi)發(fā)者確定了一個(gè)規(guī)約,說(shuō)明哪些屬性和方法應(yīng)該被看做私有的。這種規(guī)約規(guī)定在屬性前后加下劃線:
obj._color_ = "blue";
這段代碼中,屬性color是私有的。注意,下劃線并不改變屬性是公用屬性的事實(shí),它只是告訴其他開(kāi)發(fā)者,應(yīng)該把該屬性看作私有的。
有些開(kāi)發(fā)者還喜歡用單下劃線說(shuō)明私有成員,例如:obj._color。
靜態(tài)作用域
靜態(tài)作用域定義的屬性和方法任何時(shí)候都能從同一位置訪問(wèn)。在Java中,類(lèi)可具有屬性和方法,無(wú)需實(shí)例化該類(lèi)的對(duì)象,即可訪問(wèn)這些屬性和方法,例如java.net.URLEncoder類(lèi),它的函數(shù)encode()就是靜態(tài)方法。
ECMAScript沒(méi)有靜態(tài)作用域
嚴(yán)格來(lái)說(shuō),ECMAScript并沒(méi)有靜態(tài)作用域。不過(guò),它可以給構(gòu)造函數(shù)提供屬性和方法。還記得嗎,構(gòu)造函數(shù)只是函數(shù)。函數(shù)是對(duì)象,對(duì)象可以有屬性和方法。例如:
function sayHello() {
alert("hello");
}
sayHello.alternate = function() {
alert("hi");
}
sayHello();//輸出"hello"
sayHello.alternate();//輸出"hi"
-------------------------------------
this的功能
在ECMAScript中,要掌握的最重要的概念之一是關(guān)鍵字this的用法,它用在對(duì)象的方法中。關(guān)鍵字this總是指向調(diào)用該方法的對(duì)象,例如:
var oCar = new Object;
oCar.color = "red";
oCar.showColor = function() {
alert(this.color);
};
oCar.showColor();//輸出"red"
---------------------------------
使用this的原因
為什么使用this呢?因?yàn)樵趯?shí)例化對(duì)象時(shí),總是不能確定開(kāi)發(fā)者會(huì)使用什么樣的變量名。使用this,即可在任何多個(gè)地方重用同一個(gè)函數(shù)。請(qǐng)思考下面的例子:
function showColor() {
alert(this.color);
};
var oCar1 = new Object;
oCar1.color = "red";
oCar1.showColor = showColor;
var oCar2 = new Object;
oCar2.color = "blue";
oCar2.showColor = showColor;
oCar1.showColor();//輸出"red"
oCar2.showColor();//輸出"blue"
例如,函數(shù)createCar()可用于封裝前面列出的創(chuàng)建car對(duì)象的操作:
function createCar() {
var oTempCar = new Object;
oTempCar.color = "blue";
oTempCar.doors = 4;
oTempCar.mpg = 25;
oTempCar.showColor = function() {
alert(this.color);
};
return oTempCar;
}
var oCar1 = createCar();
var oCar2 = createCar();
------------------------------------
為函數(shù)傳遞參數(shù)
我們還可以修改createCar()函數(shù),給它傳遞各個(gè)屬性的默認(rèn)值,而不是簡(jiǎn)單地賦予屬性默認(rèn)值:
function createCar(sColor,iDoors,iMpg) {
var oTempCar = new Object;
oTempCar.color = sColor;
oTempCar.doors = iDoors;
oTempCar.mpg = iMpg;
oTempCar.showColor = function() {
alert(this.color);
};
return oTempCar;
}
var oCar1 = createCar("red",4,23);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();//輸出"red"
oCar2.showColor();//輸出"blue"
給createCar()函數(shù)加上參數(shù),即可為要?jiǎng)?chuàng)建的car對(duì)象的color、doors和mpg屬性賦值。這使兩個(gè)對(duì)象具有相同的屬性,卻有不同的屬性值。
在工廠函數(shù)外定義對(duì)象的方法
雖然ECMAScript越來(lái)越正式化,但創(chuàng)建對(duì)象的方法卻被置之不理,且其規(guī)范化至今還遭人反對(duì)。一部分是語(yǔ)義上的原因(它看起來(lái)不像使用帶有構(gòu)造函數(shù)new運(yùn)算符那么正規(guī)),一部分是功能上的原因。功能原因在于用這種方式必須創(chuàng)建對(duì)象的方法。前面的例子中,每次調(diào)用函數(shù)createCar(),都要?jiǎng)?chuàng)建新函數(shù)showColor(),意味著每個(gè)對(duì)象都有自己的showColor()版本。而事實(shí)上,每個(gè)對(duì)象都共享同一個(gè)函數(shù)。
有些開(kāi)發(fā)者在工廠函數(shù)外定義對(duì)象的方法,然后通過(guò)屬性指向該方法,從而避免這個(gè)問(wèn)題:
function showColor() {
alert(this.color);
}
function createCar(sColor,iDoors,iMpg) {
var oTempCar = new Object;
oTempCar.color = sColor;
oTempCar.doors = iDoors;
oTempCar.mpg = iMpg;
oTempCar.showColor = showColor;
return oTempCar;
}
var oCar1 = createCar("red",4,23);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();//輸出"red"
oCar2.showColor();//輸出"blue"
-------------------------------------------
構(gòu)造函數(shù)方式
創(chuàng)建構(gòu)造函數(shù)就像創(chuàng)建工廠函數(shù)一樣容易。第一步選擇類(lèi)名,即構(gòu)造函數(shù)的名字。根據(jù)慣例,這個(gè)名字的首字母大寫(xiě),以使它與首字母通常是小寫(xiě)的變量名分開(kāi)。除了這點(diǎn)不同,構(gòu)造函數(shù)看起來(lái)很像工廠函數(shù)。請(qǐng)考慮下面的例子:
function Car(sColor,iDoors,iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.showColor = function() {
alert(this.color);
};
}
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
下面為您解釋上面的代碼與工廠方式的差別。首先在構(gòu)造函數(shù)內(nèi)沒(méi)有創(chuàng)建對(duì)象,而是使用this關(guān)鍵字。使用new運(yùn)算符構(gòu)造函數(shù)時(shí),在執(zhí)行第一行代碼前先創(chuàng)建一個(gè)對(duì)象,只有用this才能訪問(wèn)該對(duì)象。然后可以直接賦予this屬性,默認(rèn)情況下是構(gòu)造函數(shù)的返回值(不必明確使用return運(yùn)算符)。
現(xiàn)在,用new運(yùn)算符和類(lèi)名Car創(chuàng)建對(duì)象,就更像ECMAScript中一般對(duì)象的創(chuàng)建方式了。
你也許會(huì)問(wèn),這種方式在管理函數(shù)方面是否存在于前一種方式相同的問(wèn)題呢?是的。
-------------------------------------------
原型方式
該方式利用了對(duì)象的prototype屬性,可以把它看成創(chuàng)建新對(duì)象所依賴(lài)的原型。
這里,首先用空構(gòu)造函數(shù)來(lái)設(shè)置類(lèi)名。然后所有的屬性和方法都被直接賦予prototype屬性。我們重寫(xiě)了前面的例子,代碼如下:
function Car() {
}
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();
alert(oCar1 instanceof Car);//輸出"true"
-------------------------------------
原型方式的問(wèn)題
原型方式看起來(lái)是個(gè)不錯(cuò)的解決方案。遺憾的是,它并不盡如人意。
首先,這個(gè)構(gòu)造函數(shù)沒(méi)有參數(shù)。使用原型方式,不能通過(guò)給構(gòu)造函數(shù)傳遞參數(shù)來(lái)初始化屬性的值,因?yàn)镃ar1和Car2的color屬性都等于"blue",doors屬性都等于4,mpg屬性都等于25。這意味著必須在對(duì)象創(chuàng)建后才能改變屬性的默認(rèn)值,這點(diǎn)很令人討厭,但還沒(méi)完。真正的問(wèn)題出現(xiàn)在屬性指向的是對(duì)象,而不是函數(shù)時(shí)。函數(shù)共享不會(huì)造成問(wèn)題,但對(duì)象卻很少被多個(gè)實(shí)例共享。請(qǐng)思考下面的例子:
function Car() {
}
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.drivers = new Array("Mike","John");
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();
oCar1.drivers.push("Bill");
alert(oCar1.drivers);//輸出"Mike,John,Bill"
alert(oCar2.drivers);//輸出"Mike,John,Bill"
-------------------------------------------
混合的構(gòu)造函數(shù)/原型方式
聯(lián)合使用構(gòu)造函數(shù)和原型方式,就可像用其他程序設(shè)計(jì)語(yǔ)言一樣創(chuàng)建對(duì)象。這種概念非常簡(jiǎn)單,即用構(gòu)造函數(shù)定義對(duì)象的所有非函數(shù)屬性,用原型方式定義對(duì)象的函數(shù)屬性(方法)。結(jié)果是,所有函數(shù)都只創(chuàng)建一次,而每個(gè)對(duì)象都具有自己的對(duì)象屬性實(shí)例。
我們重寫(xiě)了前面的例子,代碼如下:
function Car(sColor,iDoors,iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.drivers = new Array("Mike","John");
}
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
oCar1.drivers.push("Bill");
alert(oCar1.drivers);//輸出"Mike,John,Bill"
alert(oCar2.drivers);//輸出"Mike,John"
--------------------------------
function StringBuffer () {
this._strings_ = new Array();
}
StringBuffer.prototype.append = function(str) {
this._strings_.push(str);
};
StringBuffer.prototype.toString = function() {
return this._strings_.join("");
};
這段代碼首先要注意的是strings屬性,本意是私有屬性。它只有兩個(gè)方法,即append()和toString()方法。append()方法有一個(gè)參數(shù),它把該參數(shù)附加到字符串?dāng)?shù)組中,toString()方法調(diào)用數(shù)組的join方法,返回真正連接成的字符串。要用StringBuffer對(duì)象連接一組字符串,可以用下面的代碼:
var buffer = new StringBuffer ();
buffer.append("hello ");
buffer.append("world");
var result = buffer.toString();
-----------------------------
通過(guò)使用ECMAScript,不僅可以創(chuàng)建對(duì)象,還可以修改已有對(duì)象的行為。
prototype屬性不僅可以定義構(gòu)造函數(shù)的屬性和方法,還可以為本地對(duì)象添加屬性和方法。
創(chuàng)建新方法
通過(guò)已有的方法創(chuàng)建新方法
可以用prototype屬性為任何已有的類(lèi)定義新方法,就像處理自己的類(lèi)一樣。例如,還記得Number類(lèi)的toString()方法嗎?如果給它傳遞參數(shù)16,它將輸出十六進(jìn)制的字符串。如果這個(gè)方法的參數(shù)是2,那么它將輸出二進(jìn)制的字符串。我們可以創(chuàng)建一個(gè)方法,可以把數(shù)字對(duì)象直接轉(zhuǎn)換為十六進(jìn)制字符串。創(chuàng)建這個(gè)方法非常簡(jiǎn)單:
Number.prototype.toHexString = function() {
return this.toString(16);
};
在此環(huán)境中,關(guān)鍵字this指向Number的實(shí)例,因此可完全訪問(wèn)Number的所有方法。有了這段代碼,可實(shí)現(xiàn)下面的操作:
var iNum = 15;
alert(iNum.toHexString());//輸出"F"
---------------------------
重命名已有方法
我們還可以為已有的方法命名更易懂的名稱(chēng)。例如,可以給Array類(lèi)添加兩個(gè)方法enqueue()和dequeue(),只讓它們反復(fù)調(diào)用已有的push()和shift()方法即可:
Array.prototype.enqueue = function(vItem) {
this.push(vItem);
};
Array.prototype.dequeue = function() {
return this.shift();
};
----------------------------
添加與已有方法無(wú)關(guān)的方法
當(dāng)然,還可以添加與已有方法無(wú)關(guān)的方法。例如,假設(shè)要判斷某個(gè)項(xiàng)在數(shù)組中的位置,沒(méi)有本地方法可以做這種事情。我們可以輕松地創(chuàng)建下面的方法:
Array.prototype.indexOf = function (vItem) {
for (var i=0; i
if (vItem == this[i]) {
return i;
}
}
return -1;
}
var aColors = new Array("red","green","blue");
alert(aColors.indexOf("green"));//輸出"1"
---------------------------
為本地對(duì)象添加新方法
最后,如果想給ECMAScript中每個(gè)本地對(duì)象添加新方法,必須在Object對(duì)象的prototype屬性上定義它。前面的章節(jié)我們講過(guò),所有本地對(duì)象都繼承了Object對(duì)象,所以對(duì)Object對(duì)象做任何改變,都會(huì)反應(yīng)在所有本地對(duì)象上。例如,如果想添加一個(gè)用警告輸出對(duì)象的當(dāng)前值的方法,可以采用下面的代碼:
Object.prototype.showValue = function () {
alert(this.valueOf());
};
var str = "hello";
var iNum = 25;
str.showValue();//輸出"hello"
iNum.showValue();//輸出"25"
-----------------------------
重定義已有方法
就像能給已有的類(lèi)定義新方法一樣,也可重定義已有的方法。如前面的章節(jié)所述,函數(shù)名只是指向函數(shù)的指針,因此可以輕松地指向其他函數(shù)。如果修改了本地方法,如toString(),會(huì)出現(xiàn)什么情況呢?
Function.prototype.toString = function() {
return "Function code hidden";
}
前面的代碼完全合法,運(yùn)行結(jié)果完全符合預(yù)期:
function sayHi() {
alert("hi");
}
alert(sayHi.toString());//輸出"Function code hidden"
--------------------------------
Function.prototype.originalToString = Function.prototype.toString;
Function.prototype.toString = function() {
if (this.originalToString().length > 100) {
return "Function too long to display.";
} else {
return this.originalToString();
}
};
function sayHi() {
alert("hi");
}
document.write(sayHi.toString());
//
function sayHi() { alert("hi"); }
---------------------------------
其原理如下:構(gòu)造函數(shù)使用this關(guān)鍵字給所有屬性和方法賦值(即采用類(lèi)聲明的構(gòu)造函數(shù)方式)。因?yàn)闃?gòu)造函數(shù)只是一個(gè)函數(shù),所以可使ClassA構(gòu)造函數(shù)成為ClassB的方法,然后調(diào)用它。ClassB就會(huì)收到ClassA的構(gòu)造函數(shù)中定義的屬性和方法。例如,用下面的方式定義ClassA和ClassB:
function ClassA(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function ClassB(sColor) {
}
還記得嗎?關(guān)鍵字this引用的是構(gòu)造函數(shù)當(dāng)前創(chuàng)建的對(duì)象。不過(guò)在這個(gè)方法中,this指向的所屬的對(duì)象。這個(gè)原理是把ClassA作為常規(guī)函數(shù)來(lái)建立繼承機(jī)制,而不是作為構(gòu)造函數(shù)。如下使用構(gòu)造函數(shù)ClassB可以實(shí)現(xiàn)繼承機(jī)制:
function ClassB(sColor) {
this.newMethod = ClassA;
this.newMethod(sColor);
delete this.newMethod;
}
在這段代碼中,為ClassA賦予了方法newMethod(請(qǐng)記住,函數(shù)名只是指向它的指針)。然后調(diào)用該方法,傳遞給它的是ClassB構(gòu)造函數(shù)的參數(shù)sColor。最后一行代碼刪除了對(duì)ClassA的引用,這樣以后就不能再調(diào)用它。
所有新屬性和新方法都必須在刪除了新方法的代碼行后定義。否則,可能會(huì)覆蓋超類(lèi)的相關(guān)屬性和方法:
function ClassB(sColor, sName) {
this.newMethod = ClassA;
this.newMethod(sColor);
delete this.newMethod;
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
為證明前面的代碼有效,可以運(yùn)行下面的例子:
var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();//輸出"blue"
objB.sayColor();//輸出"red"
objB.sayName();//輸出"John"
------------------------
對(duì)象冒充可以實(shí)現(xiàn)多重繼承
有趣的是,對(duì)象冒充可以支持多重繼承。也就是說(shuō),一個(gè)類(lèi)可以繼承多個(gè)超類(lèi)。用UML表示的多重繼承機(jī)制如下圖所示:
例如,如果存在兩個(gè)類(lèi)ClassX和ClassY,ClassZ想繼承這兩個(gè)類(lèi),可以使用下面的代碼:
function ClassZ() {
this.newMethod = ClassX;
this.newMethod();
delete this.newMethod;
this.newMethod = ClassY;
this.newMethod();
delete this.newMethod;
}
----------------------------------
call()方法
call()方法是與經(jīng)典的對(duì)象冒充方法最相似的方法。它的第一個(gè)參數(shù)用作this的對(duì)象。其他參數(shù)都直接傳遞給函數(shù)自身。例如:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.call(obj, "The color is ", "a very nice color indeed.");
在這個(gè)例子中,函數(shù)sayColor()在對(duì)象外定義,即使它不屬于任何對(duì)象,也可以引用關(guān)鍵字this。對(duì)象obj的color屬性等于blue。調(diào)用call()方法時(shí),第一個(gè)參數(shù)是obj,說(shuō)明應(yīng)該賦予sayColor()函數(shù)中的this關(guān)鍵字值是obj。第二個(gè)和第三個(gè)參數(shù)是字符串。它們與sayColor()函數(shù)中的參數(shù)sPrefix和sSuffix匹配,最后生成的消息"The color is blue, a very nice color indeed."將被顯示出來(lái)。
要與繼承機(jī)制的對(duì)象冒充方法一起使用該方法,只需將前三行的賦值、調(diào)用和刪除代碼替換即可:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.call(this, sColor);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
------------------------
apply()方法
apply()方法有兩個(gè)參數(shù),用作this的對(duì)象和要傳遞給函數(shù)的參數(shù)的數(shù)組。例如:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
這個(gè)例子與前面的例子相同,只是現(xiàn)在調(diào)用的是apply()方法。調(diào)用apply()方法時(shí),第一個(gè)參數(shù)仍是obj,說(shuō)明應(yīng)該賦予sayColor()函數(shù)中的this關(guān)鍵字值是obj。第二個(gè)參數(shù)是由兩個(gè)字符串構(gòu)成的數(shù)組,與sayColor()函數(shù)中的參數(shù)sPrefix和sSuffix匹配,最后生成的消息仍是"The color is blue, a very nice color indeed.",將被顯示出來(lái)。
該方法也用于替換前三行的賦值、調(diào)用和刪除新方法的代碼:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.apply(this, new Array(sColor));
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
同樣的,第一個(gè)參數(shù)仍是this,第二個(gè)參數(shù)是只有一個(gè)值color的數(shù)組。可以把ClassB的整個(gè)arguments對(duì)象作為第二個(gè)參數(shù)傳遞給apply()方法:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.apply(this, arguments);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
---------------------------
在上一章學(xué)過(guò),prototype對(duì)象是個(gè)模板,要實(shí)例化的對(duì)象都以這個(gè)模板為基礎(chǔ)。總而言之,prototype對(duì)象的任何屬性和方法都被傳遞給那個(gè)類(lèi)的所有實(shí)例。原型鏈利用這種功能來(lái)實(shí)現(xiàn)繼承機(jī)制。
如果用原型方式重定義前面例子中的類(lèi),它們將變?yōu)橄铝行问剑?/p>
function ClassA() {
}
ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB() {
}
ClassB.prototype = new ClassA();
原型方式的神奇之處在于突出顯示的藍(lán)色代碼行。這里,把ClassB的prototype屬性設(shè)置成ClassA的實(shí)例。這很有意思,因?yàn)橄胍狢lassA的所有屬性和方法,但又不想逐個(gè)將它們ClassB的prototype屬性。還有比把ClassA的實(shí)例賦予prototype屬性更好的方法嗎?
注意:調(diào)用ClassA的構(gòu)造函數(shù),沒(méi)有給它傳遞參數(shù)。這在原型鏈中是標(biāo)準(zhǔn)做法。要確保構(gòu)造函數(shù)沒(méi)有任何參數(shù)。
與對(duì)象冒充相似,子類(lèi)的所有屬性和方法都必須出現(xiàn)在prototype屬性被賦值后,因?yàn)樵谒百x值的所有方法都會(huì)被刪除。為什么?因?yàn)閜rototype屬性被替換成了新對(duì)象,添加了新方法的原始對(duì)象將被銷(xiāo)毀。所以,為ClassB類(lèi)添加name屬性和sayName()方法的代碼如下:
function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
alert(this.name);
};
可通過(guò)運(yùn)行下面的例子測(cè)試這段代碼:
var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();
此外,在原型鏈中,instanceof運(yùn)算符的運(yùn)行方式也很獨(dú)特。對(duì)ClassB的所有實(shí)例,instanceof為ClassA和ClassB都返回true。例如:
var objB = new ClassB();
alert(objB instanceof ClassA);//輸出"true"
alert(objB instanceof ClassB);//輸出"true"
----------------------
混合方式
這種繼承方式使用構(gòu)造函數(shù)定義類(lèi),并非使用任何原型。對(duì)象冒充的主要問(wèn)題是必須使用構(gòu)造函數(shù)方式,這不是最好的選擇。不過(guò)如果使用原型鏈,就無(wú)法使用帶參數(shù)的構(gòu)造函數(shù)了。開(kāi)發(fā)者如何選擇呢?答案很簡(jiǎn)單,兩者都用。
在前一章,我們?cè)?jīng)講解過(guò)創(chuàng)建類(lèi)的最好方式是用構(gòu)造函數(shù)定義屬性,用原型定義方法。這種方式同樣適用于繼承機(jī)制,用對(duì)象冒充繼承構(gòu)造函數(shù)的屬性,用原型鏈繼承prototype對(duì)象的方法。用這兩種方式重寫(xiě)前面的例子,代碼如下:
function ClassA(sColor) {
this.color = sColor;
}
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB(sColor, sName) {
ClassA.call(this, sColor);
this.name = sName;
}
ClassB.prototype = new ClassA();
ClassB.prototype.sayName = function () {
alert(this.name);
};
在此例子中,繼承機(jī)制由兩行突出顯示的藍(lán)色代碼實(shí)現(xiàn)。在第一行突出顯示的代碼中,在ClassB構(gòu)造函數(shù)中,用對(duì)象冒充繼承ClassA類(lèi)的sColor屬性。在第二行突出顯示的代碼中,用原型鏈繼承ClassA類(lèi)的方法。由于這種混合方式使用了原型鏈,所以instanceof運(yùn)算符仍能正確運(yùn)行。
下面的例子測(cè)試了這段代碼:
var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();//輸出"blue"
objB.sayColor();//輸出"red"
objB.sayName();//輸出"John"
------------------------------------