JS中instanceof校验通过但调用原型方法报incompatible receiver错误原因
运行现象复现
对应JS执行环境中的代码与报错如下:
> d = new Date() 2022-06-22T03:27:24.357Z > d2 = Object.assign({ __proto__ : d.__proto__ }, d) Date {} > d2 instanceof Date true > d2.toString() Uncaught: TypeError: Method Date.prototype.toString called on incompatible receiver [object Object] at Date.toString (<anonymous>)
无论是用Object.assign修改普通对象原型,还是用Object.create直接创建原型指向Date.prototype的对象,都会触发相同报错;该问题在Node.js的Buffer类型上也可以稳定复现。
核心逻辑说明
出现这个矛盾现象的本质是两套检查规则的差异:
instanceof的判断逻辑非常单一:它只会沿着对象的原型链向上遍历,只要原型链上存在对应构造函数的prototype属性,就会返回true,完全不关心对象本身的实际结构。你手动把普通对象的__proto__指向Date.prototype后,原型链检查自然可以通过,因此d2 instanceof Date返回true。Date、Buffer这类内置类型的实例方法,依赖的是实例自身持有的引擎内置内部槽(Internal Slots)。这些槽位是JS引擎通过构造函数创建对应类型实例时,直接绑定在实例自身的隐藏状态,比如Date实例专属的[[DateValue]]槽用来存储时间戳数值,Buffer实例的专属槽用来存储内存指针、数据长度等核心信息。内部槽既不存在于原型链上,也不是普通的可枚举属性,Object.assign这类复制属性的操作、手动修改原型的操作都无法获取或复制这些槽位。
当你调用Date.prototype.toString这类内置方法时,方法执行前会先做品牌检查(Brand Check):确认当前的this对象真的持有对应类型的专属内部槽,如果检查不通过就会直接抛出「接收者不兼容」的类型错误。
你创建的d2本质上只是一个原型被修改为Date.prototype的普通对象,从始至终没有拿到Date实例专属的[[DateValue]]内部槽,自然通不过方法的前置类型检查,调用就会报错。
正确实现方式
如果要复制Date实例,不要通过修改原型、复制普通属性的方式伪造实例,直接调用构造函数传入原实例的时间戳即可:
const d2 = new Date(d.getTime()) d2.toString() // 正常输出时间字符串,无报错
Buffer的复制同理,需要调用Buffer官方提供的复制API、或通过构造函数传入原Buffer数据创建新实例,不要通过手动改原型的方式伪造类型实例。
内容的提问来源于stack exchange,提问作者Yevgeniy P
相关产品推荐
相关产品推荐

