Javascript實現繼承的幾種方式

1、構造函數模式

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function Person(name,age,job){

    this.name = name;

    this.age = age;

    this.job = job;

    this.sayName = function(){

        alert(this.name);

    };

}

 

varperson1 = new Person("Nicholas",29,"Software Engineer");

varperson2 = new Person("Greg",27,"Doctor");

 

person1.sayName();//"Nicholas"

person2.sayName();//"Greg"

 

alert(person1.constructor == Person);//true

alert(person2.constructor == Person);//true

 

alert(person1 instanceof Object);//true

alert(person1 instanceof Person);//true

alert(person2 instanceof Object);//true

alert(person2 instanceof Person);//true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

問題:

每個方法都要在每個實例上重新創建一遍。在前面的例子中,person1person2都有一個名爲sayName()的方法,但那兩個方法不是同一個Function的實例。

alert(person1.sayName ==person2.sayName);//false

然而,創建兩個完成同樣任務的Function實例的確沒有必要;況且有this對象在,根本不用在執行代碼前就把函數綁定到特定對象上面。因此,可以通過把函數定義轉移到構造函數外部來解決這個問題:

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function Person(name,age,job){

    this.name = name;

    this.age = age;

    this.job = job;

    this.sayName = sayName;

}

 

functionsayName(){

    alert(this.name);

}

 

varperson1 = new Person("Nicholas",29,"Software Engineer");

varperson2 = new Person("Greg",27,"Doctor");

 

alert(person1.sayName == person2.sayName);//true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

問題:在全局作用域中定義的函數實際上只能被某個對象調用,這讓全局作用域有點名不副實。更讓人無法接受的是:如果對象需要定義很多方法,那麼就要定義很多個全局函數,於是我們這個自定義的引用類型就絲毫沒有封裝性可言了。

2、原型模式

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function Person(){

}

Person.prototype.name = "Nicholas";

Person.prototype.age = 29;

Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){

    alert(this.name);

};

 

varperson1 = new Person();

person1.sayName();//"Nicholas"

varperson2 = new Person();

person2.sayName();//"Nicholas"

 

alert(person1.sayName == person2.sayName);//true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

理解原型:無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性。在默認情況下,所有prototype屬性都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。Person.prototype.constructor指向Person。創建了自定義的構造函數之後,其原型屬性默認只會取得constructor屬性;至於其他方法,則都是從object繼承而來的。當調用構造函數創建一個新實例後,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型屬性。在很多實現中,這個內部屬性的名字是_proto_。不過,要明確的真正重要一點,就是這個鏈接存在於實例與構造函數的原型屬性之間,而不是存在於實例與構造函數之間。

file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath002.jpg

alert(Person.prototype.isPrototypeOf(person1));//true

alert(Person.prototype.isPrototypeOf(person2));//true

每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性。如果在原型對象中找到了這個屬性,則返回該屬性的值。雖然可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。如果在實例中添加一個與原型中屬性同名的屬性,則該屬性會屏蔽原型中的那個屬性。添加的同名屬性只會阻止我們訪問原型中的那個屬性,但不會修改那個屬性。即使將這個屬性設置爲null,也只會在實例中設置這個屬性,而不會恢復其指向原型的鏈接。不過,使用delete操作符則可以完全刪除實例屬性,從而讓我們嫩鞏固重新訪問原型中的屬性。in操作符只要通過對象能訪問到屬性就返回truehasOwnProperty()只在屬性存在於實例中時才返回true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function Person(){

}

Person.prototype.name = "Nicholas";

Person.prototype.age = 29;

Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){

    alert(this.name);

};

 

varperson1 = new Person();

varperson2 = new Person();

 

alert(person1.hasOwnProperty("name"));//false

alert("name" in person1);//true

 

person1.name = "Greg";

alert(person1.name);//"Greg"

alert(person1.hasOwnProperty("name"));//true

alert("name" in person1);//true

 

deleteperson1.name;

alert(person1.name);//"Nicholas"

alert(person1.hasOwnProperty("name"));//false

alert("name" in person1);//true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

