又是這個經典的問題,嗯,我先來寫個構造函數,而後實例化一個對象看看。segmentfault
function Person(name) { this.name = name } Person.prototype.eat = () => {console.log('eat')} Person.prototype.play = () => {console.log('play')} let Han = new Person('Han')
經過一系列打印發現了這樣的關係:性能優化
能夠看出實例對象沒有prototype(也就是原型),只有構造器才擁有原型。而全部的js對象都擁有__proto__(也就是隱式原型),這個隱式原型所指向的就是創造這個對象的構造器的原型。如實例Han的隱式原型指向了其構造函數(Person)的原型;Person的隱式原型指向了Function的原型;而原型自身也有隱式原型,指向了Object的原型。函數
有點繞口,其實就是經過隱式原型能夠向上找到是誰構造了本身,而且若是本身沒有相應的屬性或者方法,能夠沿着這條原型鏈向上找到最近的一個屬性或方法來調用。如Han.eat(),其實是調用了Han.__proto__.eat(),把構造器Person的原型的eat方法給拿來用了;再如Han.hasOwnProperty('name'),其實是調用了Han.__proto__.__proto__.hasOwnProperty('name'),由於Han本身沒hasOwnProperty這方法,就經過隱式原型向上找到了Person的原型,發現也沒這方法,就只能再沿着Person的原型的隱式原型向上找到了Object的原型,嗯而後發現有這方法就拿來調用了。性能
全部構造函數都有本身的原型(prototype),而原型必定有constructor這麼個屬性,指向構造函數自己。也就是告訴你們這個原型是屬於本構造函數的。優化
能夠看出Person這個構造函數是由Function建立出來的,而咱們看下Function的隱式原型,居然是指向了Function的原型,也就是Function也是由Function建立出來的。很繞是否是,咱們先無論,繼續溯源下去,再看下Function的原型的隱式原型,指向的是Object的原型,繼續往上找Object的原型的隱式原型,嗯終於結束了找到的是null,也就是Object的原型是原型鏈上的最後一個元素了。this
接下來看下Object,Object是由Function建立出來的,而Function的隱式原型的隱式原型是Object的原型也就是Function經過原型鏈能夠向上找到Object的原型,二者看起來是你生我我生你的關係,這裏也就引用比較好懂的文章來解釋下: 從Object和Function說說JS的原型鏈spa
Object
JavaScript中的全部對象都來自Object;全部對象從Object.prototype繼承方法和屬性,儘管它們可能被覆蓋。例如,其餘構造函數的原型將覆蓋constructor屬性並提供本身的toString()方法。Object原型對象的更改將傳播到全部對象,除非受到這些更改的屬性和方法將沿原型鏈進一步覆蓋。Function
Function 構造函數 建立一個新的Function對象。 在 JavaScript 中, 每一個函數實際上都是一個Function對象。prototype---- 來自mozilla3d
接下來講下構造函數實例化對象到底作了些啥,其實看也能看出來了。code
let Jan = {} Person.call(Jan, 'Jan') Jan.__proto__ = Person.prototype
一、建立一個空對象。
二、將構造函數的執行對象this賦給這個空對象而且執行。
三、把對象的隱式原型指向構造函數的原型。
四、返回這個對象
是的就是這樣,next page!
function Person(name) { this.name = name this.skills = ['eat', 'sleep'] } Person.prototype.say = ()=> {console.log('hi')} function Boss() {} Boss.prototype = new Person() let Han = new Boss()
原理就是這樣👇
子構造函數的原型指向了父構造函數的實例對象,所以子構造函數的實例對象能夠經過原型鏈找到父構造函數的原型方法和類屬性。
優勢:
全部實例對象均可以共享父構造函數的原型方法。
缺點:
一、父構造函數的引用屬性也被共享了,至關於全部的實例對象只要對自身的skills屬性進行修改都會引起共振,由於其實修改的是原型鏈上的skills屬性。固然對skills從新賦值能夠擺脫這一枷鎖,至關於自身新建了skills屬性來覆蓋了原型鏈上的。
二、實例化時沒法對父構造函數傳參。
三、子構造函數原型中的constructor再也不是子類自身,而是經過原型鏈找到了父類的constructor。
function Person(name) { this.name = name this.skills = ['eat', 'sleep'] } Person.prototype.say = ()=> {console.log('hi')} function Boss(name) { Person.call(this, name) } let Han = new Boss('Han')
原理就是父構造函數把執行對象賦給子構造函數的實例對象後執行自身。
優勢:
一、實例化時能夠對父構造函數傳參。
二、父類的引用屬性不會被共享。
三、子構造函數原型中的constructor仍是自身,原型沒有被修改。
缺點:
每次實例化都執行了一次父構造函數,子類不能繼承父類的原型,若是把父類原型上的方法寫在父類的構造函數裏,雖然子類實例對象能夠調用父類的方法,但父類的方法是單獨加在每一個實例對象上,會形成性能的浪費。
結合了原型鏈繼承和構造函數繼承兩種方法。
function Person(name) { this.name = name this.skills = ['eat', 'sleep'] } Person.prototype.say = ()=> {console.log('hi')} function Boss(name, age) { Person.call(this, name) this.age = age } Boss.prototype = new Person() Boss.prototype.constructor = Boss Boss.prototype.sleep = ()=> {console.log('sleep')} let Han = new Boss('Han', 18)
看起來是完美解決了一切。但就是👇
實例化的對象其實是用構造函數繼承的方法往本身身上加屬性從而覆蓋原型鏈上的相應屬性的,既然如此,爲何不直接那父構造器的原型加到子構造器的原型上呢?這樣就不會出現那多餘的父類實例化對象出來的屬性了。
function Person(name) { this.name = name this.skills = ['eat', 'sleep'] } Person.prototype.say = ()=> {console.log('hi')} function Boss(name, age) { Person.call(this, name) this.age = age } Boss.prototype = Person.prototype //modified Boss.prototype.constructor = Boss Boss.prototype.sleep = ()=> {console.log('sleep')} let Han = new Boss('Han', 18)
看起來非常完美,反正效果是達到了,性能優化也是最佳。但問題就是這樣一點繼承關係都看不出來啊,父類和子類的原型徹底融合在一塊了,一點都不嚴謹。
因此最優的繼承方式應該是。。。
function Person(name) { this.name = name this.skills = ['eat', 'sleep'] } Person.prototype.say = ()=> {console.log('hi')} function Boss(name, age) { Person.call(this, name) this.age = age } Boss.prototype = Object.create(Person.prototype) Boss.prototype.sleep = ()=> {console.log('sleep')} Boss.prototype.constructor = Boss let Han = new Boss('Han', 18)
先看圖👇
其實跟組合繼承有點像,構造函數繼承部分和組合繼承的同樣就不說了。原型鏈那塊和原型鏈繼承有所不一樣的是原型鏈繼承是直接拿了父類的實例對象來做爲子類的原型,而這裏是用以父類的原型爲原型的構造函數實例化出來的對象做爲子類的原型(Object.create作的事情),完美避開了沒必要要的父類構造函數裏的東西。
Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。
至關於這樣👇
function create(proto) { function F() {} F.prototype = proto return new F() }
據說ES6的class extend也是這麼作的,更多的繼承細節能夠看看這篇文章,本繼承章節也參考了的👇
一篇文章理解JS繼承——原型鏈/構造函數/組合/原型式/寄生式/寄生組合/Class extends