TypeGuard工具类通过Object.prototype.toString.call()获取内置标签并遍历原型链校验类型,解决typeof和instanceof的局限性,支持Array等内置类型及其子类判断。

JavaScript 中手写一个具备完整原型链校验的类型判断工具类,核心在于不依赖 typeof(它对对象、数组、null 都返回 "object"),也不直接用 instanceof(它依赖运行时构造函数引用,易受污染或跨 iframe 失效),而是基于 Object.prototype.toString.call() 获取内置标签([[Class]]),再结合 Object.getPrototypeOf() 向上遍历原型链,验证目标是否“属于”某类的继承体系。
1. 为什么需要原型链校验?
单纯用 toString.call(x) 只能识别“直接类型”,比如 [object Array]、[object Date],但无法识别自定义子类实例:
例如:
class MyArray extends Array {}
const arr = new MyArray(1, 2);
toString.call(arr) === '[object Array]'; // true —— ✅ 识别为 Array
// 但若想判断 arr 是否“是 Array 类型或其子类”,仅靠 toString 不够,需查原型链
2. 基础类型识别:统一用 toString.call()
这是最可靠的标准方式,不受原型篡改影响,能精确区分内置类型:
立即学习“Java免费学习笔记(深入)”;
-
[object Object]→ 普通对象(非 null) -
[object Array]→ 数组(含子类) -
[object Date]→ 日期 -
[object RegExp]→ 正则 -
[object Map]/[object Set]/[object Promise]等 -
[object Null]和[object Undefined](注意:null和undefined也能被正确识别)
封装成私有方法:getTag(val),返回标准化标签字符串(如 "Array")。
3. 原型链校验:递归比对构造器或内置标签
对任意值 val,要判断它是否“属于”某类型(如 Array 或其子类),不能只看 val.constructor === Array,而应:
- 先获取
val的原始标签(getTag(val)),若匹配目标类型(如"Array"),直接返回true - 否则,沿
Object.getPrototypeOf(val)向上遍历原型链,对每个原型对象重复调用getTag - 遇到
null(原型链终点)仍未匹配,则返回false - 注意:普通对象(
{})的原型链终点是Object.prototype,它的getTag是"Object";而Array.prototype的getTag是"Array",所以子类实例在原型链上必会经过带对应标签的原型
4. 工具类实现(TypeGuard)
以下是一个轻量、无依赖、可扩展的实现:
class TypeGuard {
static #toString = Object.prototype.toString;
static #getTag(val) {
return this.#toString.call(val).slice(8, -1); // 如 'Array', 'Date'
}
// 核心:检查 val 是否为 targetTag 类型或其子类实例
static #isTagInPrototypeChain(val, targetTag) {
if (val == null) return false;
let proto = Object.getPrototypeOf(val);
while (proto !== null) {
if (this.#getTag(proto) === targetTag) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 公共判断方法(可按需扩展)
static isArray(val) {
return this.#getTag(val) === 'Array' || this.#isTagInPrototypeChain(val, 'Array');
}
static isDate(val) {
return this.#getTag(val) === 'Date' || this.#isTagInPrototypeChain(val, 'Date');
}
static isRegExp(val) {
return this.#getTag(val) === 'RegExp' || this.#isTagInPrototypeChain(val, 'RegExp');
}
static isMap(val) {
return this.#getTag(val) === 'Map' || this.#isTagInPrototypeChain(val, 'Map');
}
static isObject(val) {
return this.#getTag(val) === 'Object';
}
static isPlainObject(val) {
if (this.#getTag(val) !== 'Object') return false;
const ctor = val.constructor;
return ctor === Object || ctor == null || ctor.prototype?.constructor === Object;
}
// 通用接口:传入类型名字符串(如 'Array')
static is(val, tag) {
if (typeof tag !== 'string') throw new TypeError('tag must be string');
return this.#getTag(val) === tag || this.#isTagInPrototypeChain(val, tag);
}
}
// 使用示例:
// class MyArray extends Array {}
// const a = new MyArray();
// TypeGuard.isArray(a); // true
// TypeGuard.is(a, 'Array'); // true
// TypeGuard.is(new Date(), 'Date'); // true
这个实现覆盖了标准内置类型及其继承关系,不依赖全局变量,抗污染,支持跨 iframe(因 toString.call 是标准方法,且原型链遍历使用原生 getPrototypeOf),也便于后续扩展自定义类型识别逻辑(比如加 isMyCustomClass 方法并手动注册其原型标签)。


















