作者:禪樓望月( http://www.cnblogs.com/yaoyinglong ) JavaScript里也可以像Java等面向對象的語言世界里創建自定義的類型,但是由於JavaScript中不支持使用class關鍵字來創建自定義的類型,因此我們只能另闢蹊徑……下麵我們一起來看在JavaSc ...
作者:禪樓望月( http://www.cnblogs.com/yaoyinglong )
JavaScript里也可以像Java等面向對象的語言世界里創建自定義的類型,但是由於JavaScript中不支持使用class關鍵字來創建自定義的類型,因此我們只能另闢蹊徑……下麵我們一起來看在JavaScript中如何使用OO。
1工廠模式
[+] view code function createPersoin(name,age){ var o=new Object(); o.name=name; o.age=age; o.sayName=function(){return o.name;} return o; } var p1=createPersoin('yyl',27); var p2=createPersoin('dyy',27);調用createPerson就可以創建一個對象,看起來我們已經創建了一個自定義的類型,該類型可以用來表示Person,但是問題是,怎麼讓解析引擎知道這是一個Person呢?很抱歉,這種工程模式解決不了這種問題,所有的實例都是Object類型。
2.構造函數模式
為瞭解決對象識別的問題,JavaScript中又出現了一種新的模式,即這裡的構造函數模式,構造函數其實就是一個普通的函數,除了函數名稱首字母一般要大寫外,其他的和普通的函數沒什麼區別。我們用構造函數模式改造是上面的工廠模式:
[+] view code function Persoin(name,age){ this.name=name; this.age=age; this.sayName=function(){return this.name;} } var p1=new Persoin('yyl',27); var p2=new Persoin('dyy',27);與工程模式相比,這次我們沒有在構造函數中顯示地創建一個Object,也沒有return語句,並且把所有的屬性與方法賦值給this。而在創建實例的時候,使用了我們可愛的new操作符,那我們來看下new操作符是怎麼創建出一個Person的實例的:
- 構建一個新的對象,這時這個對象可不單單是Object了,它還是Person,將該對象的[[Prototype]]設置為對應構造函數的原型對象;
- 將構造函數的作用域賦值給該對象;
- 執行構造函數中的代碼;
- 返回該對象。
這樣構造出來的對象便可以通過instanceof操作符來識別了:
[+] view code alert(p1 instanceof Persoin);//true剛剛也說了,構造函數其實和普通的函數一模一樣,只是函數名稱首字母被大寫了而已。如果在調用構造函數的時候沒用使用new操作符,豈不是將所有的屬性和方法添加到window上了,這樣可不好,為此我們重寫Person的構造函數:
[+] view code function Persoin(name,age){ if(!(this instanceof Persoin)){ return new Persoin(name,age); } this.name=name; this.age=age; this.sayName=function(){return this.name;} }當丟失了new操作符的時候,我們強制返回一個Person對象,這樣全局作用域就不會被污染了。
構造函數模式的缺點:
所有的屬性和方法都不會被共用,p1和p2都是Person的對象,但是他們中的sayName雖然完成的功能相同,但卻是兩個不同的Function實例,這是非常沒有必要的。況且sayName內部有tihs,就更沒有必要在執行代碼前將函數綁定到特定的對象上面。
3.原型模式
在創建一個函數的時候,JavaScript引擎就會根據一組規則為該函數創建一個prototype屬性,該屬性是一個指針,指向一個對象,這個對象的用途是包含可以由特定類型的所有實例共用的屬性和方法。現在我們用原型模式來改造構造函數模式:
[+] view code function Persoin(name,age){ if(!(this instanceof Persoin)){ return new Persoin(name,age); } this.name=name; this.age=age; } Persoin.prototype.sayName= function () { return this.name; };由上圖可發現,這個原型對象其實就是一個特殊的Person對象,特殊到哪裡了呢?
- 它是由JavaScript引擎根據一定的規則幫我們創建的;
- 它預設只包含constructor屬性(其他屬性,方法都是從Object繼承而來),而這個constructor指向包含該prototype的函數(這裡便是Person函數);
- 強制將該原型對象的[[Prototype]]指向Object函數的原型對象。
現在我們就把要在各個實例中共用的屬性和方法添加到該對象中。
它是怎麼做到在各個實例中共用的呢?由159行的代碼,我們可以看出,每個Person的實例都包含一個指向Person的原型的指針(__proto__),嗯,就是這樣做到共用的。
有沒有註意到剛剛所說的這個prototype的用途?它只是用來存儲包含它的構造函數的所有實例所共有的屬性和方法。那它就沒必要必須是一個特殊的Person對象了,並且每次為其添加屬性和方法總是要寫Person.prototype.……很麻煩,因此我們可以這樣寫:
[+] view code function Persoin(name,age){ if(!(this instanceof Persoin)){ return new Persoin(name,age); } this.name=name; this.age=age; //this.sayName=function(){return this.name;} } /*Persoin.prototype.sayName= function () { return this.name; };*/ Persoin.prototype={ sayName: function () { return this.name; }, sayAge: function () { return this.age; } }; //查看原型對象 var p=Persoin.prototype; //創建實例 var p1=new Persoin('yyl',27); var p2=new Persoin('dyy',27); var flag=(p1.__proto__===p2.__proto__);現在,Person構造函數的原型已經不再是一個Person的特殊實例,而是一個Object。現在有一個問題,這個Object對象中沒有constructor屬性了。但是還是能訪問到這個屬性:
onsole.info(p.constructor)//function Object() |
註意這可以不是該Person的原型對象中的constructor,它是Object的原型對象的constructor。
為什麼能拿到Object類型的原型對象的constructor屬性呢?這是因為在JavaScript獲取屬性和方法的值的過程其實就是一次搜索的過程,看一個更明顯的例子:
console.info(str=p1.toString());//[object Object]
我們並沒有在構造函數中定義toString函數,也沒有再Person的原型中定義該函數,那是怎麼獲取的呢?
首先在構造函數中找,接著在原型對象的中找,再在該原型對象所在函數的原型對象中找,以此類推,直到找到為止。
如果這個constructor的值很重要的話,添加上就是了,但是這會導致其[[Enumerable]]特性被設置為true。而原生的該特性的值為false。
最重要的一句話:原型對象是引用類型。
4.動態原型模式
如果你是用過Java、C#或者其他的面向對象的語言,你可能覺得這種將構造函數和原型獨立起來的做法很彆扭,那麼動態原型模式就是解決這一問題的:
[+] view code function Persoin(name,age){ if(!(this instanceof Persoin)){ return new Persoin(name,age); } this.name=name; this.age=age; if(typeof this.sayName!=="function"){ Persoin.prototype.sayName= function () { return this.name; }; Persoin.prototype.sayAge= function () { return this.age; } } } //查看原型對象 var p=Persoin.prototype; //創建實例 var p1=new Persoin('yyl',27);這種方法構造對象可以說是非常完美的。註意if語句可以是初始化之後應該存在的任何屬性或方法,但是請註意,不必用一大堆的if來檢查每個屬性和方法,只要檢查其中一個即可。它是用來說明在第一次調用構造函數的時候執行if裡面的代碼,以後就不要再執行這些代碼了。
特別註意:使用動態原型模式構造對象時,不能使用對象字面量來重寫原型。這是又是為什麼呢?
我們來看一下這串代碼的執行過程,首先JavaScript引擎會將Person函數放在最開始創建出來,這是它的原型對象便是我們所說的那個特殊的Person對象(正如在161行代碼獲取到的一樣),執行到163行代碼,JavaScript得到的指令時去新建一個Person實例,回想一下JavaScript創建對象的過程:1.創建一個新的對象;對了,問題的關鍵就在這裡了,JavaScript引擎創建的這個對象中已經包含了[[Prototype]]這個內部指針。它現在指向這個特殊的Person實例。2.將構造函數的作用域賦值給該對象;3.執行構造函數中的代碼,當執行了151-157的代碼時,Person.prototype的指向已將變了,現在它指向這個對象字面量了。而目前的p1的[[Prototype]]還是指向原來的原型對象。這就導致了我們從p1中訪問不到sayName方法。當執行164行代碼時,JavaScript引擎再去新建一個Person實例,這時Person.prototype已經在新建p1時被替換了,它順理成章的擁有了這個原型對象。當執行到151-157的代碼時由於原型對象中有sayName方法,這段代碼沒有被執行,因此,創建出來的p2才是符合我們願望的對象。