更簡單的原型語法:

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

Person.prototype = {

    name : "Nicholas",

    age : 29,

    job: "Software Engineer",

    sayName : function(){

         alert(this.name);

    }

};

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

在上面代碼中,我們將Person.prototype設置爲等於一個以對象字面量形式創建的新對象。本質上完全重寫了默認的prototype對象,因此constructor屬性也就變成了新對象的屬性指向Object構造函數,不再指向Person函數。儘管instanceof操作符還能返回正確的結果,但是constructor已經無法確定對象的類型了。如果constructor的值真的很重要,可以特意設置回適當的值:

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

Person.prototype = {

    constructor : Person,

    name : "Nicholas",

    age : 29,

    job: "Software Engineer",

    sayName : function(){

         alert(this.name);

    }

};

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

由於在原型中查找值的過程是一次搜索,因此我們對原型對象所作的任何修改都能夠立即從實例上反映出來,即使是先創建了實例後修改原型也照樣如此。比如新定義一個person.sayHi();,其原因可以歸結爲實例與原型之間鬆散連接的關係。但如果是重寫整個原型對象,那情況就不一樣。我們知道,調用構造函數時會爲實例添加一個指向最初原型的_proto_指針,而把原型修改爲另外一個對象就等於切斷了構造函數與最初原型之間的聯繫。

原型對象的問題:首先,它省略了爲構造函數傳遞初始化參數這一環節,結果所有實例在默認情況下都將取得相同的屬性值。這還不是最大的問題,最大的問題是由其共享的本性所導致的。對於包含引用類型值得屬性來說,就會出現嚴重問題。

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function Person(){

}

Person.prototype = {

     constructor : Person,

     name : "Nicholas",

     age : 29,

     job : "Software Engineer",

     friends :["Shelby","Court"],

     sayName : function(){

           alert(this.name);

     }

};

 

varperson1 = new Person();

varperson2 = new Person();

 

person1.friends.push("Van");

 

alert(person1.friends);//"Shelby,Court,Van"

alert(person2.friends);//"Shelby,Court,Van"

alert(person1.friends === person2.friends);//true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

3、組合使用構造函數模式和原型模式

構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每個實例都會有自己的一份實例屬性和副本,但同時又共享這對方法的引用,最大限度地節省了內存。另外,這種混成模式還支持向構造函數傳遞參數,可謂是集兩種模式之長。這種模式是目前在ECMAScript中使用最廣泛、認同度最高的一種創建自定義類型的方法。

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function Person(name,age,job){

     this.name = name;

     this.age = age;

     this.job = job;

     this.friends = ["Shelby","Court"];

}

 

Person.prototype = [

    constructor : Person,

    sayName : function(){

         alert(this.name);

    }

}

 

varperson1 = new Person("Nicholas",29,"Software Engineer");

varperson2 = new Person("Greg",29,"Doctor");

 

person1.friends.push("Van");

alert(person1.friends === person2.friends);//false

alert(person1.sayName === person2.sayName);//true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

 

4、原型鏈

原型鏈:ECMAScript中描述了原型鏈的概念,並將原型鏈作爲實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。簡單回顧一下構造函數、原型、實例的關係:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那麼,假如我們讓原型對象等於另一個類型的實例,結果會怎麼樣呢?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含着一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function SuperType(){

    this.property = true;

}

 

SuperType.prototype.getSuperValue =  function(){

     return this.property;

};

 

functionSubType(){

   this.subproperty = false;

}

 

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){

    return this.subproperty;

};

 

varinstance = new SubType();

alert(instance.getSuperValue());//true

alert(instance instanceof Object);//true

alert(instance instanceof SuperType);//true

alert(instance instanceof SubType);//true

 

alert(Object.prototype.isPrototypeOf(instance));//true

alert(SuperType.prototype.isPrototypeOf(instance));//true

alert(SubType.prototype.isPrototypeOf(instance));//true

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath003.jpg

子類型有時候需要重寫超類型中的某個方法,或者需要添加超類型中不存在的某個方法。給原型添加方法的代碼一定要放在替換原型的語句之後。還有一點需要注意的是,通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。因爲這樣做就會重寫原型鏈。

