ECMAScript

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"

TIY

給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"

------------------------------------

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 工廠模式類似于現實生活中的工廠可以產生大量相似的商品,去做同樣的事情,實現同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 7,827評論 2 17
  • 本人是android開發的,由于最近React Native的火熱,再加上自己完全不懂JS的語法,俗話說的好"落后...
    Bui_vlee閱讀 294評論 0 0
  • 我基本從來不寫工作的事兒。 因為工作實在沒啥好寫的,不就是工作唄。 然後今天打算稍微寫一點,就寫JS吧。 我一直相...
    LostAbaddon閱讀 1,462評論 22 21
  • 一、let 和 constlet:變量聲明, const:只讀常量聲明(聲明的時候賦值)。 let 與 var 的...
    dadage456閱讀 768評論 0 0
  • 第5章 引用類型(返回首頁) 本章內容 使用對象 創建并操作數組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,270評論 0 4