原型链继承:
原理:子类的原型对象指定为父类的实例,通过原型链,将父类中的属性赋值 给子类实例;
优点:方便快捷;
缺点:父类中的引用属性会被子类所有实例共享。 即在子类实例之中修改了父类的引用属 性之后,在之后新创建的子类实例中拿到对应的引用属性不再是父类之前的引用属 性,而是修改之后的引用属性。
代码:
<script>
function Parent(){
this.parentPrototype='parentPrototype'
//如果父类中有一个引用属性,则将会被所有子类共享
this.parentObj={
info:'我是parent引用属性parentObj中的info'
}
}
function Children(){
}
//Children的原型对象指定为Parent的实例,通过原型链,将Parent中的属性赋值给Children实例
Children.prototype=new Parent()
const a=new Children()
console.log(a.parentPrototype)//parentPrototype
//缺点
const b=new Children()
//在a实例中更改parent的继承属性
a.parentObj.info='我是a实例中引用属性parentObj中的info'
//b与a共享引用属性
console.log(b.parentObj.info)//我是a实例中引用属性parentObj中的info
</script>
使用构造函数继承:
原理:就是将父类函数的实例的this指向子类
优点:
1.避免了子类示例共享引用属性的情况;
2.可以在实例化时给父类构造函数传递参数
缺点:如果父类中存在一个函数,那么每次实例化子类的时候,都会创建一个同样的函 数,函数的复用性就难以体现
代码:
<script>
function Parent() {
this.parentPrototype = 'parentPrototype'
this.parentObj = {
info: '我是parent里面的引用属性parentObj的info'
}
this.fn = function () {
console.log('我是parent里面的打印函数')
}
}
function Children() {
Parent.call(this)
}
const a = new Children()
a.parentObj.info = '我是a实例中的info'
console.log(a.parentPrototype)//parentPrototype
const b = new Children()
console.log(b.parentObj.info)//我是parent里面的引用属性parentObj的info
</script>
组合继承(原型链+构造函数)(这是js中最常见的继承方式):
原理:原型链+构造函数
优点:
1.避免了子类示例共享引用属性的情况;
2.避免父类构造函数重复对function函数的创建
代码:
<script>
function Parent(){
this.parentPrototype='parentPrototype'
this.parentObj={
info:'我是parent里面的引用属性parentObj的info'
}
this.fn=function(){
console.log('我是parent里面的打印函数')
}
}
function Children(){
Parent.call(this)
}
Children.prototype=new Parent()
const a=new Children()
console.log(a.parentPrototype)//parentPrototype
a.fn()//我是parent里面的打印函数
</script>
原型式继承(注意是原型式不是原型链,这种方法使用较少):
原理:创建一个构造函数,构造函数的原型指向对象,然后调用new操作符创建实例并且 返回这个实例,本质是一个浅拷贝)
缺点:父类中的引用属性会被子类所有实例共享。 即在子类实例之中修改了父类的引用属 性之后,在之后新创建的子类实例中拿到对应的引用属性不再是父类之前的引用属 性,而是修改之后的引用属性。(和原型链继承缺点一样)
代码:
<script>
function object(o) {
o.objectPrototype = 'objectPrototype'
function F() { }
F.prototype = o
//仔细想想,这里返回的是在这里面的构造函数!
return new F()
}
let a = object({
name: 'name1'
})
console.log(a.name)//name1
console.log(a.objectPrototype)//objectPrototype
</script>
寄生式继承:
原理:就是定义了一个方法,然后在方法里面复制一个对象,然后在对象的后面添加属性 和方法,最后把这个对象return出去
缺点:父类中的引用属性会被子类所有实例共享。 即在子类实例之中修改了父类的引用属 性之后,在之后新创建的子类实例中拿到对应的引用属性不再是父类之前的引用属 性,而是修改之后的引用属性。(和原型链继承缺点一样)
代码:
<script>
function createObje(obj) {
let clone = Object.assign(obj)//接受到对象之后又原封不动的创建一个新对象
clone.prototype1 = '我是新增的prototype1'
return clone//返回新对象
}
const parent = {
parentPrototype: 'parentPrototype'
}
//c实例就继承了parent的所有属性
let c = createObje(parent)
console.log(c)
</script>
寄生组合式继承(寄生+组合(原型链+构造函数)):
原理:寄生+组合(原型链+构造函数)
优点:和组合继承一样,只不过没有组合继承的调用两次父类构造函数的缺点
代码:
<script>
function All(Father, Son) {
//拷贝一个超类(父类)的原型副本
let proto = {
...Father.prototype
}
//这里和原型链继承一样,将原型的超类副本作为子类的原型对象,只不过继承的是超类原型的副本
Son.prototype = proto
//在这里拷贝的proto对象会丢失掉自己的构造函数,所以将proto的构造函数指向Son,不做这个也没 问题,不过防止可能有副作用加上吧
proto.constructor = Son
}
function Father() {
this.Fatherproto = 'Fatherproto'
this.color = ['red', 'blue']
}
function Son() {
this.Sonproto = 'Sonproto'
this.name = 'Son'
//这里和构造函数继承一样
Father.call(this)
}
Father.prototype.getName = function () {
console.log(this.name)
}
//这里在定义完Father的属性后执行,因为继承的是超类的原型的副本,与Father的Father.prototype 是两个对象
//在这之后再改变Father.prototype,就不会影响Son所继承的副本超类原型对象了
//(简洁来说就是子类继承的是父类原型对象的另一个复制品,目的是为了子类改动引用属性的时候,其他 子类不会随着这一个子类的改动而改动)
All(Father, Son)
let a = new Son()
a.getName()//Son
a.Fatherproto='这是a的proto'
let b=new Son()
console.log(b.Fatherproto)//Fatherproto
</script>
PS:该篇文章有很多琐碎的点需要记住,特别是最后一种继承方式,但是经常用的是第三种方式继承。
除了记住其原理和优缺点之外,代码也是非常重要的,记住看代码的时候仔细留意其中的每个注释!
这些也是前端面试常见的问题,写这篇文章也是为了巩固自己的知识,也希望大家提出其中的不足之 处。后续我还会写一些前端的面试题目,希望可以帮到需要的人。
分享一下最近读到的季羡林的一句话:
“生活必须体验丰富的情感,把自己变成丰富,宽大,能优容,能了解,能同情种种‘人性’,能看懂自己,不苛责自己,也不苛责旁人,不难自己以所不能,也不难别人所不能。”
和一位博主的话:
”漫漫前端技术路,其中的冷暖只有自己知道,我不喜欢面试,也不喜欢被人挑选的感觉。但只有自己时刻准备、一直学习着,才会拥有选择的权利。“
诸君共勉!