先记住这个答案
两者都是沿着对象的原型链一层层往上找,区别在于拿什么去比。proto.isPrototypeOf(obj) 直接拿 proto 这个对象去比,问「proto 在不在 obj 的原型链上」;obj instanceof Fn 先取 Fn.prototype,再去链上找。所以 obj instanceof Fn 和 Fn.prototype.isPrototypeOf(obj) 在默认情况下结果相同。不同点有两个:instanceof 右边必须是函数,而且能被 Symbol.hasInstance 改写;isPrototypeOf 对任何对象都能用,也不会被改写。
isPrototypeOf直接查询对象原型链instanceof通常查询构造器原型instanceof可使用自定义匹配逻辑isPrototypeOf不受该钩子改写
同一个对象,两种问法
用构造函数创建的对象,两种写法都能用;用 Object.create 直接指定原型的对象,只有 isPrototypeOf 能判断。
function Foo() {}
const f = new Foo()
console.log(f instanceof Foo, Foo.prototype.isPrototypeOf(f)) // 结果一致
const animal = { eats: true }
const dog = Object.create(animal)
console.log(animal.isPrototypeOf(dog)) // 直接拿对象问
try {
dog instanceof animal // 右边不是函数
} catch (err) {
console.log(err.name)
}
Foo.prototype = {} // 换掉原型
console.log(f instanceof Foo, Foo.prototype.isPrototypeOf(f))查看输出与解释
true true
true
TypeError
false falseanimal 只是个普通对象,没有 prototype 属性,也不能被调用,放在 instanceof 右边直接报 TypeError。这就是 isPrototypeOf 存在的意义:原型式继承里只有对象,没有构造函数。
最后一行把 Foo.prototype 换成了新对象,f 的原型链上还是旧的那个,所以两种判断都变成 false。
两者在原型链上怎么查
isPrototypeOf 的过程很简单:从 obj 的原型开始(不包括 obj 自己),逐层用 Object.getPrototypeOf 往上走,每一层和调用者比较是不是同一个对象,找到返回 true,走到 null 返回 false。所以 obj.isPrototypeOf(obj) 是 false,自己不算自己的原型。
instanceof 多了一步。它先看右边有没有 Symbol.hasInstance 方法,普通函数都从 Function.prototype 继承了一个默认实现,默认实现取出 Fn.prototype,再做和上面一样的逐层比较。如果某个类自己写了 static [Symbol.hasInstance],instanceof 的结果就由这个方法决定,可能和原型链完全无关,而 isPrototypeOf 仍然老老实实查链。
- 两者共同的盲区:跨 iframe
- 它们比较的都是对象引用。iframe 里的数组,原型是 iframe 自己的
Array.prototype,和主页面的不是同一个对象,所以arr instanceof Array和Array.prototype.isPrototypeOf(arr)都是 false。判断数组用Array.isArray,它看的是对象内部标记,不依赖原型对象。
容易答错的地方
- 以为
isPrototypeOf只看直接原型 - 它查的是整条链,不只是上一层。
Object.prototype.isPrototypeOf([])是 true,因为数组的链是[]→Array.prototype→Object.prototype。只判断直接原型要用Object.getPrototypeOf(obj) === proto。 - 在
Object.create(null)的对象上调用它 Object.create(null)建的对象没有原型,身上也就没有isPrototypeOf方法,直接写dict.isPrototypeOf(x)会报TypeError。要写成Object.prototype.isPrototypeOf.call(dict, x)。反过来,用它判断空原型对象不会出错,Object.prototype.isPrototypeOf(dict)就是 false。- 以为两者永远等价
- 默认情况下
x instanceof F等于F.prototype.isPrototypeOf(x),但只要F定义了Symbol.hasInstance,两者就可能不同。还有原始值:1 instanceof Number是 false,Number.prototype.isPrototypeOf(1)也是 false,它们都不会把原始值包装成对象再查。
面试官还会怎么问?
怎么用 isPrototypeOf 手写 instanceof?
先检查右边是不是函数,不是就抛 TypeError;然后返回 Fn.prototype.isPrototypeOf(obj) 即可。要更贴近规范,就在前面先判断 Fn[Symbol.hasInstance] 是否被自定义过。
class B extends A,A.isPrototypeOf(B) 是什么?
是 true。extends 会建立两条链:实例这条是 B.prototype 的原型为 A.prototype;构造函数这条是 B 本身的原型为 A,所以静态方法能被继承。A.isPrototypeOf(B) 查的就是第二条。
TypeScript 里哪个能用来收窄类型?
instanceof 能,TypeScript 会把 if (x instanceof Foo) 里的 x 收窄成 Foo。isPrototypeOf 只返回普通布尔值,编译器不认识它的含义,要收窄得自己写类型守卫函数,返回类型写成 x is Foo。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。