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