本文記錄的幾種繼承方式,努力向基於class語法糖實現的面向對象靠攏html
class Person { constructor(friends) { this.friends = friends == null ? [] : friends; } sayFriends() { console.log(`${this.name}'s friends are ${this.friends}`); } } class Man extends Person { constructor(name, age, friends) { super(friends); this.name = name; this.age = age; } sayName() { console.log(this.name); } sayAge() { console.log(this.age); } } const xialuo = new Man('xialuo', 20, ['qiuya']); const yuanhua = new Man('yuanhua', 21, []);
以上代碼能夠代表咱們的目的:函數
將父類實例做爲子類的原型對象this
function Person(friends) { this.friends = friends == null ? [] : friends; } Person.prototype.sayFriends = function () { console.log(this.friends); } // 沒法向父類構造函數傳遞參數,第2點未達成 function Man(name, age, friends) { this.name = name; this.age = age; } // 原型鏈繼承 Man.prototype = new Person(); Man.prototype.sayName = function () { console.log(this.name) } Man.prototype.sayAge = function () { console.log(this.age); } var xialuo = new Man('xialuo', 20, ['qiuya']); var yuanhua = new Man('yuanhua'); // friends被共享,第3點未達成 console.log(xialuo.friends); // [] console.log(yuanhua.friends); // [] xialuo.friends.push('teacherWang'); console.log(xialuo.friends); // ['teacherWang'] console.log(yuanhua.friends); // ['teacherWang'] // xialuo、yuanhua是Man也是Person的實例,第4點達成 console.log(xialuo instanceof Man); // true console.log(xialuo instanceof Person); // true
缺點:spa
- 沒法向父類構造函數傳參
- 父類的引用類型friends被共享
借用父類構造函數加強子類prototype
function Person(friends) { this.friends = friends == null ? [] : friends; this.personMethod = () => { console.log('personMethod run') } } Person.prototype.sayFriends = function () { console.log(this.friends); } function Man(name, age, friends) { Person.call(this, friends); this.name = name; this.age = age; } Man.prototype.sayName = function () { console.log(this.name) } Man.prototype.sayAge = function () { console.log(this.age); } var xialuo = new Man('xialuo', 20, ['qiuya']); var yuanhua = new Man('yuanhua'); // 未繼承到父類Person的原型方法,第2點未達成 // xialuo.sayFriends(); // TypeError: xialuo.sayFriends is not a function xialuo.personMethod(); // 'personMethod run' console.log(xialuo.personMethod === yuanhua.personMethod); // false // friends未被共享,第3點達成! console.log(xialuo.friends); // ['qiuya'] console.log(yuanhua.friends); // [] xialuo.friends.push('teacherWang'); console.log(xialuo.friends); // [''qiuya, 'teacherWang'] console.log(yuanhua.friends); // [] // xialuo.__proto__和Person.prototype找不到同一個對象,第4點未達成 console.log(xialuo instanceof Man); // true console.log(xialuo instanceof Person); // false
優勢:3d
- 解決原型鏈繼承中沒法向父類傳參的問題
缺點指針
- instanceof 沒法判斷實例是父類的實例
- 沒法繼承父類Person的原型方法,只能繼承父類的自有方法,且沒法複用(構造方法形成的)
Lint:爲何要區分原型方法和自有方法?code
function Man() { // 自有方法sayHello this.sayHello = () => { console.log('hello') } } Man.prototype.sayWorld = () => { console.log('world'); } const xialuo = new Man(); const yuanhua = new Man(); /** * 私有方法在每次調用構造函數new時都會從新建立,沒法複用。 * * 解決辦法是將方法添加到原型對象prototype,實例會從Man.prototype調用函數,實現函數複用 */ console.log(xialuo.sayHello === yuanhua.sayHello); // false console.log(xialuo.sayWorld === yuanhua.sayWorld); // true
原型鏈模式 + 借用構造函數模式,集兩者之長htm
function Person(friends) { this.friends = friends == null ? [] : friends; } Person.prototype.sayFriends = function () { console.log(this.friends); } // 子類屬性處理 function Man(name, age, friends) { // 子類繼承父類屬性 Person.call(this, friends); // 借用構造函數 // 子類生成自有屬性 this.name = name; this.age = age; } // 子類方法處理 Man.prototype = new Person(); // 原型鏈模式,繼承父類屬性 Man.prototype.constructor = Man; // lint: 修復因原型鏈模式改變的子類構造函數(否則會變成Person) // 子類自有方法 Man.prototype.sayName = function () { console.log(this.name) } Man.prototype.sayAge = function () { console.log(this.age); } var xialuo = new Man('xialuo', 20, ['qiuya']); var yuanhua = new Man('yuanhua'); // friends未被共享,第3點達成! console.log(xialuo.friends); // ['qiuya'] console.log(yuanhua.friends); // [] xialuo.friends.push('teacherWang'); console.log(xialuo.friends); // [''qiuya, 'teacherWang'] console.log(yuanhua.friends); // [] console.log(xialuo instanceof Man); // true console.log(xialuo instanceof Person); // true
優勢:
集原型鏈模式和借用構造函數的優勢缺點:
建立一個xialuo實例,卻調用兩次構造函數對象
目前,咱們還須要解決組合式繼承的缺點,以獲得js繼承的最佳實踐
function object(o) { function Fn() { } Fn.prototype = o; return new Fn(); } function Person(name, age, friends) { this.name = name; this.age = age; this.friends = friends == null ? [] : friends; } var p = new Person('xialuo', 20, ['qiuya']); var xialuo = object(p); p = new Person('yuanhua', 21, []); var yuanhua = object(p);
能夠看到,object函數接收一個對象obj,做爲臨時對象Fn的原型對象,最終返回Fn。
也就是說,xialuo,yuanhua的屬性,全在xialuo.__proto__,yuanhua.__proto__,而不是實例對象xialuo,yuanhua上。
這一般會致使原型指針混亂而形成this指向不明的問題。—— 維爾希寧
對了,ES5提供一個方法Object.create()替代了上述的object函數,內部實現是同樣的。
<br>
寄生組合式繼承是對組合式繼承的改造,結合原型式繼承,目的是解決組合式繼承中兩次調用父類構造函數的問題。
function object(obj) { function Fn() { } Fn.prototype = obj; return new Fn(); } function Person(friends) { this.friends = friends == null ? [] : friends; } Person.prototype.sayFriends = function () { console.log(this.friends); } function Man(name, age, friends) { Person.call(this, friends); this.name = name; this.age = age; } Man.prototype = object(Person.prototype); // 在此處,減小一次父類構造函數調用 Man.prototype.constructor = Man; Man.prototype.sayName = function () { console.log(this.name) } Man.prototype.sayAge = function () { console.log(this.age); } var xialuo = new Man('xialuo', 20, ['qiuya']); var yuanhua = new Man('yuanhua'); console.log(xialuo.friends); // ['qiuya'] console.log(yuanhua.friends); // [] xialuo.friends.push('teacherWang'); console.log(xialuo.friends); // [''qiuya, 'teacherWang'] console.log(yuanhua.friends); // [] console.log(xialuo instanceof Man); // true console.log(xialuo instanceof Person); // true
收工!
本文的思想和代碼參考: