面试:了解继承嘛?继承有那些方法,优缺点呢?
前言
在面试中有一个非常热门的知识点——继承,那么什么是继承呢?通俗来讲就是让子类的实例对象能访问到父类上的属性,但是我们再JacaScript中如何实现呢?下面Virtual09将会为大家介绍我们如何在JavaScript中实现继承
正文
原型链继承
众所周知,构造函数new出来的对象会隐式继承到构造函数原型上的属性。 那么我们来看看这串代码
function Parent(){
this.name = 'Tom',
this.like = [1,2,3]
}
function Child(){
this.type = 'children'
}
Child.prototype = new Parent()
let s1 = new Child()
console.log(s1.name);
我们把new Parent()
挂在Child
的原型上,因为JavaScript引擎在查找属性时,会顺着对象的隐式原型向上查找,找不到,则查找隐式原型的隐式原型,一直向上,直到找到null为止。所以在Child
中是不存在name
这个属性的,JavaScript引擎就会顺着对象的隐式原型上去找属性,也就是会去Parent
里面找。所以结果是打印的是
而且对于原型我们知道的是实例对象可以修改显示继承到的属性,但是无法修改隐式继承到的属性(原型上的),所以我们来看看,当执行s1.like.push(4)
之后,打印s1.like
的结果是什么?
结果是[1,2,3,4]
,不是说实例对象无法修改隐式原型上的数据嘛?这里我们需要注意的是s1
拿到数组like
去帮它调用数组自己身上自带的push()
方法,导致了隐式原型上的数据被修改,并不是s1
实例对象修改的。所以我们可以使用原型链的方式实现继承,但是当我们再实例一个s2
,同样打印like
拿到的数据依旧是[1,2,3,4]
并不是我们要的[1,2,3]
所以原型链继承的缺点就是子类实例会继承同一个原型对象,内存共享,所以实例之间会相互影响。
构造函数继承
现在我们来看看,构造函数继承,是如何实现的?
function Parent(){
this.name = 'Tom',
this.like = [1,2,3]
}
function Child(){
Parent.call(this)
this.type = 'children'
}
let s1 = new Child()
我们通过调用Parent.call()
把Parent
的thi
s指向Child
,等效于Child
也拥有Parent
的属性,这样我们也可以访问到Parent
上面的属性。
但是,这样我们就无法通过原型链访问到Parent
隐式原型上属性和方法了。因为我们调用构造函数,就不会存在一个原型链供我们使用,去找到Parent
隐式原型上的属性和方法,既然不存在原型链,那我们去修改数组中的值,也不会说,创建了一个s2,这个s2调用的数组会等于s1调用数组,它们不会共享数据。所以对于构造函数继承子类实例之间不会相互影响,无法继承到父类原型上的属性和方法,只能继承父类的属性和方法。
组合继承
其实也就是原型链加上构造函数一起使用就可以了。
Parent.prototype.getName = function(){
return this.name
}
function Parent(){
this.name = 'Tom',
this.like = [1,2,3]
}
function Child(){
Parent.call(this)
this.type = 'children'
}
Child.prototype = new Parent()
let s1 = new Child()
console.log(s1.getName());
这样写,会存在一个问题,也是就按理来说s1
的构造函数应该是Child
,但是这里我们可以看到
它这里显示的构造函数是Parent
,是因为我们把new Parent()
这个实例对象,给到了Child.prototype
,而new Parent()
的构造函数是Parent
,所以导致了我们看到的s1
的构造函数是Parent
,所以我们要手动设置一下Child.prototype.constructor = Child
我们这样写,解决了原型链继承,以及构造函数继承带来的问题,但是自己也存在问题,也就是父类被二次调用了。
原型式继承
我们来看看对象的继承
let parent = {
name: 'Tom',
age:40,
like:[1,2]
}
let child = Object.create(parent);
我们使用Object.create()
创建了一个新的对象,我们把对象parent
作为新创建的对象的原型,也就是说,我们child
可以通过原型链访问到parent
里面的数据,但是提到原型链,我们就会知道内容是可以共享Object.create(x) 缺点: 多个实例之间,继承到的引用类型是相同的,会相互影响浅拷贝,会把引用类型的属性拷贝一份,值类型的属性会共享(深拷贝的引用地址就会变)
寄生式继承
let parent = {
name: 'Tom',
age:40,
like:[1,2]
}
function clone(obj){
let clone = Object.create(obj);
clone.getLike = function(){
return this.like
}
return clone;
}
let child = clone(parent)
寄生式继承的特点就是同原型式继承,但是可以让子对象继承父对象,并且可以添加一些新的属性和方法
寄生组合继承
Parent.prototype.getName = function(){
return this.name
}
function Parent(){
this.name = 'Tom',
this.like = [1,2,3]
}
function Child(){
Parent.call(this)
this.type = 'children'
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
let s1 = new Child()
let s2 = new Child()
寄生组合继承就是使用Object.create(Parent.prototype)
创建一个新的对象,这个对象的原型就是Parent.prototype
。保证了我们修改Child
原型上的值时不会修改到parent.prototype
上的值。
本文到此也就结束了,感谢大家阅读,若有不足,恳请支持,谢谢大家!!
转载自:https://juejin.cn/post/7393311009686306835