JavaScript物件導向之二(建構函式繼承)

Pomelo1213發表於2019-02-15

學習一波阮一峰的部落格 戳這裡

部落格中是自己的理解,以及對大佬描述不清楚的地方進行了修正,也算是自己的一個再(xiao)產(tu)出(cao)吧

上一篇:JavaScript物件導向之一(封裝)

建構函式進行繼承


先來看個簡單的:

function Animal(){
    this.type = `animal;`
}

function Cat(name, color){
    this.name = name
    this.color = color
    //這裡用call,個人覺得更好些
    Animall.call(this)
}

var cat = new Cat(`po`, `orange`)
console.log(cat.type) //animal
複製程式碼

建立了一個Animal和Cat建構函式,然後在Cat裡面呼叫Animal的建構函式,在將Cat例項化,就可以訪問到Animal的屬性了。
這個例子顯然有問題,放在第一個就是用來找茬的。

什麼問題呢?假如我想使用在Animal上的公共方法,像這樣Animal.prototype.eat = function(){ console.log(`animal eat`) },用cat.eat()是訪問不到的。

為什麼呢?因為我們Cat和Animal的原型根本就沒有關聯起來呀。你看看我們們上面的程式碼,那個地方關聯過?

使用原型進行繼承


那下面我們就將兩者的原型關聯起來試試看

function Animal(){
    this.type = `animal;`
}
Animal.prototype.eat = function(){ console.log(`animal eat`) }

function Cat(name, color){
    this.name = name
    this.color = color
}

Cat.prototype = new Animal()

var cat = new Cat(`po`, `orange`)
console.log(cat.type) //animal
console.log(cat.eat()) //animal eat
複製程式碼

這種方法好!可以拿到屬性和方法,一舉兩得。但是,這裡有個陷阱(keng)!!!Cat.prototype = new Animal()之後,我們的Cat.prototype裡面的所有方法都消失了!這是怎麼回事?因為new,new做了四件事,這裡再次回顧一下:

var temp = {}
temp.__proto__ = Animal.prototype
Animal.call(temp)
return temp
複製程式碼

看到了嗎?new會使用一個空物件並且將其返回。這樣一來我們的Cat.prototype就被清空了(包括自身的constructor)。所以我們需要自己多做一件事Cat.prototype.constructor = Cat

如果我們不這樣做,那麼Cat.prototype的建構函式會是什麼?我們在去看看new做的第二件事,這個空物件指向了Animal.prototype,所以Cat.prototype自身如果沒有constructor屬性的話就會去Animal.prototype上面去找。Cat.prototype.constructor === Animal //true

所以,如果我們替換了prototype,需要手動去糾正它。

直接繼承prototype


既然上面這種方法有坑,而且的的確確讓你很容易漏掉,那我們改進一下:

function Animal(){}
Animal.prototype.eat = function(){ console.log(`animal eat`) }

Cat.prototype = Animal.prototype
Cat.prototype.constructor = Cat

var cat = new Cat(`po`, `orange`)
console.log(cat.eat()) //animal eat
複製程式碼

既然我們想從Animal.prototype上面那東西,直接從上面拿不就行了?而且我還機智的填了上面會出現的坑。同時結果也是我想要的。

但是!!我們的Cat.prototype和Animal.prototype指向的是同一個原型,這會導致我在Cat.prototype上做了什麼事,會同時發生在Animal.prototype上。這是為什麼呢?MDZZ,這兩個就是同一個東西呀,原型是堆中一塊記憶體,Cat和Animal都指向這塊記憶體,操作的是同一個東西,怎麼會不影響?

與此同時,自以為聰明的填坑Cat.prototype.constructor = Cat,此時的Cat和Animal的原型是同一個,修改了constructor之後,導致Animal的constructor變成了Cat。這種方法果斷PASS。。。

利用空物件作為中介


var F = function(){}
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat
複製程式碼

我們使用中間物件進行過度,巧妙的將Cat.prototype和Animal.prototype解耦,這樣就不會出現問題了。仔細觀察會發現這個和new做的事情有異曲同工之處。

我們進行封裝一下在使用

function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
}

extend(Cat,Animal);
var cat1 = new Cat(`po` `orange`);
alert(cat1.eat()); // animal eat
複製程式碼

這裡Child.uber = Parent.prototype的意思類似於__proto__

拷貝繼承


這裡還有一種簡單粗暴的方式

function extend2(Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
}

function Animal(){}
Animal.prototype.eat = function(){ console.log(`animal eat`) }

extend2(Cat, Animal);
var cat1 = new Cat(`po` `orange`);
alert(cat1.eat()); // animal eat
複製程式碼

直接遍歷父類的原型,一個個複製到子類原型上即可。

感慨一下,大佬還是大佬呀。。。

相關文章