徹底弄懂原型、原型鏈以及繼承

javascript在創(chuàng)建一個(gè)對(duì)象的時(shí)候,默認(rèn)會(huì)為該對(duì)象創(chuàng)建一個(gè)prototype的屬性,該屬性的值是一個(gè)對(duì)象,即創(chuàng)建對(duì)象的原型。

下面我們通過一個(gè)例子了解原型的特性:

function Person(){

}

Person.prototype.name="tom"

var person1 = new Person()

console.log(person1.name) //tom

var person2 = new Person()

console.log(person2.name) //tom

上面的例子中,構(gòu)造函數(shù)Person的原型對(duì)象有一個(gè)自定義屬性name

所有的實(shí)例共享同一個(gè)原型對(duì)象,所以打印person1.name和person2.name都是tom

構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系:

每一個(gè)構(gòu)造函數(shù)(函數(shù)對(duì)象)都有一個(gè)prototype屬性,指向函數(shù)的原型對(duì)象;每一個(gè)原型對(duì)象都有一個(gè)constructor屬性,指向構(gòu)造函數(shù);每一個(gè)實(shí)例都有一個(gè)__proto__屬性,指向構(gòu)造函數(shù)的原型對(duì)象

那么原型對(duì)象、prototype對(duì)象、實(shí)例對(duì)象之間的關(guān)系圖下圖所示


上圖中prototype對(duì)象也是一個(gè)實(shí)例對(duì)象,他也有自己的原型對(duì)象,默認(rèn)原型的構(gòu)造函數(shù)是Object,加上prototype對(duì)象關(guān)系圖如下:

接下來我們來說一下繼承

1.原型鏈繼承

function?Animal()?{

??this.sleep?=?function()?{

????console.log('睡覺')

??}

}

function?Person(name)?{

??this.name?=?name

}

Person.prototype?=?new?Animal()

var?liming?=?new?Person('liming')

console.log(liming.name)

liming.sleep()

打印結(jié)果

2

睡覺

上述例子中,改變了Person構(gòu)造函數(shù)的原型指向,從而實(shí)現(xiàn)了繼承,讓Person的實(shí)例liming擁有了sleep的方法。上述例子的圖解如下:


缺點(diǎn):如果Animal實(shí)例中有引用類型的變量,這個(gè)引用變量被所有實(shí)例共享,一個(gè)實(shí)例更改之后會(huì)影響其他實(shí)例

如果我們將代碼改成

function?Animal()?{

??this.todoList?=?['坐車',?'開會(huì)',?'吃飯',?'回家']

??this.sleep?=?function()?{

????console.log('睡覺')

??}

}

function?Person(name)?{

??this.name?=?name

}

Person.prototype?=?new?Animal()

var?liming?=?new?Person('liming')

console.log(liming.name)

liming.todoList.splice(1,?1)

var?tom?=?new?Person('tom')

console.log(tom.todoList)


可以看到liming的代辦去掉了開會(huì),tom的代辦的開會(huì)也沒有了。而且沒辦法給Animal傳遞實(shí)例參數(shù)。所以有了構(gòu)造函數(shù)繼承

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

構(gòu)造函數(shù)繼承就是在子類的構(gòu)造函數(shù)中執(zhí)行父類的構(gòu)造函數(shù),并使用call改變父類的this指向子類,還可以使用call傳遞參數(shù),實(shí)現(xiàn)實(shí)例屬性,具體代碼如下

function?Animal(sort)?{

??this.sort?=?sort

??this.todoList?=?['坐車',?'開會(huì)',?'吃飯',?'回家']

??this.sleep?=?function()?{

????console.log('睡覺')

??}

}

function?Person(name,?sort)?{

??Animal.call(this,?sort)

??this.name?=?name

}

var?liming?=?new?Person('liming',?'people')

liming.todoList.splice(1,?1)

console.log('liming的todolist',?liming.todoList)

var?tom?=?new?Person('tom',?'people')

console.log(tom.sort)

console.log('tom的todolist',?tom.todoList)


? ? 結(jié)果展示:

上述例子在Person的構(gòu)造函數(shù)中執(zhí)行了Animal.call(this,sort)? 執(zhí)行力Animal構(gòu)造函數(shù)的代碼,并把this,指向當(dāng)前person實(shí)例,所以liming去掉了開會(huì)日程,但是并不影響tom的代辦列表。

