JS笔记《实现继承的方式》

97 阅读4分钟

继承

原型链继承

  • 将父类的实例作为子类的原型对象,实现属性和方法的继承

    • 优点:简单易懂,实现方便。
    • 缺点:
      • 多个子类实例共享同一个原型对象,操作原型对象的属性和方法会影响到所有子类实例。
      • 创建子类实例时,无法向父类构造函数传参。
function Parent(){
  this.name = 'father';
  this.hobbit = ['sleep', 'music'];
}

function Child(){
  this.name = 'child';
}

Child.prototype = new Parent();  // 所有子类实例对象都共享此原型对象
Child.prototype.constructor = Child;

var c = new Child();
var c2 = new Child();

console.log(c.hobbit);    // ['sleep', 'music']
c.hobbit.push('speak'); 
console.log(c.hobbit)     // ['sleep', 'music', 'speak']
console.log(c2.hobbit);   // ['sleep', 'music', 'speak']

构造函数继承

  • 在子类构造函数中调用父类构造函数,实现属性的继承。

    • 优点:
      • 每个子类实例对象都对应独立的父类对象,不会互相影响。
      • 创建子类实例时,可以向父类传递参数。
    • 缺点:
      • 只能继承父类的实例属性和方法,无法继承父类原型对象上的属性或方法。
Person.prototype.address = '辽宁沈阳';
function Person(name, age){
  this.name = name;
  this.age = age;
}

function Child(name, age, sex){
  Person.call(this, name, age);
  this.sex = sex;
}
var c = new Child('张三', 20, '男');
var c2 = new Child('李四', 30, '男');

c.type.push('TS');
console.log(c.type);  // ['JS', 'HTML', 'CSS', 'TS']
console.log(c2.type)  // ['JS', 'HTML', 'CSS']

console.log(c.address); // undefined 未继承Person的原型对象

组合式继承

  • 将原型链继承和构造函数继承结合起来。

    • 优点:
      • 弥补了原型链继承和构造函数继承的缺点,保证了父类属性和方法的独立;即继承了父类的属性,又继承了父类原型对象上的属性或方法。
    • 缺点:
      • 会调用两次父类构造函数:一次是在创建子类型原型的时候;一次是在子类型构造函数的内部。
Person.prototype.address = '辽宁沈阳';

function Person(name, age){
  this.name = name;
  this.age = age;
  this.type = ['JS','HTML','CSS'];
}

function Child(name, age, sex){
  Person.call(this, name, age);  // 第二次调用父类 保证了父类属性的独立性
  this.sex = sex;
}

Child.prototype = new Person();  // 第一次调用父类
Child.prototype.constructor = Child;


var c = new Child('张三', 20, '男');
var c2 = new Child('李四', 20, '女');

c.type.push('TS');
console.log(c.type);   // ['JS', 'HTML', 'CSS', 'TS']
console.log(c2.type)   // ['JS', 'HTML', 'CSS']
console.log(c.address) // '辽宁沈阳' // 又继承了父类原型对象的属性或方法

共享原型继承

  • 子类和父类共同指向一个原型对象。

    • 优点:简单!
    • 缺点:
      • 无法继承父类构造函数的属性或方法。
      • 由于指向的是一个原型对象,一有改动子类和父类的实例对象都会受影响。
Person.prototype.address = '辽宁沈阳';
Person.prototype.hobbit = ['rap', 'sing'];
function Person(){
  this.name = '张三';
}

function Child(){}

Child.prototype = Person.prototype;  // 子类和父类共用一个原型对象

let c = new Child();
console.log(c.name);  // undefined  不能继承父类属性或方法

c.hobbit.push('sleep');
console.log(c.hobbit) // ['rap', 'sing', 'sleep']

Person.prototype.address = '辽宁大连';
console.log(c.address) // '辽宁大连' 子类和父类指向的是同一个原型对象,一改全改,无法添加独有属性

let p = new Person();
console.log(p.name);  // '张三'
console.log(p.hobbit) // ['rap', 'sing', 'sleep'] 子类和父类指向的是同一个原型对象,一改全改,无法添加独有属性

原型式继承

  • 通过创建一个空的构造函数,将一个对象作为该构造函数的原型对象,返回该构造函数的实例对象,实现继承。

  • 优点:

    • 不需要单独创建构造函数就可以实现对象继承。
  • 缺点:

    • 父类中的属性如果包含引用值,则会被共享。
// Object.create()等同于:
function create(obj){
    function F(){}     // 创建一个空的构造函数
    F.prototype = obj; // 将传入的对象作为构造函数的原型对象
    return new F();    // 返回构造函数的实例对象
}

let person = {
  name: '张三',
  hobbit:['sleep', 'music'],
  sayHobbit: function(){
    console.log(this.hobbit);
  }
}

let zhong = Object.create(person);

zhong.name = 'zhong';
zhong.hobbit.push('moive');
zhong.sayHobbit();  // ['sleep', 'music', 'moive'] 

console.log(person.sayHobbit());  // ['sleep', 'music', 'moive']  引用属性被共享

寄生式继承

  • 在原型式继承的基础上添加一个包装函数,用来增强对象。返回一个新的对象,实现继承。相当于原型式继承的增强版。
  • 优点:
    • 不需要单独创建构造函数就可以实现对象继承。
  • 缺点:
    • 父类中的属性如果包含引用值,则会被共享。
let person = {
  name: '张三',
  hobbit:['sleep', 'music'],
  sayHobbit: function(){
    console.log(this.hobbit);
  }
}

function createChild(obj){
  let child = Object.create(obj);
  child.sayName = function(){
    console.log(this.name);
  }
  return child;
}

let child = createChild(person);
child.hobbit.push('moive');
child.sayHobbit()  // ['sleep', 'music'] 继承的方法
child.sayName()    // 当前对象添加的新方法

person.sayHobbit() // ['sleep', 'music', 'moive'] 引用属性被共享

寄生式组合继承

  • 结合了寄生式继承与组合式继承,优化父类构造函数的调用,实现继承。
    • 优点:
      • 实现了完整的继承,避免了组合继承调用两次父类构造函数的缺点。
    • 缺点:
      • 除了代码复杂,无他!
// 实现继承核心代码
function inherit(subClass, parentClass){
  subClass.prototype = Object.create(parentClass.prototype);
  subClass.prototype.constructor = subClass;
}

Person.prototype.sayName = function(){
  console.log(this.name);
}

function Person(name){
  this.name = name;
  this.hobbit = ['sleep', 'music'];
}

function Child(name, age){
  Person.call(this, name);
  this.age = age;
}

Child.prototype.sayAge = function(){
  console.log(this.age);
}

inherit(Child, Person); 
// c -->  c.prototype --> F -->F.prototype --> parentClass.prototype
let c = new Child('张三', 20);
c.sayName(); // 张三 继承了父类原型对象的属性方法
c.hobbit     // ['sleep', 'music'] 继承了父类构造函数的实例属性方法
c.hobbit.push('moive');
console.log(c.hobbit)  // ['sleep', 'music', 'moive']

let c2 = new Child('李四', 18);
c2.hobbit  // ['sleep', 'music'] 修改父类引用值,实例对象之间不互相影响