这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战
问题
面试官:你能讲讲js继承的几种方式吗?
面试者:可以,有原型链继承,构造函数继承,组合继承(原型链和构造函数)等等
面试官:那你能展示说说吗,比如,通过例子来讲讲它们的优点和缺点
面试者:可以,巴拉巴拉小魔仙。。。
以上场景纯属虚构,如有雷同,纯属虚构。
今天总结js继承的几种方式,包括原型链继承,构造函数继承,组合继承(原型链和构造函数)等等
继承
原型链继承
先来说说什么是原型链?
js通过构造函数创建实例对象,构造函数有prototype属性,指向原型对象,实例对象也有_proto_指向原型对象,然后原型对象也有_proto_指向上一层原型对象(Object的原型),最终指向null。
function People () {
}
var p = new People()
console.log(People.prototype) // {constructor: ƒ}
console.log(p.__proto__) // {constructor: ƒ}
console.log(p.__proto__.__proto__) // Object的原型
console.log(p.__proto__.__proto__ === Object.prototype) // true
console.log(p.__proto__.__proto__.__proto__) // null
然后当我们调用实例对象的某个方法,如果实例对象没有,则一层一层往上找,找到就返回,找不到就报错。
比如我们新建的实例对象默认都会有valueOf和toString方法,这个就是继承Object的原型对象的。
function People () {
}
var p = new People()
console.log(p.test()) // error: p.test is not a function
console.log(p.valueOf()) // People {}
console.log(p.toString()) // '[object Object]'
console.log(Reflect.ownKeys(p.__proto__.__proto__)) // ["toString","valueOf", ...]
这种一层一层的链式结构就叫原型链。
那么原型链继承就是继承原型上的方法和属性。通过改写子类构造函数的prototype,来达到继承的目的。
// 父类构造函数
function People () {
}
People.prototype.source = 'dragon'
People.prototype.skin = []
// 父类构造函数
function Man () {
}
// 改写prototype
Man.prototype = new People()
var p = new Man()
console.log(p.source) // dragon
原型链继承可以让实例继承原型上的方法和属性,但是如果实例对原型的方法或者属性做了改变,则会影响后面的实例,它们获取到的是改变后的方法或者属性。
这个需要注意⚠️
function People () {
}
People.prototype.skins = []
function Man () {
}
Man.prototype = new People()
var p = new Man()
console.log(p.skins) //[]
p.skins.push('yellow')
var p1 = new Man()
console.log(p1.skins) //['yellow']
可以看到新建的p1实例的skins是被影响的。
构造函数继承
这种方式是通过在子构造函数调用父类构造函数,把this改变指向子类达到继承父类。
把this改变一般通过apply,call等方法操作。
function People () {
this.source = 'dragon'
}
People.prototype.skins = []
function Man () {
People.call(this)
}
var p = new Man()
console.log(p.source) // 'dragon'
console.log(p.skins) // undefined
可以看到实例p继承构造函数People的source属性,但是不能继承它原型上的方法/属性。
组合继承
组合继承是通过结合原型链继承和构造函数继承,结合二者的特点形成组合继承。
可以达到子类实例改变原型链继承的属性,但是不影响别的实例,同时还可以继承父类构造函数的原型的属性/方法。
function People () {
this.skins = []
}
People.prototype.source = 'dragon'
function Man () {
People.call(this)
}
Man.prototype = new People()
var p = new Man()
console.log(p.source) // 'dragon'
p.skins.push('yellow')
console.log(p.skins) // ['yellow']
var p1 = new Man()
console.log(p1.source) // 'dragon'
console.log(p1.skins) // []
可以看到子类实例继承父类构造函数的属性,改变这些属性不会影响到别的实例。同时子类实例还可以继承父类构造函数的原型的属性。