原型關(guān)系圖解:(這個(gè)例子Person的原型指向并沒有改變,只是把Person的代碼拿來執(zhí)行了一遍。)


缺點(diǎn):

這種方法的缺點(diǎn)顯而易見,沒有實(shí)現(xiàn)繼承。

三、原型構(gòu)造函數(shù)組合繼承

function?Animal(sort)?{

??this.sort?=?sort

??this.todoList?=?['坐車',?'開會(huì)',?'吃飯',?'回家']

??this.sleep?=?function()?{

????console.log('睡覺')

??}

}

Animal.prototype.saySort?=?function()?{

??console.log(this.sort)

}

function?Person(name,?sort)?{

??Animal.call(this,?sort)

??this.name?=?name

}

Person.prototype?=?new?Animal()

var?liming?=?new?Person('liming',?'people')

liming.todoList.splice(1,?1)

console.log('liming的todolist',?liming.todoList)

var?tom?=?new?Person('tom',?'people')

tom.saySort()

顧名思義是前兩種方式的結(jié)合,在構(gòu)造函數(shù)的基礎(chǔ)上使用Person.prototype?=?new?Animal() 改變Person的prototype的指向。從而是可以訪問Animal原型上的方法saySort()

關(guān)系圖解:


可以清晰的看到 liming和tom都有自己的todolist 所以不會(huì)去原型上查找todolist屬性。

這種方法的缺點(diǎn):

每個(gè)實(shí)例都有一份自己的屬性,原型上也有一份屬性,雖然實(shí)例上有就不會(huì)去原型上查找,但是這種方法原型上的屬性和方法就沒有用,有些多余。

4.原型式繼承

創(chuàng)建一個(gè)名為F的構(gòu)造函數(shù),把 F的prototype設(shè)置為傳進(jìn)來的對(duì)象

var?person?=?{

??name:?'tom',

??sayName:?function()?{

????console.log(this.name)

??}

}

function?object(obj)?{

??function?F()?{}

??F.prototype?=?obj

??return?new?F()

}

var?person1?=?object(person)

person1.name?=?'jerry'

person1.sayName()

var?person2?=?object(person)

person2.name?=?'jack'

person1.sayName()

打印結(jié)果:


結(jié)果很明顯所有實(shí)例共享原型屬性

關(guān)系圖解:


缺點(diǎn):所有實(shí)例共享原型,而且沒有自己的實(shí)例屬性。

5.寄生式繼承

在原型式繼承的基礎(chǔ)上,封裝原型式繼承

function?create(obj,?suject)?{

??var?example?=?Object(obj)

??example.suject?=?suject

??return?example

}

var?person?=?{

??name:?'tom',

??sayName:?function()?{

????console.log(this.name)

??}

}

var?person1?=?create(person,?'游泳')

console.log(person1.suject)

person1.name?=?'jerry'

var?person2?=?create(person,?'拳擊')

console.log(person2.suject)

person2.sayName()

打印結(jié)果如下:


顯而易見,person1和person2擁有了自己的實(shí)例屬性,

Object(person) 的作用等同于原型式繼承 把person作為構(gòu)造函數(shù)的原型,所有的實(shí)例共享person的屬性和方法

6.寄生組合式繼承

就是把寄生式繼承和組合式繼承結(jié)合起來

function?inherite(parent,?child)?{

??var?pro?=?Object.create(parent.prototype)

??pro.construct?=?child

??child.prototype?=?pro

}

function?Animal(sort)?{

??this.sort?=?sort

??this.todoList?=?['坐車',?'開會(huì)',?'吃飯',?'回家']

??this.sleep?=?function()?{

????console.log('睡覺')

??}

}

Animal.prototype.saySort?=?function()?{

??console.log(this.sort)

}

function?Person(name,?sort)?{

??Animal.call(this,?sort)

??this.name?=?name

}

inherite(Animal,?Person)

var?liming?=?new?Person('liming',?'people')

liming.todoList.splice(1,?1)

console.log('liming的todolist',?liming.todoList)

var?tom?=?new?Person('tom',?'people')

tom.saySort()

console.log('tom的todolist',?tom.todoList)


打印結(jié)果


關(guān)系圖解:


這種方式就避免了再次創(chuàng)建Animal實(shí)例,sort todolist? sleep等屬性和方法也只存在在實(shí)例上,原型上已經(jīng)沒有了。

大多數(shù)的繼承都采用這種方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容