前段時間,在面試中不停的會被提到繼承。做一個關於繼承的總結。關於對象之間的5中繼承方式
若現在有對象“A”的構造函數:
function A(){ this.species = "我是A"; }
還有一個對象“B”的構造函數:
function B() { this.name = "我是B"; }
如何操作,可以使得B繼承到A中的方法?(很容易我們知道A相當於是父對象,B是子對象)
一、構造函數繼承:使用call/apply方法,即:將父對象的構造函數綁定在子對象中。其實就是在子對象添加call/apply。
function B(){ //使用call繼承 A.call(this); //使用apply繼承: A.apply(this,arguments); this.name = "我是B"; } var b = new B(); console.log(b.species); //我是A console.log(b.name); //我是B
二、prototype模式:將子對象的prototype屬性指向A,則B的實例中就有瞭A。
B.prototype = new A(); B.prototype.constructor = B; var b = new B(); console.log(b.species); //我是A console.log(b.name); //我是B
首先在代碼的第一行中,使用B.prototype = new A(),將B的prototype屬性指向瞭A的實例。也就是說完全刪除瞭prototype對象原先值,重新加值。 在第二行中B.prototype.constructor = B是因為在原型的概念中,我們都知道任何一個函數都會有一個prototype屬性。而他們的prototype屬性都會有一個constructor屬性,指向其的構造函數。通過讓B的constructor重新指回原構造函數,防止造成繼承紊亂。 如果在編程中替換瞭prototype對象,那麼下一步就需要在新的prototype對象加上constructor屬性,並將這個屬性指回原來的構造函數。
三、直接繼承prototype:改寫A對象,將不變的對象都直接寫入A.prototype,所以可以通過B跳過A,直接繼承A.prototype(改進上一中方法的繼承方式)。
首先改寫A對象:
function A(){ this.a = "B訪問不到我" } A.prototype.aa = "B可以訪問到我";
再將B的prototype指向A的prototype
B.prototype = A.prototype; B.prototype.constructor = B; var bb = new B(); console.log(bb.a); //undefined console.log(bb.aa); //B可以訪問到我
使用這種模式的繼承,優點是可以將b不需要繼承到的東西放在構造函數內部使得實例化後的B無法訪問,而節省瞭內存。缺點是因為B.prototype和A.prototype指向瞭同一個對象,所以會造成如果修改瞭B.prototype 都會影響到A.prototype。可以在上面的代碼之後加入一句話判斷是否是這樣子的:
console.log(A.prototype.constructor); //B
四、利用空對象繼承:改進上一個。不會污染到父對象。
var F = function(){}; F.prototype = A.prototype; B.prototype = new F(); B.prototype.constructor = B;
新申請的F是空對象,所以幾乎不占內存。這時,修改B的prototype對象,就不會影響到A的prototype對象瞭。
五、拷貝繼承:將父對象的所有屬性和方法都拷貝在子對象上。
上面的都是采用prototype對象,實現繼承。我們也可以換一種思路,純粹采用”拷貝”方法實現繼承。簡單說,如果把父對象的所有屬性和方法,拷貝進子對象,也就可以實現繼承。
和之前的一樣,將不需要B去繼承的還是放在A的內部 將A的屬性都放在A.prototype上 再寫一個可以實現拷貝的目的的函數。這個函數的作用是將父級對象的prototype屬性,拷貝在子對象的prototype上。
function extend(Child,Parent){ var p = Parent.prototype; var c = Child.prototype; for (var i in p){ c[i] = p[i]; } c.uber = p; }
使用方式:
extend(B,A); var b = new B();