1.原型链继承
我们知道js对象的属性查找流程,是先查找本对象的属性,查找不到就会从 原型对象 上查找(每个实例对象都有会__proto__属性指向 原型对象 ),还是找不到那就__proto__.__proto__ ,原型对象上的原型对象,这样一路往上找,找到为止或者__proto__值为null。
我们可以使用这种查找原理来实现js对象继承
function Parent(){
this.name = 'my name is Parent'
this.actions = ['sing','jump']
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){
}
//原型链继承
Child.prototype = new Parent()
//保证类型正确,指向Child函数
Child.prototype.constructor = Child
const child1 = new Child()
child1.getName(); //my name is Parent
这种继承方式很简单,接下来再看一段代码
//先来构建两个实例
const child2 = new Child()
const child3 = new Child()
child2.actions.pop();
console.log(child1.action) //['sing']
console.log(child2.action) //['sing']
显然,这不是我们想要的结果
问题:
1. 当父亲出现引用类型的时候,某个子实例继承了父类的引用类型属性,一旦某个子实例修改了这个引用类型里面的值,其他子实例继承的这个属性全部都会被修改
2. 不能传参到父类
2.构造函数继承
通过 原型链继承的方式 引申出来的问题,我们只要想办法把 Parent 上的 属性方法,添加到 Child 上,而不是放在原型对象上,就可以防止被其他实例共享。
function Parent(){
this.name = 'my name is Parent'
this.actions = ['sing','jump']
this.eat = function(){
console.log(`${this.name} - eat`)
}
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){
//构造函数继承,可以传参
Parent.apply(this,arguments)
}
const child1 = new Child()
const child2 = new Child()
child1.actions.pop()
console.log(child1.actions) //['sing']
console.log(child2.actions) //['sing','jump']
这样看起来是不是就很完美的实现继承了, 但是 再仔细想想 会有什么问题呢?
console.log(child1.eat === child2.eat) //false
问题:属性或者方法想要被继承,就必须放在构造函数中定义,但是这样每次创建实例的时候,都会创建一遍父类的属性和方法, 多占用一块内存
3.组合继承
a. 通过 原型链继承 的方式, 我们 可以 实现基本的继承, 但是 父类的 引用类型属性 会被所有实例共享,一旦某个实例修改后,其他实例全部都会修改,而且这种继承方式不能传参到父类
b. 通过 构造函数继承 的方式,我们 解决了 原型链继承 的 两个问题(父类引用和传参问题), 但是 由于 每次创建实例,构造函数都会重复创建一遍属性和方法,会导致内存占用过多 的问题
那么 我们 结合一下 这两种 继承 方式
function Parent(){
this.name = 'my name is Parent'
this.actions = ['sing','jump']
}
Parent.prototype.eat = function(){
console.log(`${this.name} - eat`)
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){
//构造函数继承,可以传参
Parent.apply(this,arguments)
}
//原型链继承
Child.prototype = new Parent()
Child.prototype.constructor = Child
const child1 = new Child()
const child2 = new Child()
child1.eat(); // c1 - eat
child2.eat(); // c2 - eat
console.log(child1.eat === child2.eat); // true
那么,这样其实就已经实现继承,并且解决上面的问题了。
但是,再仔细观察,我们会发现, 上面调用了2次 构造函数,Parent.apply() 和 new Parent() , 明显也是一种资源浪费,是否可用再次精简呢
4.寄生组合继承
仔细观察,我们可以发现 其实 new Parent() 里面创建的属性和方法其实已经是不需要的,已经在Parent.apply()子构造函数里面创建了, 那么我们就可以精简这一步
function Parent(){
this.name = 'my name is Parent'
this.actions = ['sing','jump']
}
Parent.prototype.eat = function(){
console.log(`${this.name} - eat`)
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){
//构造函数继承,可以传参
Parent.apply(this,arguments)
}
//原型链继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child
const child1 = new Child()
const child2 = new Child()
child1.eat(); // c1 - eat
child2.eat(); // c2 - eat
console.log(child1.eat === child2.eat); // true
我们只需要简单的修改一下 代码,就可以解决两次调用构造函数的问题, Object.create 的效果其实跟下面的代码一样,通过一个空对象做原型中转,来跳过 Parent上面的属性和方法
let TempFunction = function () {};
TempFunction.prototype = Parent.prototype;
Child.prototype = new TempFunction();
这里再 引申 思考一个问题。有思考到答案的小伙伴可以在评论区写上~~
问: 为什么不直接 Child.prototype = Parent.prototype ?
5.Class 继承
毋庸置疑,这种方式最简单,最直观
class Parent {
constructor() {
this.name = 'aaa';
}
getName() {
console.log('getname');
}
}
class Child extends Parent {
constructor() {
super();
}
}
const p1 = new Child();
p1.getName(); //aaa