以前寫過一篇關於JS創建對象的帖子,JS創建對象的幾種方法,突然想溫習一下,所以寫了下面的小例子,用來回顧這七種create pattern。
每種pattern都有自己的特色。
1 工廠模式中,在構造函數內部用 o={}創建一個新對象,最後返回這個對象。當實例化時,不需要用new關鍵字,就像調用一般的方法一樣。
我們可以把create函數設想成一個貼牌工廠,面對不同的需求,製作出內在相同,標籤不同的產品。
工廠模式最大的問題是容易和普通函數混淆,方便歸方便,但我們只能通過命名來確認它是一個構造函數。
/*1.factory pattern*/ function createPerson(name,age,job){ var o = {}; //new object o.name=name; o.age=age; o.job=job; o.friends=["ansel","suisa"]; o.sayName=function(){ alert("factory pattern:"+this.name); }; return o; } var tanya= createPerson("tanya","28","Software Engineer"); tanya.sayName(); /*tips: o={} inside the create function no "this" in the create function */
2 構造函數模式中,用new關鍵字來實例化對象,在構造函數內部使用this,無需返回任何對象。
構造函數相比工廠函數更加OO啦,並且可以將它的實例標識作爲一種特定的類型。像String,Array一樣的使用自定義的類型。
但是,它的問題是不斷的拷貝拷貝,每new一次就造出一份副本,每個方法都要在每個實例上重新創建一遍,不同實例的同名函數是不相等的(工廠模式也有此問題),
都說未來世界是個資源共享,專業細分的社會,JS OO的世界中當然應該體現這種共享性,於是Eric發明了原型鏈。
/*2.constructor pattern */ function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert("constructor pattern:"+this.name); }; } var ansel = new Person("ansel","28","Software Engineer"); ansel.sayName(); /*tips: new in entities this in constructor */
3 原型模式中,最特色的當然是prototype。但是原型僅在需要share的時候好用,可是人不能是完全透明的,也需要一點點隱私。所以最爲常用的模式一定是混合型的。
/*3.prototype pattern*/ function Tanya(){ } Tanya.prototype.name="tanya"; Tanya.prototype.age="28"; Tanya.prototype.sayName= function(){ alert("prototype pattern:"+this.name); } var person1= new Tanya(); person1.sayName(); /*tips: public share of all the entities if no return ,then need new. else you may use return. */
4 構造函數模式和原型模式的混合類型。特點就是想隱私,那麼就用構造函數模式去copy封裝在自己的包裏,想要share的地方就用prototype。
/*4.hybrid constructor & prototype Pattern*/ function Student(name,sno){ this.name=name; this.sno=sno; this.sayName=function(){alert("hybrid constructor & prototype Pattern:"+this.name);} } //Student.prototype.teacher=["ansel","tanya"]; //Student.prototype.sayTeacher=function(){alert("hybrid constructor & prototype Pattern:"+this.teacher);}; Student.prototype={ constructor:Student, teacher:["ansel","tanya"], sayTeacher:function(){alert("hybrid constructor & prototype Pattern:"+this.teacher);} } var wang = new Student("wang","12"); var li = new Student("li","13"); wang.sayName(); li.sayName(); wang.sayTeacher(); li.sayTeacher(); /*tips: something you need to be public share,and something you want to be private. we often use this mode,because anything never goes in extrmely way. */
5 有些時候別人做過的事情我們不想再做,如果別人還沒有做,那麼我們就去做一遍,這種情況就是動態原型模式,無非就是if 沒這個函數...do prototype。
/*5.dynamic prototype pattern*/ function Dogs(name){ this.name=name; this.showName=function(){ alert(this.name); } if (typeof this.shout != "function") { Dogs.prototype.shout=function(){ alert("dynamic prototype pattern:wangwang"); } } } var xixi=new Dogs("xixi"); var mimi=new Dogs("mimi"); xixi.showName(); xixi.shout(); mimi.showName(); mimi.shout(); /*tips: sometimes,function is already defined ,if it already defined ,you may not define again, else you should make prototype attributes functions dynamically,. */
6 寄生構造函數模式更像是工廠模式,幾乎一模一樣,不過在每次實例化對象的時候用了一個 new。這種模式的作用是在特殊情況下用來爲對象創建構造函數。
但是就像它的名字非常的怪異一樣,這不是一種被推崇的創建對象模式,因爲不能依賴 instanceof 操作符來確定對象的類型,
紅皮書上的解釋是:構造函數返回的對象與在構造函數外部創建的對象沒有什麼不同。
那麼我就姑且理解成這種模式對外看不到創建它的對象的類型,所有通過此模式創建的實例都像一個個找不到廠家的無牌商品,工廠模式也有這樣的問題。
/*6.parasitic constrctor pattern (hybird factory)*/ function Person1(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person1("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas" /*tips: similar to factory pattern,but use "new" in entities */
7 穩妥構造函數模式,這種模式的設計初衷是絕對保守的,他的目的是安全,手段是不用this,不用new,和工廠模式、寄生構造函數模式一樣instanceof失效,
保守的人永遠成不了氣候,所以這是一種方法,但不會是主流。
/*7.durable constructor pattern*/ function Person2(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(name); }; return o; } var friend = Person2("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas" /*tips: Be sure no this and new in your code except o=new Object(). */
紅皮書下面的章節是關於對象繼承的,個人覺得相比創建對象,對象繼承顯得更爲重要,不過前者是基礎。
很多OO都支持兩種繼承,分別是接口繼承和實現繼承,很遺憾ECMAScript無法實現接口繼承,它的繼承主要依靠原型鏈。
一 原型鏈繼承
關於原型鏈,我以前寫過一篇帖子 js原型鏈原理看圖說話 。在這篇帖子中介紹了每個實例中都默認擁有屬性prototype,在很多瀏覽器中與其對應的屬性叫__proto__。
學過鏈表的人很好理解,如果我要認某個人當爸爸,我只要把指針指向他,由於每個實例的鏈表中都只有一個prototype屬性,所以通過原型鏈只能認一個爸爸。
按這個邏輯,通過原型鏈應該無法實現多重繼承!寫個例子驗證一下。
<!DOCTYPE html> <html> <head> <title>apply,call,bind</title> <script type="text/javascript"> /*I often confused with the three function, this example get a deep research of them*/ function baseType1(){ this.name="tanya"; } function baseType2(){ this.age="28"; } function subType(){ } subType.prototype=new baseType2(); subType.prototype=new baseType1(); var instance = new subType(); alert(instance.name); alert(instance.age); </script> </head> <body> </body> </html>
果然,後一個原型的賦值會覆蓋前一個,也就是說通過原型鏈只能繼承離實例化最近的一個原型對象。
原型鏈繼承的本質就是一個單鏈表的深度搜索。
1 搜索實例
2 搜索subType.prototype
3 搜索baseType.prototype
一環一環,直到搜索在prototype屬性等於null時,纔會停下。
有幾點需要記住的:
1 所有的對象都繼承自Object對象,就像所有的DOM對象都繼承自window對象一樣,Object對象提供了很多我們喜歡的方法。
2 確定原型和實例的關係可以使用instanceof操作符和isPropertyOf()方法。
3 在原型鏈上重寫方法會屏蔽原來的方法。
4 通過原型鏈條實現繼承時,不能使用對象字面量創建原型方法。因爲對象字面量可以理解成一個實實在在的對象,對象可以成爲原型但是會切斷原型鏈。
5 由於js中的引用類型實際上保存的是一個指針,所以在原型鏈上任意一個引用類型值的原型屬性都會被所有實例共享。
下面的例子說明,如果是基本類型值不會被所有實例共享:
function baseType1(){ this.name="tanya"; } function subType(){ } subType.prototype=new baseType1(); var instance = new subType(); var instance2 = new subType(); instance2.name="ansel"; alert(instance.name); //tanya alert(instance2.name); //ansel
但若替換成引用類型,就相當於所有實例共享引用的屬性啦
function baseType1(){ this.name=["tanya","ansel"]; } function baseType2(){ this.age="28"; } function subType(){ } subType.prototype=new baseType1(); var instance = new subType(); var instance2 = new subType(); instance.name.push("xixi"); alert(instance.name);//tanya,ansel.xixi alert(instance2.name);//tanya,ansel,xixi
因爲原型鏈繼承中引用類型 有這種不分你我 不分彼此 的狀態,所以 constructor stealing技術被引入。
二 借用構造函數繼承(constructor stealing)
constructor stealing技術的基本思想是:在子類型構造函數的內部調用基類的構造函數,即在subType中調用 baseType.call(this)來實現繼承。
通過這種技術,可以在子類型的構造函數中向父類傳遞參數。並且在子類型內部複製引用類型,保證引用類型的不共享。
其本質就是在subType中調用 baseType.call(this) 在subType內部clone了baseType的所有的屬性和方法。看看下面的例子:
function baseType(){ this.name=["tanya","ansel"]; } function subType(){ baseType.call(this); } subType.prototype=new baseType(); var instance1 = new subType(); var instance2 = new subType(); instance1.name.push("xixi"); alert(instance1.name);//tanya,ansel.xixi alert(instance2.name);//tanya,ansel
但是這種技術的問題在於在超類型的原型中定義的方法對於子類型是不可見的,這就意味着所有的類型都只能使用構造函數模式。
三 組合繼承模式(原型鏈+借用構造函數)
最常用的辦法肯定是折衷的產物,於是結合了原型鏈的優勢和constructor stealing的優勢的組合繼承模式應運而生。
組合繼承模式有一個約定,那就是,用原型鏈實現對原型屬性和方法的繼承,而使用call或者apply來實現對實例屬性的繼承。
四 原型式繼承
原型繼承的本質是:
fuction object(o){ function F(){} F.prototype=o; return new F(); }
原型式繼承相當於複製父類型,但是如紅皮書上所說,僅僅是淺複製。因爲引用類型相當於複製了引用地址,和原型模式一樣引用類型的屬性會被共享。
在ES5中新增了Object.create()方法規範了原型式繼承。Object.create()的本質就是上面的obect(o)函數。
五 寄生式繼承
前文創建對象部分介紹了寄生式模式,其特點是和工廠模式幾乎一樣,但會用new創建對象,之所以說是寄生,因爲那種創建對象的模式一般用於爲已有的原生對象創建構造函數.
寄生式繼承的思路和寄生構造函數和工廠模式是一樣的,即創建一個僅用於封裝繼承過程的函數。
var tanya={ name:"tanya", age:28, friends:["ansel","sunny","funny"] } function createAnother(o){ var clone=o; o.sayHi=function(){alert("hello");} return clone; } var anotherPerson1=createAnother(tanya); var anotherPerson2=createAnother(tanya); anotherPerson1.friends.push("dire"); //anotherPerson2.friends.pop("funny"); anotherPerson1.sayHi(); alert(anotherPerson1.friends); alert(anotherPerson2.friends);