本文记录的几种继承方式,努力向基于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
收工!
本文的思想和代码参考: