javascript面向?qū)ο螅üP記)系列: 構(gòu)造函數(shù)繼承

繼承

原型鏈

構(gòu)造函數(shù)被new 操作符實例化一個對象之后,對象中會有一個秘密鏈接。(非IE內(nèi)核瀏覽器這個鏈接叫__proto__),通過這個鏈接我們可以從實例對象中查找屬性和方法。而原型對象中本身也包含了指向其原型的鏈接,由此形成的一個鏈條,既原型鏈。

456.png

比如像下面這樣查詢一個屬性的過程,
function A() { this.hairColor = "blue"; } function B() { this.height = "fat"; } function C() { this.name = "Child"; } B.prototype = new A(); C.prototype = new B(); var d = new C(); d.hairColor //blue
首先查詢d這個實例對象中的所有屬性,沒有找到,繼而去查找d.__proto__指向的對象,即new出來的B實例對象。重復(fù)這個過程在A中找到了,立即打印在瀏覽器中。

繼承模式

實現(xiàn)繼承的模式,可以分為倆類

  • 基于構(gòu)造函數(shù)的繼承
  • 基于對象的繼承

一、 構(gòu)造函數(shù)繼承

1. 原型鏈繼承

ECMAScript 默認(rèn)的繼承方法。
function Cat() { this.color = "blue"; } Cat.prototype.getColor = function() { console.log (this.color); };
function Dog() { this.height = "150cm"; } //繼承 Dog.prototype = new Cat(); Dog.prototype.consctructor = Dog; Dog.prototype.getHeight = function() { console.log (this.height); };

var animal = new Dog();
animal.getColor();
animal.getHeight();

給原型添加方法的代碼一定要放在替換原先的語句之后。
缺點:

  • 當(dāng)原型對象中有引用類型的值時,屬性會被所有實例所共享。修改其中一個實例對象,會在所有實例中反應(yīng)出來。
  • 給父類型構(gòu)造函數(shù)傳遞參數(shù)會影響所有實例。
    不推薦單獨使用原型鏈繼承模式。
2. 借用構(gòu)造函數(shù)(偽造對象或經(jīng)典繼承)

利用函數(shù)的apply()或者call方法,在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。
function Cat() { this.color = ["red", "blue", "green"]; } function Dog() { Cat.call(this); }

    var animal1 = new Dog();
    animal1.color.push("yellow");
    console.log(animal1.color);    // ["red", "blue", "green", "yellow"]
  

    var animal2 = new Dog();
    console.log(animal2.color);  // ["red", "blue", "green"]

這種方式通過call()apply()在實例化對象的時候,調(diào)用父類構(gòu)造函數(shù)Cat()來初始化實例的屬性和方法,這樣每一個實例都能擁有自己的引用類型的副本了。而不在是實例共享一個引用對象。

function Cat(name) { this.name = name; }

function Dog() { Cat.call(this, "張三"); this.age = "29"; }

    var animal3 = new Dog();
    animal3.name;    //"張三"
    animal3.age      //"29"

優(yōu)點: 可以再子類型構(gòu)造函數(shù)中向父類構(gòu)造函數(shù)傳遞參數(shù)。
缺點: 方法都在構(gòu)造函數(shù)中定義,函數(shù)無法復(fù)用。
不推薦單獨使用原型鏈繼承模式。

3. 組合繼承(偽經(jīng)典繼承)

使用原型鏈實現(xiàn)對原型屬性和方法的繼承,通過借用構(gòu)造函數(shù)實現(xiàn)對實例屬性的繼承。

定義父類構(gòu)造函數(shù) function Man(name) { this.name = name; this.friends = ["mark", "david", "july"]; }

在父類原型對象中定義方法 Man.prototype.sayName = function() { console.log(this.name); };

function Woman(name, age) { Man.call(this, name); //繼承屬性 this.age = age; //定義自有屬性 }

Woman.prototype = new Man(); //繼承再原型中定義的方法 Woman.prototype.constructor = Woman; //讓consctructor指向正確 Woman.prototype.sayAge = function() { //定義自己的原型方法 console.log(this.age); };

這樣不同的實例擁有自己的屬性, 又可以使用共享的方法
var person = new Woman("marry", 24); person.friends.push("Joe"); console.log(person.friends); // ["mark", "david", "july", "Joe"] person.sayName(); // marry person.sayAge(); // 24

var person1 = new Woman("Nip", 30); console.log(person1.friends); // ["mark", "david", "july"] person1.sayName(); // Nip person1.sayAge(); // 30

4. 只繼承于原型

由于原型中的所有代碼都是共享的,這意味這Woman.prototype = Man.prototype繼承于原型,比 Woman.prototype = new Man();繼承于構(gòu)造函數(shù)的實例,更加效率,因為中間省去了到new Man()實例對象查找這一步。.也不用實例化父類對象了。

function Man() {} Man.prototype = { constructor: Man, friends: ["mark", "david", "july"], };

function Woman(name, age) { Man.call(this, name); this.age = age; }
Woman.prototype = Man.prototype;
Woman.prototype.getAge= function() { console.log(this.age); };

缺點: 由于引用的是一個同一個原型,對子對象的原型經(jīng)行修改,父對象也會跟著改變,所有有用繼承關(guān)系的都是如此。
var person1 = new Woman(“bob", 25); console.log(person.friends) // ["mark", "david", "july"]

var person2 = new Woman(“marry", 28); console.log(person.friends) // ["mark", "david", "july"]

并且對象的conctructor屬性的值也是一樣的。
Man.prototype.constructor === WoMan.prototype.constructor //true

5. 臨時構(gòu)造器 newF()

這種模式是對第三種模式的一種改進(jìn),既然所有屬性都指向了一個相同的對象,父對象就會受到子對象屬性的影響。要避免這種缺點,可以用一個臨時的構(gòu)造器來替代。
var F = function() {};

F.prototype = Man.prototype;

Woman.prototype = new F();

Woman.prototype.constructor = Woman;
通過這種方法,我們可以再保持原型鏈的基礎(chǔ)上使父對象的屬性擺脫對子對象的影響了;

我們可以把之前的這個臨時構(gòu)造器封裝起來,方便使用:
function extend(Child, Parent) { var F = function () {}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; }
可以像下面這樣使用:
extend(Woman, Man);

var person = new Woman("mark", 25);

console.log(person.friends) // ["mark", "david", "july"]

這也是Yui庫的實現(xiàn)方法

Child.uber = Parent.prototype;

這句話的意思是為子對象設(shè)一個uber屬性,這個屬性直接指向父對象的prototype屬性。可以直接調(diào)用父對象的方法。

6. 屬性拷貝

在構(gòu)建可重用的繼承代碼之前,我們可以見簡單的將父對象的屬性拷貝給子對象。根據(jù)之前的extend函數(shù)可以創(chuàng)建一個函數(shù),該函數(shù)接受2個構(gòu)造器函數(shù)。將父類的原型屬性全部拷貝 給子類原型。
function extend2(Child, Parent) {
var p = Parent.prototype;

var c = Child.prototype;

for(var i in p) { c[i] = p[i]; }
c.uber = p;
}
這個方法與之前的相比,效率較低,這里執(zhí)行的是子對象原型的逐一拷貝,而非簡單的查詢。這種方式僅適用于簡單類型值的對象。引用類型的值不可復(fù)制,只支持復(fù)制指針。指向的還是原來的對象。

確定原型和實例關(guān)系的方法
  1. instanceof(),用來確定實例是不是某個原型對象的實例。可以像這樣使用
    console.log (animal instanceof(Dog)); //true console.log (animal instanceof(Cat)); //true

  2. isPrototypeOf() 在原型鏈中出現(xiàn)過的原型,都會返回true

``
console.log (Dog.prototype.isPrototypeOf(animal)); //true
console.log (Cat.prototype.isPrototypeOf(animal)); //true
console.log (Object.prototype.isPrototypeOf(animal)); //true

``

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

推薦閱讀更多精彩內(nèi)容