原型鏈的問題:1、包含引用類型值的原型會被所有實例共享;2、不能向超類型的構造函數中傳遞參數。實踐中很少會單獨使用原型鏈。

 

5、組合繼承

組合繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數複用,又能夠保證每個實例都有它自己的屬性。

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function SuperType(name){

    this.name = name;

    this.colors = ["red","blue","green"];

}

 

SuperType.prototype.sayName = function(){

     alert(this.name);

};

 

functionSubType(name,age){

    SuperType.call(this,name);

    this.age = age;

}

 

//繼承方法

SubType.prototype = new SuperType();

SubType.prototype.sayAge = function(){

    alert(this.age);

};

 

varinstance1 = new SubType("Nicholas",29);

instance1.colors.push("black");

alert(instance1.colors);//"red,blue,green,black"

 

instance1.sayName();//"Nicholas";

instance1.sayAge();//29

 

varinstance2 = new SubType("Greg",27);

alert(instance2.colors);//"red,blue,green"

instance2.sayName();//"Greg";

instance2.sayAge();//27

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

組合繼承成爲javascript中最常用的繼承模式,而且,instanceof()isPrototypeOf()也能夠用於識別給予組合繼承創建的對象。

 

6、原型式繼承

道格拉斯克羅克福德2006年寫了一篇文章,介紹了一種實現繼承的方法,這種方法沒有使用嚴格意義上的構造函數。他得想法是藉助原型可以基於已有得對象創建新對象,同時還不必因此創建自定義類型。爲了達到這個目的,他給出瞭如下函數:

function object(o){

    function F(){}

    F.prototype = o;

    return new F();

}

object()函數內部,先創建了一個臨時性的構造函數,然後將傳入的對象作爲這個構造函數的原型,最後返回了這個零食類型的一個新實例。從本質上講,object()對傳入其中的對象執行了一次淺複製。看下面的例子:

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

var person = {

    name : "Nicholas",

    friends:["Shelby","Court","Van"]

};

 

varanotherPerson = object(person);

anotherPerson.name = "Greg";

anotherPerson.friends.push("Rob");

 

varyetAnotherPerson = object(person);

yetAnotherPerson.name = "Linda";

yetAnotherPerson.friends.push("Barbie");

 

alert(person.friends);//"Shelby,Court,Van,Rob,Barbie"

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

 

7、寄生式繼承

寄生式繼承時與原型式繼承緊密相關的一種思路,同樣由克羅克福德推出的。寄生式繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最後再像真地是它做了所有工作一樣返回對象。

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function createAnother(original){

    var clone = object(original);//通過調用函數創建一個新對象

   clone.sayHi = function(){//以某種方式增強這個對象

       alert("hi");

    };

    return clone;//返回這個對象

}

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

 

8、寄生組合式繼承

前面說過,組合繼承是javascript最常用的繼承模式,不過,它也有自己的不足。組合繼承最大的問題就是無論什麼情況下,都會調用兩次超類型構造函數:一次是再創建子類型原型的時候,另一次是在子類型構造函數內部。沒錯,子類型最終會包含超類型對象的全部實例屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。再來看一看下面組合繼承的例子:

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function SuperType(name){

    this.name = name;

    this.colors = ["red","blue","green"];

}

 

SuperType.prototype.sayName = function(){

    alert(this.name);

};

 

functionSubType(name,age){

    SuperType.call(this.name);//第二次調用SuperType()

    this.age = age;

}

 

SubType.prototype = new SuperType();//第一次調用SuperType()

 

SubType.prototype.sayAge = function(){

   alert(this.age);

};

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

第一次調用SuperType構造函數時,SubType.prototype會得到兩個屬性:namecolors,它們都是SuperType的實例屬性,只不過現在位於SubType的原型中。當調用SubType構造函數時,又會調用一次SuperType構造函數,這一次又在新對象上創建了實例屬性namecolors。於是,這兩個屬性就屏蔽了原型中的兩個同名屬性。

