总结js继承的几种方式(上)

170 阅读3分钟

这是我参与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

然后当我们调用实例对象的某个方法,如果实例对象没有,则一层一层往上找,找到就返回,找不到就报错。

比如我们新建的实例对象默认都会有valueOftoString方法,这个就是继承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) // []

可以看到子类实例继承父类构造函数的属性,改变这些属性不会影响到别的实例。同时子类实例还可以继承父类构造函数的原型的属性。