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種原始類型(primitive type),即Undefined、Null窿侈、Boolean肠缔、Number和String匿级。ECMA-262把術(shù)語類型(type)定義為值的一個(gè)集合蛔屹,每種原始類型定義了它包含的值的范圍及其字面量表示形式。
原始值
存儲(chǔ)在棧(stack)中的簡(jiǎn)單數(shù)據(jù)段冤留,也就是說恩掷,它們的值直接存儲(chǔ)在變量訪問的位置倡鲸。
引用值
存儲(chǔ)在堆(heap)中的對(duì)象,也就是說黄娘,存儲(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類型的
boolean -如果變量是Boolean類型的
number -如果變量是Number類型的
string -如果變量是String類型的
object -如果變量是一種引用類型或Null類型的
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語句進(jìn)行枚舉傅是。
ToString()
返回對(duì)象的原始字符串表示匪燕。對(duì)于Object對(duì)象蕾羊,ECMA-262沒有定義這個(gè)值喧笔,所以不同的ECMAScript實(shí)現(xiàn)具有不同的值帽驯。
ValueOf()
返回最適合該對(duì)象的原始值。對(duì)于許多對(duì)象书闸,該方法返回的值都與ToString()的返回值相同尼变。
注釋:上面列出的每種屬性和方法都會(huì)被其他對(duì)象覆蓋。
instanceof運(yùn)算符
在使用typeof運(yùn)算符時(shí)采用引用類型存儲(chǔ)值會(huì)出現(xiàn)一個(gè)問題浆劲,無論引用的是什么類型的對(duì)象嫌术,它都返回"object"。ECMAScript引入了另一個(gè)Java運(yùn)算符instanceof來解決這個(gè)問題牌借。
instanceof運(yùn)算符與typeof運(yùn)算符相似度气,用于識(shí)別正在處理的對(duì)象的類型。與typeof方法不同的是膨报,instanceof方法要求開發(fā)者明確地確認(rèn)對(duì)象為某特定類型磷籍。例如:
var oStringObject = new String("hello world");
alert(oStringObject instanceof String);//輸出"true"
這段代碼問的是“變量oStringObject是否為String對(duì)象的實(shí)例?”oStringObject的確是String對(duì)象的實(shí)例现柠,因此結(jié)果是"true"院领。盡管不像typeof方法那樣靈活,但是在typeof方法返回"object"的情況下够吩,instanceof方法還是很有用的比然。
全等號(hào)由三個(gè)等號(hào)表示(===),只有在無需類型轉(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語句引用湾笛。
with語句用于設(shè)置代碼在特定對(duì)象中的作用域拟烫。
它的語法:
with (expression)statement
例如:
var sMessage = "hello";
with(sMessage) {
alert(toUpperCase());//輸出"HELLO"
}
arguments對(duì)象
在函數(shù)代碼中,使用特殊對(duì)象arguments迄本,開發(fā)者無需明確指出參數(shù)名硕淑,就能訪問它們。
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ì)象(類)
ECMAScript最令人感興趣的可能莫過于函數(shù)實(shí)際上是功能完整的對(duì)象。
Function類可以表示開發(fā)者定義的任何函數(shù)靶橱。
用Function類直接創(chuàng)建函數(shù)的語法如下:
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"传黄。
如果以下面的形式重寫該代碼塊杰扫,這個(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ù)屬于引用類型钾军,所以它們也有屬性和方法。
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()沒有定義參數(shù)银还,所以length是0。
記住耕腾,無論定義了幾個(gè)參數(shù)见剩,ECMAScript可以接受任意多個(gè)參數(shù)(最多25個(gè)),這一點(diǎn)在《函數(shù)概述》這一章中講解過扫俺。屬性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ù)疗琉,也就是說冈欢,函數(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言的要求
一種面向?qū)ο笳Z言需要向開發(fā)者提供四種基本能力:
封裝-把相關(guān)的信息(無論數(shù)據(jù)或方法)存儲(chǔ)在對(duì)象中的能力
聚集-把一個(gè)對(duì)象存儲(chǔ)在另一個(gè)對(duì)象內(nèi)的能力
繼承-由另一個(gè)類(或多個(gè)類)得來類的屬性和方法的能力
多態(tài)-編寫能以多種方法運(yùn)行的函數(shù)或方法的能力
對(duì)象的構(gòu)成
在ECMAScript中,對(duì)象由特性(attribute)構(gòu)成柠贤,特性可以是原始值香浩,也可以是引用值。如果特性存放的是函數(shù)臼勉,它將被看作對(duì)象的方法(method)邻吭,否則該特性被看作對(duì)象的屬性(property)。
聲明和實(shí)例化
對(duì)象的創(chuàng)建方式是用關(guān)鍵字new后面跟上實(shí)例化的類的名字:
var oObject = new Object();
var oStringObject = new String();
第一行代碼創(chuàng)建了Object類的一個(gè)實(shí)例宴霸,并把它存儲(chǔ)到變量oObject中囱晴。第二行代碼創(chuàng)建了String類的一個(gè)實(shí)例膏蚓,把它存儲(chǔ)在變量oStringObject中。如果構(gòu)造函數(shù)無參數(shù)畸写,括號(hào)則不是必需的驮瞧。因此可以采用下面的形式重寫上面的兩行代碼:
var oObject = new Object;
var oStringObject = new String;
對(duì)象廢除
ECMAScript擁有無用存儲(chǔ)單元收集程序(garbage collection routine),意味著不必專門銷毀對(duì)象來釋放內(nèi)存艺糜。當(dāng)再?zèng)]有對(duì)對(duì)象的引用時(shí)剧董,稱該對(duì)象被廢除(dereference)了幢尚。運(yùn)行無用存儲(chǔ)單元收集程序時(shí)破停,所有廢除的對(duì)象都被銷毀。每當(dāng)函數(shù)執(zhí)行完它的代碼尉剩,無用存儲(chǔ)單元收集程序都會(huì)運(yùn)行真慢,釋放所有的局部變量,還有在一些其他不可預(yù)知的情況下理茎,無用存儲(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這樣的語言中,有了早綁定沟启,就可以在開發(fā)環(huán)境中使用IntelliSense(即給開發(fā)者提供對(duì)象中屬性和方法列表的功能)忆家。ECMAScript不是強(qiáng)類型語言,所以不支持早綁定德迹。
另一方面芽卿,晚綁定(late binding)指的是編譯器或解釋程序在運(yùn)行前,不知道對(duì)象的類型胳搞。使用晚綁定卸例,無需檢查對(duì)象的類型,只需檢查對(duì)象是否支持屬性和方法即可肌毅。ECMAScript中的所有變量都采用晚綁定方法筷转。這樣就允許執(zhí)行大量的對(duì)象操作,而無任何懲罰芽腾。
--------------------------------
一般來說旦装,可以創(chuàng)建并使用的對(duì)象有三種:本地對(duì)象、內(nèi)置對(duì)象和宿主對(duì)象摊滔。
本地對(duì)象
ECMA-262把本地對(duì)象(native object)定義為“獨(dú)立于宿主環(huán)境的ECMAScript實(shí)現(xiàn)提供的對(duì)象”阴绢。簡(jiǎn)單來說店乐,本地對(duì)象就是ECMA-262定義的類(引用類型)。它們包括:
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程序開始執(zhí)行時(shí)出現(xiàn)”。這意味著開發(fā)者不必明確實(shí)例化內(nèi)置對(duì)象左电,它已被實(shí)例化了廉侧。ECMA-262只定義了兩個(gè)內(nèi)置對(duì)象,即Global和Math(它們也是本地對(duì)象篓足,根據(jù)定義段誊,每個(gè)內(nèi)置對(duì)象都是本地對(duì)象)。
相關(guān)頁面
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)頁面
JavaScript高級(jí)教程:JavaScript實(shí)現(xiàn)
W3School參考手冊(cè):JavaScript參考手冊(cè)
W3School教程:HTML DOM教程
-------------------------------
作用域指的是變量的適用范圍涩哟。
公用索赏、私有和受保護(hù)作用域
概念
在傳統(tǒng)的面向?qū)ο蟪绦蛟O(shè)計(jì)中,主要關(guān)注于公用和私有作用域贴彼。公用作用域中的對(duì)象屬性可以從對(duì)象外部訪問潜腻,即開發(fā)者創(chuàng)建對(duì)象的實(shí)例后,就可使用它的公用屬性器仗。而私有作用域中的屬性只能在對(duì)象內(nèi)部訪問融涣,即對(duì)于外部世界來說,這些屬性并不存在青灼。這意味著如果類定義了私有屬性和方法暴心,則它的子類也不能訪問這些屬性和方法。
受保護(hù)作用域也是用于定義私有的屬性和方法杂拨,只是這些屬性和方法還能被其子類訪問专普。
ECMAScript只有公用作用域
對(duì)ECMAScript討論上面這些作用域幾乎毫無意義,因?yàn)镋CMAScript中只存在一種作用域-公用作用域弹沽。ECMAScript中的所有對(duì)象的所有屬性和方法都是公用的檀夹。因此,定義自己的類和對(duì)象時(shí)策橘,必須格外小心炸渡。記住,所有屬性和方法默認(rèn)都是公用的丽已!
建議性的解決方法
許多開發(fā)者都在網(wǎng)上提出了有效的屬性作用域模式蚌堵,解決了ECMAScript的這種問題。
由于缺少私有作用域,開發(fā)者確定了一個(gè)規(guī)約吼畏,說明哪些屬性和方法應(yīng)該被看做私有的督赤。這種規(guī)約規(guī)定在屬性前后加下劃線:
obj._color_ = "blue";
這段代碼中,屬性color是私有的泻蚊。注意躲舌,下劃線并不改變屬性是公用屬性的事實(shí),它只是告訴其他開發(fā)者性雄,應(yīng)該把該屬性看作私有的没卸。
有些開發(fā)者還喜歡用單下劃線說明私有成員,例如:obj._color秒旋。
靜態(tài)作用域
靜態(tài)作用域定義的屬性和方法任何時(shí)候都能從同一位置訪問约计。在Java中,類可具有屬性和方法滩褥,無需實(shí)例化該類的對(duì)象病蛉,即可訪問這些屬性和方法炫加,例如java.net.URLEncoder類瑰煎,它的函數(shù)encode()就是靜態(tài)方法。
ECMAScript沒有靜態(tài)作用域
嚴(yán)格來說俗孝,ECMAScript并沒有靜態(tài)作用域酒甸。不過,它可以給構(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í)滑沧,總是不能確定開發(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越來越正式化挠唆,但創(chuàng)建對(duì)象的方法卻被置之不理处窥,且其規(guī)范化至今還遭人反對(duì)。一部分是語義上的原因(它看起來不像使用帶有構(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ù)。
有些開發(fā)者在工廠函數(shù)外定義對(duì)象的方法踪蹬,然后通過屬性指向該方法胞此,從而避免這個(gè)問題:
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ù)一樣容易。第一步選擇類名跃捣,即構(gòu)造函數(shù)的名字漱牵。根據(jù)慣例,這個(gè)名字的首字母大寫疚漆,以使它與首字母通常是小寫的變量名分開酣胀。除了這點(diǎn)不同,構(gòu)造函數(shù)看起來很像工廠函數(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)沒有創(chuàng)建對(duì)象,而是使用this關(guān)鍵字丸升。使用new運(yùn)算符構(gòu)造函數(shù)時(shí)铆农,在執(zhí)行第一行代碼前先創(chuàng)建一個(gè)對(duì)象,只有用this才能訪問該對(duì)象发钝。然后可以直接賦予this屬性顿涣,默認(rèn)情況下是構(gòu)造函數(shù)的返回值(不必明確使用return運(yùn)算符)。
現(xiàn)在酝豪,用new運(yùn)算符和類名Car創(chuàng)建對(duì)象涛碑,就更像ECMAScript中一般對(duì)象的創(chuàng)建方式了。
你也許會(huì)問孵淘,這種方式在管理函數(shù)方面是否存在于前一種方式相同的問題呢蒲障?是的。
-------------------------------------------
原型方式
該方式利用了對(duì)象的prototype屬性,可以把它看成創(chuàng)建新對(duì)象所依賴的原型揉阎。
這里庄撮,首先用空構(gòu)造函數(shù)來設(shè)置類名。然后所有的屬性和方法都被直接賦予prototype屬性毙籽。我們重寫了前面的例子洞斯,代碼如下:
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"
-------------------------------------
原型方式的問題
原型方式看起來是個(gè)不錯(cuò)的解決方案。遺憾的是坑赡,它并不盡如人意烙如。
首先,這個(gè)構(gòu)造函數(shù)沒有參數(shù)毅否。使用原型方式亚铁,不能通過給構(gòu)造函數(shù)傳遞參數(shù)來初始化屬性的值,因?yàn)镃ar1和Car2的color屬性都等于"blue"螟加,doors屬性都等于4徘溢,mpg屬性都等于25。這意味著必須在對(duì)象創(chuàng)建后才能改變屬性的默認(rèn)值捆探,這點(diǎn)很令人討厭然爆,但還沒完。真正的問題出現(xiàn)在屬性指向的是對(duì)象徐许,而不是函數(shù)時(shí)施蜜。函數(shù)共享不會(huì)造成問題,但對(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ì)語言一樣創(chuàng)建對(duì)象缸沃。這種概念非常簡(jiǎn)單恰起,即用構(gòu)造函數(shù)定義對(duì)象的所有非函數(shù)屬性,用原型方式定義對(duì)象的函數(shù)屬性(方法)趾牧。結(jié)果是检盼,所有函數(shù)都只創(chuàng)建一次,而每個(gè)對(duì)象都具有自己的對(duì)象屬性實(shí)例翘单。
我們重寫了前面的例子吨枉,代碼如下:
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();
-----------------------------
通過使用ECMAScript拘央,不僅可以創(chuàng)建對(duì)象,還可以修改已有對(duì)象的行為书在。
prototype屬性不僅可以定義構(gòu)造函數(shù)的屬性和方法灰伟,還可以為本地對(duì)象添加屬性和方法。
創(chuàng)建新方法
通過已有的方法創(chuàng)建新方法
可以用prototype屬性為任何已有的類定義新方法儒旬,就像處理自己的類一樣袱箱。例如,還記得Number類的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í)例,因此可完全訪問Number的所有方法垃杖。有了這段代碼男杈,可實(shí)現(xiàn)下面的操作:
var iNum = 15;
alert(iNum.toHexString());//輸出"F"
---------------------------
重命名已有方法
我們還可以為已有的方法命名更易懂的名稱。例如调俘,可以給Array類添加兩個(gè)方法enqueue()和dequeue()伶棒,只讓它們反復(fù)調(diào)用已有的push()和shift()方法即可:
Array.prototype.enqueue = function(vItem) {
this.push(vItem);
};
Array.prototype.dequeue = function() {
return this.shift();
};
----------------------------
添加與已有方法無關(guān)的方法
當(dāng)然,還可以添加與已有方法無關(guān)的方法彩库。例如肤无,假設(shè)要判斷某個(gè)項(xiàng)在數(shù)組中的位置,沒有本地方法可以做這種事情骇钦。我們可以輕松地創(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é)我們講過窥翩,所有本地對(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"
-----------------------------
重定義已有方法
就像能給已有的類定義新方法一樣幔荒,也可重定義已有的方法糊闽。如前面的章節(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)鍵字給所有屬性和方法賦值(即采用類聲明的構(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ì)象吃嘿。不過在這個(gè)方法中,this指向的所屬的對(duì)象梦重。這個(gè)原理是把ClassA作為常規(guī)函數(shù)來建立繼承機(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ì)覆蓋超類的相關(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ì)象冒充可以支持多重繼承贞岭。也就是說八毯,一個(gè)類可以繼承多個(gè)超類。用UML表示的多重繼承機(jī)制如下圖所示:
例如瞄桨,如果存在兩個(gè)類ClassX和ClassY话速,ClassZ想繼承這兩個(gè)類,可以使用下面的代碼:
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,說明應(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."將被顯示出來竿报。
要與繼承機(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捂襟,說明應(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."涨共,將被顯示出來。
該方法也用于替換前三行的賦值宠漩、調(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é)過,prototype對(duì)象是個(gè)模板雕崩,要實(shí)例化的對(duì)象都以這個(gè)模板為基礎(chǔ)魁索。總而言之盼铁,prototype對(duì)象的任何屬性和方法都被傳遞給那個(gè)類的所有實(shí)例粗蔚。原型鏈利用這種功能來實(shí)現(xiàn)繼承機(jī)制。
如果用原型方式重定義前面例子中的類饶火,它們將變?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ù)裆针,沒有給它傳遞參數(shù)。這在原型鏈中是標(biāo)準(zhǔn)做法寺晌。要確保構(gòu)造函數(shù)沒有任何參數(shù)世吨。
與對(duì)象冒充相似,子類的所有屬性和方法都必須出現(xiàn)在prototype屬性被賦值后呻征,因?yàn)樵谒百x值的所有方法都會(huì)被刪除耘婚。為什么?因?yàn)閜rototype屬性被替換成了新對(duì)象陆赋,添加了新方法的原始對(duì)象將被銷毀沐祷。所以,為ClassB類添加name屬性和sayName()方法的代碼如下:
function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
alert(this.name);
};
可通過運(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ù)定義類,并非使用任何原型顺饮。對(duì)象冒充的主要問題是必須使用構(gòu)造函數(shù)方式吵聪,這不是最好的選擇。不過如果使用原型鏈兼雄,就無法使用帶參數(shù)的構(gòu)造函數(shù)了吟逝。開發(fā)者如何選擇呢?答案很簡(jiǎn)單赦肋,兩者都用块攒。
在前一章,我們?cè)?jīng)講解過創(chuàng)建類的最好方式是用構(gòu)造函數(shù)定義屬性佃乘,用原型定義方法局蚀。這種方式同樣適用于繼承機(jī)制,用對(duì)象冒充繼承構(gòu)造函數(shù)的屬性恕稠,用原型鏈繼承prototype對(duì)象的方法。用這兩種方式重寫前面的例子扶欣,代碼如下:
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類的sColor屬性澎羞。在第二行突出顯示的代碼中,用原型鏈繼承ClassA類的方法敛苇。由于這種混合方式使用了原型鏈妆绞,所以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"
------------------------------------