- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
instanceof是 JavaScript 中判断对象是否属于某个类(并兼顾继承关系)的核心运算符,而Object.prototype.toString配合Symbol.toStringTag则提供了一种"增强版 typeof"的字符串化类型识别方案。本文以 en.javascript.info 教程的 Class checking: "instanceof" 章节为主体,结合仓库内的配套练习与源码级示例,完整讲解这两种类型检查机制的语法、底层算法、自定义扩展方式以及实战中的取舍,读完后你将能熟练运用它们构建多态函数、识别内建对象类型,并理解"构造函数本身不参与 instanceof 判定"这一关键陷阱。
instanceof 运算符:语法、用途与基本示例
instanceof运算符用于检查一个对象是否属于某个特定的类,并且会考虑继承关系。这种检查在很多场景下都是必需的,例如在构建多态函数时——即根据参数类型的不同采取不同处理逻辑的函数。
其语法非常简洁:
obj instanceof Class如果obj属于Class本身,或者属于继承自Class的某个类,则返回true。
与 class 一起使用
class Rabbit {} let rabbit = new Rabbit(); // 它是 Rabbit 类的对象吗? alert( rabbit instanceof Rabbit ); // true与构造函数一起使用
instanceof并不局限于class语法,对传统的构造函数同样生效:
// 使用构造函数而不是 class function Rabbit() {} alert( new Rabbit() instanceof Rabbit ); // true与内建类一起使用
它同样适用于Array、Object等内建类:
let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true请注意,arr同时也属于Object类——这是因为Array在原型上继承自Object,instanceof沿原型链查找时会在第二层命中。
instanceof 的判定算法:从 Symbol.hasInstance 到原型链
通常情况下,instanceof通过检查原型链来完成判定。但我们可以通过静态方法Symbol.hasInstance为其设置自定义逻辑。obj instanceof Class的完整算法大致如下:
第一步:优先调用静态方法 Symbol.hasInstance
如果类上存在静态方法Symbol.hasInstance,则直接调用ClassSymbol.hasInstance,它应返回true或false,判定到此结束。这正是我们自定义instanceof行为的入口。
例如,下面的代码假设"任何拥有canEat属性的对象都是动物":
// 设定 instanceof 检查规则: // 任何拥有 canEat 属性的对象都视为 Animal class Animal { static Symbol.hasInstance { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true:会调用 AnimalSymbol.hasInstance注意这里obj只是一个普通对象字面量,与Animal类没有任何原型关系,但自定义的Symbol.hasInstance仍然让它通过了判定——这正是该机制可用于实现"结构化类型匹配"的原因。
在仓库的 Symbol 章节中,Symbol.hasInstance与Symbol.iterator、Symbol.toPrimitive等一起被列为 JavaScript 内部使用的"系统符号(system symbols)",其行为由规范中的 Well-known symbols 表定义。
第二步:大多数类走标准逻辑——原型链比对
大多数类并没有Symbol.hasInstance,此时使用标准逻辑:检查Class.prototype是否等于obj原型链中的某一个原型。换句话说,就是逐级比对:
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // 若任何一级比对为 true,则返回 true // 若到达链的末尾仍无匹配,则返回 false在上面的例子中,rabbit.__proto__ === Rabbit.prototype,所以第一步就能给出答案。
在继承场景下,匹配会发生在第二级:
class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); alert(rabbit instanceof Animal); // true // rabbit.__proto__ === Animal.prototype (不匹配) // rabbit.__proto__.__proto__ === Animal.prototype (匹配!)下图直观展示了rabbit instanceof Animal时,rabbit的原型链如何与Animal.prototype逐级比对:
从图中可以看到,rabbit的[[Prototype]]指向Rabbit.prototype,再往上指向Animal.prototype,最终指向null。判定过程就是沿着这条链把每一级原型与Animal.prototype做比较。
关键洞察:构造函数不参与判定,prototype 才定义"类型"
instanceof还存在一个等价写法:objA.isPrototypeOf(objB)方法,若objA位于objB的原型链中的某个位置则返回true。因此obj instanceof Class可以改写为:
Class.prototype.isPrototypeOf(obj)有意思的是,Class构造函数本身完全不参与检查!只有原型链和Class.prototype才是判定依据。这会导致一个有趣的后果:当对象的prototype属性在对象创建之后被修改时,instanceof的结果会随之改变:
function Rabbit() {} let rabbit = new Rabbit(); // 修改了 prototype Rabbit.prototype = {}; // ……不再是兔子了! alert( rabbit instanceof Rabbit ); // false原因在于:rabbit的[[Prototype]]仍然指向旧的Rabbit.prototype对象,而新的Rabbit.prototype = {}是另一个全新对象,两者不相等,原型链比对自然失败。
仓库配套练习:Strange instanceof
这一特性在仓库的配套练习 1-strange-instanceof 中被进一步强化。题目问:下面的代码中,a明明不是由B()创建的,为什么instanceof返回true?
function A() {} function B() {} A.prototype = B.prototype = {}; let a = new A(); alert( a instanceof B ); // true官方给出的解答正是本节结论的印证:instanceof关心的不是函数本身,而是它的prototype是否与对象的原型链相匹配。这里a.__proto__ == B.prototype,所以返回true。按照instanceof的逻辑,prototype 实际上定义了类型,而不是构造函数。
Bonus:把 Object.prototype.toString 当作"增强版 typeof"
我们知道,普通对象被转换为字符串时会得到[object Object]:
let obj = {}; alert(obj); // [object Object] alert(obj.toString()); // 同样输出 [object Object]这是它们对toString的实现。但toString还有一个隐藏的强大特性:它可以被提取出来,在任意其他值的上下文中执行,并作为增强版typeof使用,甚至可以作为instanceof的替代方案。
根据规范,内建的toString可以从对象中提取并在任何其他值的上下文中执行,其返回结果取决于该值:
- 数字:
[object Number] - 布尔值:
[object Boolean] null:[object Null]undefined:[object Undefined]- 数组:
[object Array] - ……等等(可自定义)。
演示如下:
// 把 toString 方法复制到变量中以便调用 let objectToString = Object.prototype.toString; // 这个值的类型是什么? let arr = []; alert( objectToString.call(arr) ); // [object Array]这里使用了call方法(详见仓库的 call-apply-decorators 章节)以this = arr的上下文执行objectToString函数。toString算法内部会检查this并返回对应结果。更多示例:
let s = Object.prototype.toString; alert( s.call(123) ); // [object Number] alert( s.call(null) ); // [object Null] alert( s.call(alert) ); // [object Function]Symbol.toStringTag:自定义类型标签
Object.prototype.toString的行为可以通过一个特殊属性Symbol.toStringTag来自定义。例如:
let user = { [Symbol.toStringTag]: "User" }; alert( {}.toString.call(user) ); // [object User]大多数环境特有的对象都带有这个属性。下面是浏览器中的一些具体示例:
// 环境特有对象和类的 toStringTag: alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]可以看到,结果恰好就是Symbol.toStringTag(如果存在的话)包上[object ...]的外壳。
最终我们得到的是一个"打了类固醇的 typeof":它不仅适用于原始数据类型,也适用于内建对象,甚至可以被自定义。当我们需要把类型作为字符串获取(而不仅仅是做布尔检查)时,可以用{}.toString.call替代instanceof来处理内建对象。
类型检查方法总结与选型
仓库文档对目前学过的类型检查方法做了系统总结:
| 方法 | 适用对象 | 返回值 |
|---|---|---|
typeof | 原始类型 | 字符串 |
{}.toString | 原始类型、内建对象、带Symbol.toStringTag的对象 | 字符串 |
instanceof | 对象 | true/false |
从中可以看出:
{}.toString在技术上是一种"更高级的typeof"——它既能覆盖原始类型,又能区分内建对象,还支持通过Symbol.toStringTag自定义。instanceof的真正用武之地在于类层次结构:当我们需要在考虑继承关系的前提下判断对象属于哪个类时,它是最合适的选择。
仓库实战印证:instanceof 在错误处理中的应用
instanceof在仓库的其他章节中被大量实战使用,最典型的是 自定义错误章节。该章节明确指出:如果自定义错误类继承自Error,就可以用obj instanceof Error来识别错误对象。其核心模式是:
if (err instanceof ValidationError) { // 处理 ValidationError } else if (err instanceof SyntaxError) { // 处理 SyntaxError }文档还特别强调了选择instanceof而非其他判断方式的原因:因为将来ValidationError可能被扩展出PropertyRequiredError等子类型,而instanceof检查对新的继承类依然有效,具有面向未来的健壮性。这恰好呼应了本文第一节"instanceof 会考虑继承关系"的论断,也印证了文档中"当我们处理类层次结构并希望在考虑继承的前提下检查类归属时,instanceof 真正大放异彩"的总结。
小结
obj instanceof Class判断对象是否属于某个类或其子类,判定时只比较Class.prototype与对象原型链,构造函数本身不参与。- 可通过静态方法
Symbol.hasInstance完全自定义判定逻辑。 Class.prototype.isPrototypeOf(obj)是instanceof的等价表述。Object.prototype.toString.call(value)是覆盖原始类型、内建对象且可自定义的"增强版 typeof"。- 需要布尔判断且关注继承关系时选
instanceof;需要拿到类型字符串时选{}.toString+Symbol.toStringTag。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
JavaScript 基础运算符与数学运算全解析(Modern JavaScript Tutorial)
JavaScript 基础运算符与数学运算全解析(Modern JavaScript Tutorial) 本篇指南围绕 Modern JavaScript Tu
文档/教程前端Modern JavaScript Tutorial 精读:JavaScript 的 8 种数据类型与 typeof 运算符全解析
Modern JavaScript Tutorial 精读:JavaScript 的 8 种数据类型与 typeof 运算符全解析 JavaScript 中的每
文档/教程前端JavaScript 逻辑运算符完全指南:`||`、`&&` 与 `!` 的求值机制与短路行为(Modern JavaScript Tutorial 实战解析)
JavaScript 逻辑运算符完全指南: || 、 && 与 ! 的求值机制与短路行为(Modern JavaScript Tutorial 实战解析) 本篇
文档/教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考