對象屬性 | __proto__ |
prototype |
---|---|---|
普通對象 | √ | × |
函數對象 | √ | √ |
function Person(name,age){ this.name = name; this.age = age; Person.prototype.say = function(){ console.log(`my name is ${this.name} and ${this.age} years`) } } let p1 = new Person('pfzz',25)
p1是普通對象
Person則是函數對象java
// prototype是函數對象的屬性,其本質是 一個對象,包含了__proto__與constructor, // 只看普通對象p1,p1只有__proto__,而且指向建立它的函數對象的prototype console.info(p1) console.info(p1.__proto__ === Person.prototype) console.info(p1.__proto__.constructor === Person) console.info(p1.__proto__.__proto__ === Object.prototype) console.info(p1.__proto__.__proto__.constructor === Object) console.info(p1.__proto__.__proto__.__proto__ === null) // 函數對象Person,有__proto__、prototype屬性 console.info(Person.__proto__ === Function.prototype) console.info(Person.__proto__.constructor === Function) console.info(Person.__proto__.__proto__ === Object.prototype) console.info(Person.__proto__.__proto__.constructor === Object) console.info(Person.__proto__.__proto__.__proto__ === null) console.info(Person.prototype.constructor === Person) console.info(Person.prototype.__proto__ === Object.prototype) console.info(Person.prototype.__proto__.constructor === Object) console.info(Person.prototype.__proto__.__proto__ === null) // 綜上具備在指向的只有__proto__與constructor屬性, // 而constructor屬性老是指向prototype所在的函數對象,造成閉環。 // __proto__最終老是指向null
由上可知javasript的繼承就要靠__proto__來實現es6
// 一、類式繼承 function Child1(name,age,sex){ this.sex = sex; const prototype = new Person(name,age); prototype.play = function(){ console.log(`my name is ${this.name} and ${this.age} years and ${this.sex}`) }; this.__proto__ = prototype } const kind1 = new Child1('pfzzz',25,'man') kind1.say() console.info(kind1) console.info(kind1.__proto__ !== Child1.prototype) 缺點:繼承屬性被放在原型鏈上,致使kind1.__proto__ !== Child1.prototype
// 二、構造函數式繼承 function Child2(name,age,sex){ this.sex = sex; Person.call(this,name,age) } const kind2 = new Child2('pfzzz',25,'man') console.info(kind2) kind2.say() // 報錯 缺點:原型鏈上的方法沒有被繼承
// 三、組合式繼承 function Child3(name,age,sex){ this.sex = sex; Person.call(this,name,age) // 第一次 }; Child3.prototype = new Person() // 第二次 const kind3 = new Child3('pfzzz',25,'man') console.info(kind3) kind3.say() 缺點:子類沒法傳遞動態參數給父類,且父類構造函數被調用兩次,問題不大。
// 四、野路子繼承 function inherit(child, parent) { // 將父類原型和子類原型合併,並賦值給子類的原型 child.prototype = Object.assign(Object.create(parent.prototype), child.prototype) // 重寫被污染的子類的constructor p.constructor = child } // 缺點:Object.assign並非最好的方法
//五、利用es5的Reflect來優化 function fancyShadowMerge(target, source) { for (const key of Reflect.ownKeys(source)) { Reflect.defineProperty(target, key, Reflect.getOwnPropertyDescriptor(source, key)) } return target } // Core function inherit(child, parent) { const objectPrototype = Object.prototype // 繼承父類的原型 const parentPrototype = Object.create(parent.prototype) let childPrototype = child.prototype // 若子類沒有繼承任何類,直接合並子類原型和父類原型上的全部方法 // 包含可枚舉/不可枚舉的方法 if (Reflect.getPrototypeOf(childPrototype) === objectPrototype) { child.prototype = fancyShadowMerge(parentPrototype, childPrototype) } else { // 若子類已經繼承子某個類 // 父類的原型將在子類原型鏈的盡頭補全 while (Reflect.getPrototypeOf(childPrototype) !== objectPrototype) { childPrototype = Reflect.getPrototypeOf(childPrototype) } Reflect.setPrototypeOf(childPrototype, parent.prototype) } // 重寫被污染的子類的constructor parentPrototype.constructor = child }
其餘還有寄生式繼承,寄生組合式繼承,但都有一些反作用。
這些都是es5的繼承,如今有了es6的 class
,相關繼承就不須要在過多考慮了,這些屬於拓展知識,知道最好不知道也不影響代碼書寫。函數