浅谈 instanceof 和 typeof 的实现原理
Category(分类): JavaScript Status: 已整理
本文保留了原文(2018 年)的讲解主线:typeof 的类型判断与底层存储的故事、Object.prototype.toString 的精确判断、instanceof 的原型链原理与经典 Object/Function/Foo 例题。整理时修复了被复制损坏的代码块,补全了
bigint返回值,并给「机器码低位存类型」这段流传甚广的说法加上了引擎视角的说明。文末附勘误清单。
typeof 实现原理
typeof 一般被用于判断一个变量的类型,我们可以利用 typeof 来判断 number、string、object、boolean、function、undefined、symbol 这七种类型(2018 年原文时尚无 bigint,ES2020 之后 typeof 10n 会返回第八种 'bigint')。这种判断能帮助我们搞定一些问题,比如在判断不是 object 类型的数据的时候,typeof 能比较清楚地告诉我们具体是哪一类的类型。但是,很遗憾的一点是,typeof 在判断一个 object 的数据的时候只能告诉我们这个数据是 object,而不能细致地具体到是哪一种 object,比如👉
let s = new String('abc')
console.log(typeof s === 'object') // true
console.log(s instanceof String) // true
要想判断一个数据具体是哪一种 object 的时候,我们需要利用 instanceof 这个操作符来判断,这个我们后面会说到。
来谈谈关于 typeof 的原理吧,我们可以先想一个很有意思的问题,js 在底层是怎么存储数据的类型信息呢?或者说,一个 js 的变量,在它的底层实现中,它的类型信息是怎么实现的呢?
原文的说法是:js 在底层存储变量的时候,会在变量的机器码的低位 1-3 位存储其类型信息👉
- 000:对象
- 010:浮点数
- 100:字符串
- 110:布尔
- 1:整数
but,对于 undefined 和 null 来说,这两个值的信息存储是有点特殊的。
null:所有机器码均为 0
undefined:用 −2^30 整数来表示
所以,typeof 在判断 null 的时候就出现问题了,由于 null 的所有机器码均为 0,因此直接被当做了对象来看待。
现代注解:上面这段「机器码低位存类型标签」的说法描述的是某个历史版本引擎的实现细节(不同引擎的打标签方案并不相同:V8 用 Smi/堆对象指针打标签,JavaScriptCore 一度用 NaN-boxing,SpiderMonkey 用自己的 JSValue 打标签),它不是 ECMAScript 规范的内容。规范只是规定
typeof null返回'object'(详见 MDN:typeof 的「历史遗留」说明)——这个规定源自初版实现里 null 与对象共用标签位的历史,后来为了兼容旧代码一直保留。所以「为什么 typeof null 是 object」的历史解释可以保留,但要记住:现代规范里它就是表格里写死的行为,与今天引擎实际怎么存值无关。
然而用 instanceof 来判断的话👉
try {
null instanceof null
} catch (e) {
console.log(e) // TypeError: Right-hand side of 'instanceof' is not an object
}
(勘误:原文说「null 直接被判断为不是 object」,这个表述不对——null instanceof null 根本不返回结果,而是直接抛出 TypeError,因为规范要求 instanceof 的右侧必须是可调用的对象。typeof null === 'object' 才是那个历史遗留问题,两件事不要混在一起。typeof null === 'object' 可以参考 MDN:typeof。)
因此在用 typeof 来判断变量类型的时候,我们需要注意,最好是用 typeof 来判断基本数据类型(包括 symbol、bigint),避免对 null 的判断。
现代补充:
typeof有两个实用小特性。一是它对未声明的变量也安全:typeof notDeclaredVar返回'undefined'而不会抛 ReferenceError,常用于检测全局变量/宿主 API 是否存在(如typeof window !== 'undefined')。二是浏览器里有个著名的规范「故意违规」:typeof document.all === 'undefined',这是 HTML 规范为了兼容老代码特批的行为,仅此一例。
还有一个不错的判断类型的方法,就是 Object.prototype.toString,我们可以利用这个方法来对一个变量的类型来进行比较准确的判断:
console.log(Object.prototype.toString.call(1)) // "[object Number]"
console.log(Object.prototype.toString.call('hi')) // "[object String]"
console.log(Object.prototype.toString.call({ a: 'hi' })) // "[object Object]"
console.log(Object.prototype.toString.call([1, 'a'])) // "[object Array]"
console.log(Object.prototype.toString.call(true)) // "[object Boolean]"
console.log(Object.prototype.toString.call(() => {})) // "[object Function]"
console.log(Object.prototype.toString.call(null)) // "[object Null]"
console.log(Object.prototype.toString.call(undefined)) // "[object Undefined]"
console.log(Object.prototype.toString.call(Symbol(1))) // "[object Symbol]"
console.log(Object.prototype.toString.call(10n)) // "[object BigInt]"(ES2020+)
它的原理是读取对象内部的 Symbol.toStringTag:大多数内置对象使用默认标签,自定义类的实例默认仍是 "[object Object]",但可以通过定义 Symbol.toStringTag 改变输出(class Foo { get [Symbol.toStringTag]() { return 'Foo' } } → "[object Foo]")。
instanceof 操作符的实现原理
之前我们提到了 instanceof 来判断对象的具体类型,其实 instanceof 主要的作用就是判断一个实例是否属于某种类型:
let person = function() {}
let nicole = new person()
console.log(nicole instanceof person) // true
当然,instanceof 也可以判断一个实例是否是其父类型或者祖先类型的实例。
let person = function() {}
let programmer = function() {}
programmer.prototype = new person()
let nicole = new programmer()
console.log(nicole instanceof person) // true
console.log(nicole instanceof programmer) // true
现代注解:
programmer.prototype = new person()是 2018 年常见的「经典继承」写法,缺点是父类构造函数会在设定原型时被执行一次、且父类实例属性会泄漏到子类原型上。现代等价写法是class/extends,或programmer.prototype = Object.create(person.prototype)(再补回constructor)。这里保留原文写法作历史示例。
这是 instanceof 的用法,但是 instanceof 的原理是什么呢?根据 ECMAScript 语言规范,我梳理了一下大概的思路,然后整理了一段代码如下:
function new_instance_of(leftValue, rightValue) {
let rightProto = rightValue.prototype // 取右表达式的 prototype 值
leftValue = leftValue.__proto__ // 取左表达式的 __proto__ 值
while (true) {
if (leftValue === null) {
return false
}
if (leftValue === rightProto) {
return true
}
leftValue = leftValue.__proto__
}
}
(勘误:原文参数名误拼为 leftVaule/rightVaule,已更正。)
其实 instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。
现代补充:这份手写版是教学简化,当前规范(ES2015+)的实际判定顺序是:
- 右侧必须是可调用对象(函数),否则抛
TypeError(这就是前面null instanceof null报错的来源);- 若右侧定义了
Symbol.hasInstance方法,优先调用它,由它决定结果(bind返回的包装函数就是靠它把instanceof转发给目标函数的,用户也可自定义);- 否则执行这里的原型链遍历(规范称 OrdinaryHasInstance)。
工程实践中还要注意跨 realm 问题:iframe / Worker 里的
Array与主窗口的Array不是同一个构造器,跨 frame 判断数组请用Array.isArray。更标准的取原型写法是Object.getPrototypeOf(leftValue)而非__proto__。
看几个很有趣的例子:
function Foo() {}
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
console.log(Foo instanceof Foo) // false
console.log(Foo instanceof Object) // true
console.log(Foo instanceof Function) // true
要想全部理解 instanceof 的原理,除了我们刚刚提到的实现原理,我们还需要知道 JavaScript 的原型继承原理。
关于原型继承的原理,我简单用一张图来表示:

我们知道每个 JavaScript 对象均有一个隐式的 __proto__ 原型(内部 [[Prototype]] 的访问器),而显式的原型属性是 prototype(只有函数才有),只有 Object.prototype.__proto__ 属性在未修改的情况下为 null 值(用 Object.create(null) 创建的对象连原型都没有,整个判定都会失效)。根据图上的原理,我们来梳理上面提到的几个有趣的 instanceof 使用的例子。
Object instanceof Object
由图可知,Object 的prototype属性是Object.prototype,而由于 Object 本身是一个函数,由 Function 所创建,所以Object.__proto__的值是Function.prototype,而Function.prototype的__proto__属性是Object.prototype,所以我们可以判断出,Object instanceof Object的结果是 true。用代码简单的表示一下:// 伪代码:跟踪左侧原型链与右侧 prototype 的比较过程 leftValue = Object.__proto__ = Function.prototype rightValue = Object.prototype // 第一次判断 leftValue != rightValue leftValue = Function.prototype.__proto__ = Object.prototype // 第二次判断 leftValue === rightValue // 返回 trueFunction instanceof Function和Function instanceof Object的运行过程与Object instanceof Object类似,故不再详说。Foo instanceof Foo
Foo 函数的prototype属性是Foo.prototype,而 Foo 的__proto__属性是Function.prototype,由图可知,Foo 的原型链上并没有Foo.prototype,因此Foo instanceof Foo也就返回 false。
我们用代码简单的表示一下:// 伪代码 leftValue = Foo.__proto__ = Function.prototype rightValue = Foo.prototype // 第一次判断 leftValue != rightValue leftValue = Function.prototype.__proto__ = Object.prototype // 第二次判断 leftValue != rightValue leftValue = Object.prototype.__proto__ // 即 null(原文误写为 leftValue = Object.prototype = null) // 第三次判断 leftValue === null // 返回 falseFoo instanceof Object// 伪代码 leftValue = Foo.__proto__ = Function.prototype rightValue = Object.prototype // 第一次判断 leftValue != rightValue leftValue = Function.prototype.__proto__ = Object.prototype // 第二次判断 leftValue === rightValue // 返回 trueFoo instanceof Function// 伪代码 leftValue = Foo.__proto__ = Function.prototype rightValue = Function.prototype // 第一次判断 leftValue === rightValue // 返回 true
总结
简单来说,我们使用 typeof 来判断基本数据类型是 ok 的,不过需要注意当用 typeof 来判断 null 类型时的问题,如果想要判断一个对象的具体类型可以考虑用 instanceof,但是 instanceof 也可能判断不准确,比如一个数组,它既可以被 instanceof 判断为 Array,也可以被判断为 Object(原型链上的每个构造器都返回 true)。所以我们要想比较准确地判断对象实例的具体类型时,可以采取 Object.prototype.toString.call 方法。
原文勘误与现代化说明
- 修复了所有被复制损坏的代码块:清除了掘金页面复制按钮残留的文字,恢复了换行、
console.log与注释(原文代码被压成一行,且多段示例的输出注释粘连在代码尾部)。 typeof的返回值列表从七种补全为八种:新增 ES2020 的'bigint'(typeof 10n);Object.prototype.toString示例同步补充"[object BigInt]"。- 「机器码低位 1-3 位存储类型信息」(000 对象 / 010 浮点数 / 100 字符串 / 110 布尔 / 1 整数)标注为历史引擎实现细节:这是对某个历史版本引擎打标签方案的记忆口诀,各引擎方案不同(V8 Smi/指针标签、JavaScriptCore NaN-boxing 等),规范只规定
typeof null === 'object'这个结果本身。 - 「null 直接被判断为不是 object」是错误表述:
null instanceof null不返回布尔值,而是抛TypeError(右侧必须是可调用对象);typeof null === 'object'才是历史遗留问题。已分开说明。 new_instance_of参数名leftVaule/rightVaule拼写更正;补充当前规范的实际判定顺序(可调用检查 →Symbol.hasInstance→ OrdinaryHasInstance 原型链遍历)、Object.getPrototypeOf标准写法、跨 realm 注意事项与Array.isArray。Foo instanceof Foo推导中leftValue = Object.prototype = null一行误写(这样写会把Object.prototype真的置空),已更正为Object.prototype.__proto__ === null的伪代码表述,并全部标注「伪代码」。programmer.prototype = new person()补充现代替代方案(class/extends或Object.create(person.prototype))说明。- 新增:
typeof对未声明变量安全返回'undefined'、浏览器中typeof document.all === 'undefined'的规范特批违规、Symbol.toStringTag可自定义Object.prototype.toString输出。 - 失效的
link.juejin.cn跳转链接替换为 MDN 直链。 - 原型继承图下载至
images/;原文末尾两张 URL 编码路径引用的附图(浅谈 instanceof 和 typeof 的实现原理/目录)已转换为images/108-image-02.webp、images/108-image-03.webp。
图片来源与授权说明
正文图片已本地化到 images/ 目录。图片的转载授权无法仅凭下载确认,公开发布前应向原作者确认许可:
images/108-image-01.webp:原型继承关系图。来源:原文插图(掘金图床)。images/108-image-02.webp:原文末尾附图。来源:原文页面随附图片(原存放于文章同目录浅谈 instanceof 和 typeof 的实现原理/image.png,内容未识别,疑为原文评论区/互动截图)。images/108-image-03.webp:原文末尾附图。来源:同上(原image 1.png)。
原文归属
作者:nicole_zhang18970。历史来源:掘金《浅谈 instanceof 和 typeof 的实现原理》。原文链接仅用于保留历史出处,当前语义以本文列出的规范和官方文档为准。