跳到主要内容

继承

继承:首先继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承

继承也是为了数据共享,JS中的继承也是为了实现数据共享

  • 原型作用之一:数据共享,节省内存空间
  • 原型作用之二:为了实现继承

继承是一种关系

  • 父类级别与类级别的关系

通过原型链实现继承​

  • 把子类型的原型对象 指向了父类型的构造函数

  • 改变对象的原型的指向即可,(改变原型的指向就会发生关系 即继承关系 )

  • 通过原型的方式,拿到原型上对应的属性和方法实现继承

js
function Parent() {
this.name = 'qqqiu';
this.arr = [1, 2, 3];
}

function Child() {}

// 把子类型的原型对象 指向了父类型的构造函数
Child.prototype = new Parent();

const child1 = new Child();
const child2 = new Child();
child1.arr.push(4);
console.log('child1', child1.arr);
console.log('child2', child1.arr);

原型继承的缺陷​

  • 如果父类型的属性有引用类型,多实例之间会互相影响,比如实例A改了属性会影响到实例B
    • 基本类型不会影响
    • 引用类型可能会影响,如果引用类型一直没有变,多个实例之间还是会引用这个属性,因为都是继承了父类的属性,指向同一个内存,会互相影响

原型继承的缺陷: 引用类型的属性共享问题,造成互相影响

为什么基本类型不会影响,而引用类型会影响?

  • 子类型继承父类型的属性

    • 如果父类型的属性是基本类型,子类型会拷贝父类型的的属性值,不会互相影响

    • 如果父类型的属性是引用类型,子类型会继承父类型的引用,指向同一内存地址,会互相影响

构造函数继承​

  • 构造函数继承是将子构造函数的原型指向父类型的实例来实现的

  • 构造函数继承通常时借助call、apply来完成继承

    • 改变this指向,把子类型的this指向父类型的实例,那么子类型就继承了父类型的属性和方法
js
function Parent(name) {
this.name = name || 'qqqiu';
this.arr = [1, 2, 3];
}

Parent.prototype.say = () => {
console.log('say');
};

function Child(name) {
Parent.call(this, name);
}

const child1 = new Child('child1');
const child2 = new Child('child2');
console.log('child1.name', child1.name);
child1.arr.push(4);
console.log('child1.arr', child1.arr);
console.log('child2.arr', child2.arr);
// child1.say(); // 报错,say方法不存在,无法获取父类型本身的原型方法

构造函数继承解决了什么问题?​

  • 解决了引用类型共享的问题 【解决了原型继承的缺陷】

构造函数继承,引用类型不会互相影响呢?​

  • 构造函数继承时,创建的是一个新的实例对象
    • 每个实例对象都是独立的,不会互相影响

构造函数继承有什么缺陷?​

  • 子类型无法获取父类型上原型的属性和方法

组合式继承​

原型链继承和构造函数继承结合表示为组合式继承

  • call方法会立即执行,即立即执行Parent构造方法

    • 这里call的意思就是把Parent的属性应用到Child这个对象身上
    • 所以child就继承了parent的方法
  • 需要重新指定constructor构造器,让child的构造器重新归位, 指向自己的构造器,否则Child的构造函数就是Parent

constructor的用处: 说明实例是由谁创造出来, 指向自己的构造器

js
function Parent() {
this.name = 'parent';
this.play = [1, 2, 3];
}
function Child() {
//call方法会立即执行,即立即执行Parent构造方法
//这里call的意思就是把Parent的属性应用到Child这个对象身上
//所以child就继承了parent的方法
Parent.call(this); // 调用父类构造函数
this.type = 'child';
}
Child.prototype = new Parent() // 调用父类构造函数
//手动挂上构造器,指向自己的构造函数
//需要重新指定constructor,否则Child的构造函数就是Parent了
Child.prototype.constructor = Child;

const child = new Child()

组合式继承解决了什么问题?​

  • 解决了原型继承和构造函数继承的缺陷
    • 原型继承的缺陷是:引用对象会互相影响
    • 构造函数继承的缺陷是:子类型无法获取父类型上的原型属性和方法

所以组合式继承 解决了 引用对象问题以及无法继承父类型的原型上的属性和方法问题

组合式继承有什么缺陷?​

  • 存在重复调用了父类的构造函数【调用了2次】

寄生组合式继承​

  • 解决了原型继承问题、构造函数继承问题、解决组合式继承两次调用的父类构造函数方法

核心:

  • 使用Object.create()方法来替代new Parent()
    • Object.create()可以返回新对象,使用指定的对象创建新对象的原型
js
function Parent() {
this.name = 'qqqiu';
this.arr = [1, 2, 3];
}

function Child() {
Parent.call(this);
}

// Child.prototype = new Parent();
// 把child的原型重写
Child.prototype = Object.create(Parent.prototype);
//需要重新指定constructor,否则Child的构造函数就是Parent了
Child.prototype.constructor = Child;
child.prototype.say = ()=>{
console.log('say')
}
const child1 = new Child();
const child2 = new Child();

console.log('child1', child1);
console.log('child2', child2);

寄生组合式继承解决了什么问题?​

  • 解决组合式继承重复调用了父类的构造函数
  • 寄生组合式继承只执行了一遍父类的构造函数,这样就弥补了组合式继承的缺点

寄生组合式继承有缺陷吗?​

  • 虽然说解决了组合式继承的缺陷,但是也是存在问题,比如如果子类型在原型上有属性和方法,有可能会被父类型的原型对象所覆盖,因为子类型的原型被重写了
    • 如果子类型的原型上的属性和方法写在 Object.create前面,那么就会被父类型的原型对象所覆盖

ES6 的 extends 关键字继承​

  • es6的class继承,通过extends关键字

寄生式继承​

  • 在原有的原型式继承上添加一下方法,增强一下,就是寄生式继承。

  • 使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。

    • 优缺点和原型式继承一样
      • 多个实例的引用类型属性指向相同的内存,互相影响
js
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};

function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}

let person5 = clone(parent5);

console.log(person5.getName());
console.log(person5.getFriends());

哪种继承方式最好?​

原型继承、构造函数继承、组合式继承、寄生式继承、寄生组合式继承

  • 寄生组合式继承是以上这里面最优的继承方式
    • 解决了重复调用父类构造函数问题
    • 解决了引用对象互相影响问题
    • 解决了子类型无法获取父类型上的原型属性和方法