继承
继承:首先继承是一种关系,类(class)与类之间的关系,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指向父类型的实例,那么子类型就继承了父类型的属性和方法
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的用处: 说明实例是由谁创造出来, 指向自己的构造器
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()可以返回新对象,使用指定的对象创建新对象的原型
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关键字
寄生式继承
-
在原有的原型式继承上添加一下方法,增强一下,就是寄生式继承。
-
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。
- 优缺点和原型式继承一样
- 多个实例的引用类型属性指向相同的内存,互相影响
- 优缺点和原型式继承一样
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());
哪种继承方式最好?
原型继承、构造函数继承、组合式继承、寄生式继承、寄生组合式继承
- 寄生组合式继承是以上这里面最优的继承方式
- 解决了重复调用父类构造函数问题
- 解决了引用对象互相影响问题
- 解决了子类型无法获取父类型上的原型属性和方法