发现问题
在开发中需要实现一个自定义Error,它继承自Error
,按照经验,一般会这样来做:
function CustomError(message) {
this.message = message;
}
CustomError.prototype = Object.create(Error.prototype, {
name: {
value: 'customError'
},
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
var myError = new CustomError('f**k error!');
console.log(myError instanceof CustomError) // true
console.log(myError instanceof Error) // true
它工作的很好,没有明显的bug,所以长期以来,我都这么干。直到某天试着这样:
> console.log(CustomError instanceof Error)
< false
为什么这里返回的是false
?
再一个,回到题目本身,我想和大家讨论的是函数之间的继承关系。具体而言,就是CustomError
通过原型链继承于Error
,实现预期不仅表现在CustomError
的每一个实例上,也表现在其本身——CustomError instanceof Error === true
。
原型与原型链
在回答问题之前,需要理一理原型和原型链是什么、干什么、以及它们之间的关系。
是什么
- 原型
prototype
每个函数有一个prototype属性,指向函数的原型对象。
它的值是一个对象(属性的集合),默认只有一个constructor属性,指向函数本身。 - 原型链
__proto__
1、每个对象默认有一个__proto__属性,指向创建该对象的函数的prototype。
2、访问一个对象的属性时,先在该对象的基本属性中查找,如果没有,在沿着__proto__这条链向上查找,这就是原型链。
3、函数也是对象,也默认有一个__proto__属性,指向Function.prototype。
干什么
prototype
:用来实现基于原型的继承和属性共享。__proto__
:构成原型链,同样用于实现基于原型的继承。
什么关系
emm,好像还是不太清楚它们之间的联系。但如果从内存的角度上看,就简单了许多。
当一个函数CustomError
被创建成功时,默认带有prototype
和__proto__
两个属性,此时,它们之间的关系如下:
+-------------+
Function.prototype -------> |
| 内存(堆1) |
CustomError.__proto__ -------> |
| +-------------+
|
| +-------------+
+----------.prototype -------> |
| 内存(堆2) |
myError.__proto__ ------------> |
+-------------+
(图1)
解决之道
有了以上的铺垫,解答之前的问题就简单多了。
首先,我们通过CustomError.prototype = Object.create(Error.prototype)
操作,改变了CustomError.prototype
的指向。
+-------------+
Function.prototype -------> |
| 内存(堆1) |
CustomError.__proto__ -------> |
| +-------------+
|
+----------.prototype--+ +-------------+
| | 内存(堆2) |
myError.__proto__ + | +-------------+
| |
| | +-------------+
| +------> |
| | 内存(堆3) |
+-----------> |
+-------------+
(图2)
其次,这里的instanceof
操作,就是判断CustomError
的原型链(__proto__
)上是否存在Error.prototype
,其结果返回一个Boolean。
所以结果false
并不令人感到奇怪,因为没有任何迹象表明CustomError.__proto__
改变了指向,且指向Error.prototype
。
回过头,比较一下图1、2,思考两秒钟。
上面的回答也暗含了解决之道:
> CustomError.__proto__ = Error.prototype
> CustomError instanceof Error
< true
总结
尽管这样解决了问题,但MDN并不推荐这么做。
一来,如MDN所讲,这个操作会很慢,可能有性能影响。
二来,若CustomError.prototype = Object.create(Object.assign({},F1.prototype, F2.prototype))
,还要继续改变CustomError.__proto__
么?
三来,我并不保证以上内容绝对正确。譬如说:通过Function.prototype.bind
方法构造出来的函数,它没有prototype属性;Object.prototype.__proto__=== null
。
烦请勘误指正~
延展
- Object.create()
使用指定的原型对象及其属性去创建一个新的对象(依然维持着对原型对象的引用)。 - instanceof 运算符
用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。 - isPrototypeOf()
用于测试一个对象是否存在于另一个对象的原型链上。
与 instanceOf 不同,在表达式object instanceOf AFunction
中, object 的原型链是针对AFunction.prototype进行检查,而不是针对AFunction本身。