首先,js 继承是怎么回事?
js 的继承基础是原型链, 在这之上, 在 es6 里加入了 class
, 也实现了 extends
, 也有了 super
关键字
大概语法是这么回事
class Sup {
name = 'sup-class'
init() {
console.log(this.name)
}
}
class Sub extends Sup {
init() {
super.init()
}
}
我最近遇到一个问题:
class Sup {
constructor() {
this.init()
}
name = 'sub-class'
init() {
console.log(this.name)
}
}
class Sub extends Sup {
name2 = 'sub-class'
init() {
super.init()
console.log(this.name2)
}
}
乍一看,貌似也没什么问题 ?
但是这个玩意儿,执行 new Sub()
运行出来的 结果, 第二处打印的是 undefined
我一开始遇到这个坑的时候, 还没反应过来, 为啥这里是 undefined
?
由于我一开始写的是 typescript, 所以习惯了不写构造函数, 就更加难以察觉这个问题
终于我去看了 一眼编译后的结果
这东西编出来的结果是这样的 -- 选择 tsc 的编译目标为 es2018 的话 -- 上面这个代码, 本质上跟下面这个是一致的
class Sup(){
constructor(){
this.name = 'sub-class'
this.init()
}
init(){
console.log(this.name)
}
}
class Sub extends Sup{
constructor(){
super()
this.name2 = 'sub-class'
}
init(){
super.init()
console.log(this.name2)
}
}
简单看过去,貌似还是没有什么问题
问题到底在哪里?
在于 js 的继承机制, js 的继承, 本质上是处理原型链, 对于对象本身的处理, 是先构造一个子类对象, 然后用父类的构造喊出来处理子类对象
也就是说, 在创建 Sub 的时候, 虽然去调用了父类的构造函数, 执行了 super()
, 但是并没有针对父类构造对象出来, 那么在父类的构造函数里的 this
关键字, 指向谁呢?
答案是 : 子类的实例
那么现在这个问题就好理解了: 由于在创建 Sub 实例的时候, 父类的构造函数执行的时候, 内部的 this
其实是只想 Sub 实例的, 所以这里调用的 this.init
其实是 Sub.prototype.init , 然后子类又去调用了父类的 init 方法, 才讲父类的内容打印出来
那么为什么打印自己的 name2
的时候, 是undefined
?
看看上面的代码: super(); this.name2 = 'sub-class'
所以这里的执行顺序为
Sub.construct
-> Sup.construct
-> this.name = ''
-> Sub.init
-> Sup.init
-> console.log(this.name)
-> console.log(this.name2)
-> this.name2 = ''
这样就清晰了吧, 为什么? 因为执行的时候, name2 还没初始化呢
如果不理解这些比较虚的东西, 改成原型链来看看:
function Sup(){
this.name = 'sub-class'
this.init()
}
Sup.prototype.init = function (){
console.log(this.name)
}
/**这里的继承, 就不写那种很通用的继承函数了*/
function Sub(){
Sup.call(this)
this.name2 = 'sub-class'
}
Sub.prototype.init = function (){
//这里要实现一个 super 的效果
Sub.prototype.__proto__.init.call(this)
console.log(this.name2)
}
Sub.prototype.__proto__ = Object.create(Sup.prototype)
以上