前几天去面试,面试官提出了这么一个问题:有A和B两个类,a和b分别继承前者,如何让b也继承A。
那时候我可是一脸懵逼,想了下回答了b = Object.create(A.prototype)
,面试官不置可否,当时我心里窃喜,以为蒙对了。回家之后越想越不对,于是有了这篇文章。
基础代码
为了可以愉快地console.log
东西,需要先以以下代码作为基础:
class A {
a() {
console.log('a');
}
}
class B {
b() {
console.log('b');
}
}
let a = new A();
let b = new B();
挺简单的对吧?
Object.create()
首先试下自己的想法,输入b = Object.create(A.prototype)
,看看会有什么结果:
结果很残酷,整个b都继承A了,其实文档写的蛮清楚的:Object.create() 方法使用指定的原型对象和其属性创建了一个新的对象。
因而b = Object.create(A.prototype)
约等于b.__proto__ = A.prototype
,都是看文档囫囵吞枣的锅啊!
(写完文章后,发现其实为了答题的话,想到一个黑科技:b.__proto__.__proto__ = Object.create(A.prototype)
,b.b
和b.a
都能打印出来,但我看起来真的好奇怪,求dalao打醒。)
for in 遍历
既然Object.create()
此路不通,那么我换一个,我遍历A.prototype
,再一个个添加到b.__proto__
里面总可以了吧?于是我喜滋滋地写下了以下代码:
for(let key in A.prototype){
b.__proto__[key] = A.prototype[key]
}
console.log(b.a());
之后脸就被打得啪啪响了,Chrome报错:Uncaught TypeError: b.a is not a function
,这是什么鬼?为毛b.a
不是函数,再三检查不是语法错误之后,我在b.__proto__[key] = A.prototype[key]
前多加了一句console.log(key)
,结果是什么都没打印出来。然而,当我打印console.log(A.prototype)
时,明显是一个对象,里面有key为a!
解释
When you have eliminated the impossible, whatever remains, however improbable, must be the truth.
造成这样的结果,只有一个原因,A.prototype.a
不可枚举,因而for in
不能遍历。不过我怎么觉得以前是可以的,于是我在Chrome中输入以下代码:
function C() {
}
C.prototype.c = function() {
console.log('c')
}
for(let key in C.prototype){
console.log(key);
}
结果是能打印出a的,这就尴尬了,这到底是怎么一回事呢?难道ES6的class语法糖,它的prototype里的方法是不可枚举,而原来构造函数prototype里的方法确实可以枚举的?赶紧验证一下:
cDescriptor = Object.getOwnPropertyDescriptor(c,'c');
aDescriptor = Object.getOwnPropertyDescriptor(a,'a');
打印出来,发现cDescriptor的enumerable
是false
,而aDescriptor的enumerable
是true
,所以,原因一目了然,ES6中的class,它的方法默认是不可枚举的,而构造函数默认是可枚举的。细想一下,这其实是个优化,让你在遍历时候,不需要再判断hasOwnProperty
了。(后面查资料发现,其实阮一峰老师的文章早就介绍过了,当时看的时候不认真啊!)
解决问题
那么,那道面试题应该如何解呢?如果使用构造函数,那倒是可以for in
遍历塞进去,但如果是ES6的class呢?
查阅文档发现,使用Object.getOwnPropertyNames()
方法,就可以拿到我所需要的prototype
对象的key,代码如下:
let keys = Object.getOwnPropertyNames(A.prototype);
for(let key of keys){
if(key !== 'constructor'){
b.__proto__[key] = A.prototype[key];
}
}
如此一来,b.a
与b.b
都能打印出来了!
小结与思考
总的来是,是复习了一些prototype
相关API,希望对大家有帮助。
另外,如此解题,我感觉是十分古怪的,因为b的constructor
指向B,然而它却有B.prototype
没有的方法,现在代码简单是无所谓,然而复杂起来的时候,debug绝对是个深坑。希望有dalao指导如何优雅的实现。