file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath004.jpg

圖中,有兩組namecolors屬性:一組在實例上,一組在SubType原型中。這就是調用兩次SuperType構造函數的結果。好在我們已經找到了解決這個問題方法——寄生組合式繼承。所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成行駛來繼承方法。其背後的基本思想是:不必爲了制定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然後再將結果指定給子類型的原型。寄生組合式繼承的基本模式如下所示:

function inheritPrototype(subType,superType){

     var prototype = object(superType.prototype);//創建對象

    prototype.constructor = subType;//增強對象

    subType.prototype = prototype;//指定對象

}

這個示例中的inheritPrototype()接收兩個參數:子類型構造函數和超類型構造函數。在函數內部,第一步是創建超類型原型的一個副本。第二步是爲創建的副本添加constructor屬性,從而彌補因重寫原型而失去的默認的constructor屬性。最後一步,將新創建的對象(即副本)賦值給子類型的原型。這樣,我們就可以調用inheritPrototype()函數的語句,去替換前面例子中爲子類型原型賦值的語句了。

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function SuperType(name){

   this.name = name;

   this.colors = ["red","blue","green"];

}

 

SuperType.prototype.sayName = functon(){

   alert(this.name);

};

 

functionSubType(name,age){

   SuperType.call(this,name);

   this.age = age;

}

 

inheritPrototype(SubType,SuperType);

 

SubType.prototype.sayAge = function(){

   alert(this.age);

};

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

這個例子的高效率體現在它只調用了一次SuperType構造函數,並且因此避免了在SubType.prototype上面創建不必要的,多餘的屬性。於此同時,原型鏈還能保持不變,因此,還能夠正常使用instanceofisPrototypeOf()。開發人員普遍認爲寄生組合式繼承時引用類型最理想的繼承範式。在雅虎的YUI框架的YAHOO.lang.extend()中,實現了寄生調用繼承。

下面是寄生組合式繼承的一個完整的例子:

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function object(o) {

    function F(){}

    F.prototype = o;

    return new F();

}

functioninheritPrototype(subC, superC) {

    var prototype = object(superC.prototype); //創建對象

   prototype.constructor = subC; //增強對象

   subC.prototype = prototype; //指定對象

}

varBaseClass = function() {

    this.className = "Base";

};

BaseClass.prototype = {

    showName: function() {

        alert(this.className);

    }

};

varSubClass = function() {

    BaseClass.call(this);

    this.classDesc = "SubClass";

};

inheritPrototype(SubClass, BaseClass);

SubClass.prototype.showDesc = function() {

    alert(this.classDesc);

};

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

 

9、拷貝組合式繼承

這個繼承模式是來自博客園的村長趙大寶的一篇文章——《尋求完美之javascript繼承》。改造的實質就是拋棄使用父類原型副本重寫子類原型的做法。這種方式不僅兼顧了效率、繼承實現、多態,而且不創建父類原型副本,即不發生原型的重寫,避免了寄生組合繼承存在的缺陷。另外,拷貝組合繼承顯而易見的對多重繼承的實現提供了更優的支持。

[url=]file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]

function extend(subC, baseC) {

    for (var ptototypeName in baseC.prototype) {

        if (typeof(subC.prototype[ptototypeName])=== 'undefined') {

            subC.prototype[ptototypeName]= baseC.prototype[ptototypeName]; //原型屬性的拷貝

        }

    }

    subC.prototype.constructor = subC; //增強

}

varBaseClass = function() {

    this.className = "Base";

};

BaseClass.prototype = {

    showName: function() {

        alert(this.className);

    }

};

varSubClass = function() {

    BaseClass.call(this); //只執行一次父類構造函數

    this.classDesc ="SubClass";

};

SubClass.prototype = {

    showDesc: function() {

        alert(this.classDesc);

    }

};

extend(SubClass, BaseClass); //不破壞子類原型鏈的位置二

[url=]

file:///C:/Users/i037145/AppData/Local/Temp/msohtmlclip1/01/clip_p_w_picpath001.gif[/url]


更多學習分享加羣      105601600

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章