详解js继承

233 阅读3分钟

js原型图


JS继承的几种方式

1、原型链继承

function Animal(name, weight) {
    this.name = name;
    this.weight = weight;
}
Animal.prototype.eat = function () {
  console.log("吃东西");
};
function Dog(color) {
    this.color = color;
}

Dog.prototype = new Animal('小黑', '50斤');
Dog.prototype.bite = function () {
    console.log('咬人');
};

function Huskie(sex) {
    this.sex=sex;
}

Huskie.prototype = new Dog('灰色');
Huskie.prototype.play = function () {
    console.log('玩');
};
var hashiqi = new Huskie('公');
console.log(hashiqi.name); // 小黑
console.log(hashiqi.weight); // 50斤
console.log(hashiqi.color); // 灰色
console.log(hashiqi.sex); // 公
hashiqi.play(); // 玩
hashiqi.eat(); // 吃东西
hashiqi.bite(); //  咬人

这种是纯粹的继承关系,实例是子类的实例,也是父类的实例

特点

  • 简单
  • 父类新增原型方法/原型属性,子类都能访问到
    console.log(hashiqi.arr); // undefined
    Animal.prototype.arr = [1, 2, 3];
    console.log(hashiqi.arr); // undefined
    

缺点

  • 新实例无法向父类构造函数传参。(即 new Dog() 无法给 Animal传参)
  • 所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
    let d1 = new Dog();
    let d2 = new Dog();
    Animal.prototype.obj = { a: 1, b: 2 };
    console.log(d1.obj); // { a: 1, b: 2 }
    console.log(d2.obj); // { a: 1, b: 2 }
    d1.obj.a = 3;
    console.log(d1.obj); // { a: 3, b: 2 }
    console.log(d2.obj); // { a: 3, b: 2 }
    

2、借用构造函数继承

function Person(name, age, sex, weight) {
  // 创建构造函数
  this.name = name;
  this.age = age;
  this.sex = sex;
  this.weight = weight;
}
Person.prototype.eat = function() {
  console.log("吃饭");
};
// 学生的构造函数
function Student(name, age, sex, weight, height) {
  this.height = height;
  Person.call(this, name, age, sex, weight); // window.Person.call(this)
}
// 实例化对象
const stu1 = new Student("小明", 18, "男", "100kg", "170cm");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.height); // 小明 18 男 100kg 170cm
console.log(stu1 instanceof Person); // false
console.log(stu1 instanceof Student); // true
stu1.eat(); // stu1.eat is not a function

所谓借用构造函数,就是在子构造函数中调用父构造函数,达到继承并向父构造函数传参的目的。

特点

  • 在子实例中可向父实例传参
  • 可以继承多个构造函数属性(call多个构造函数即可)

缺点

  • 只继承了父类的属性,没有继承父类的原型的属性
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
  • 实例并不是父类的实例,只是子类的实例

3、组合继承

function Person(name, age, sex, weight) {
  // 创建构造函数
  this.name = name;
  this.age = age;
  this.sex = sex;
  this.weight = weight;
}
Person.prototype.eat = function() {
  console.log("吃饭");
};
// 学生的构造函数
function Student(name, age, sex, weight, height) {
  this.height = height;
  Person.call(this, name, age, sex, weight); // window.Person.call(this)
}
Student.prototype = new Person();
// 修复构造函数指向, 不然Student.prototype.constructor会指向Person
Student.prototype.constructor = Student;
// 实例化对象
const stu1 = new Student("小明", 18, "男", "100kg", "170cm");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.height); // 小明 18 男 100kg 170cm
console.log(stu1 instanceof Person); // true
console.log(stu1 instanceof Student); // true
stu1.eat(); // 吃饭
console.log(Student.prototype.constructor); // [Function: Student]

结合了以上两种继承方式的特点,可传参,可复用

特点

  • 解决以上两种继承方式的缺点

缺点

  • 调用了两次父类构造函数(耗内存),生成了两份实例(子类实例将子类原型上的那份屏蔽了)

4、寄生组合式继承

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {
  console.log("说话");
};
Person.go = function() {
  console.log("走动");
};

// 1.创建F构造函数,并且返回实例 new F();
// 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
function object(parentPro) {
  var F = function() {};
  F.prototype = parentPro;
  return new F();
}
// 2.将返回的实例作为子类的原型对象,同时修改子类原型对象的constructor指向子类。
function inhert(par, child) {
  var mid = object(par.prototype);
  mid.constructor = child;
  child.prototype = mid;
}

function Chinese(skin, language, name, age) {
  this.skin = skin;
  this.language = language;
  Person.call(this, name, age);
}
inhert(Person, Chinese);
var p1 = new Chinese("黄皮肤", "中文", "小白", 18);
console.log(p1); // Chinese { skin: '黄皮肤', language: '中文', name: '小白', age: 18 }
console.log(p1.constructor); // [Function: Chinese]
p1.say(); // 说话

这种继承方式基本是完美的解决方案,通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

图解如下


5、ES6 extends继承

class People {
  constructor(name) {
    this.name = name;
  }
  sayMyName() {
    return this.name;
  }
}

class Student extends People {
  constructor(name, age) {
    super(name); //调用父类的constructor(x,y)
    this.age = age;
  }
  sayMyAge() {
    //调用父类的方法
    return this.age;
  }
}
let stu1 = new Student("小李", 20);
console.log(stu1); // Student { name: '小李', age: 20 }
console.log(stu1.sayMyName()); // 小李
console.log(stu1.sayMyAge()); // 20
console.log(stu1 instanceof People); // true
console.log(stu1 instanceof Student); // true