instanceof 和 typeof 理解和实现原理
typeof
操作符
typeof
用于检查一个变量的“原始类型”。它返回一个字符串,表示变量的数据类型,就像是一个“侦探”,专门用来查一个东西到底是什么类型。
就好比你手里有个东西,不知道它是什么,用 typeof
一查,它就会告诉你这个东西是字符串、数字还是对象之类的。
typeof <expression>
typeof
返回一个字符串,表示给定操作数的类型,常见的值有
"undefined"
:表示变量未定义。"boolean"
:表示布尔类型。"number"
:表示数字类型。"string"
:表示字符串类型。"object"
:表示对象类型 (包括数组和 null)。"function"
:表示函数类型。
typeof 42; // "number"
typeof "hello"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof {}; // "object"
typeof null; // "object" -- 这是一个 JavaScript 的历史遗留问题
typeof []; // "object" -- 数组也是对象,`typeof` 无法区分数组和普通对象
typeof function() {}; // "function"
可以用typeof
来检查用户输入的东西是不是你想要的类型。比如你让用户输入一个名字,用 typeof
一查,发现它不是字符串,就可以提醒用户重新输入。
还可以用来检查变量是不是被声明过。比如 typeof someVariable === "undefined"
,就知道这个变量可能还没定义。
typeof
要注意的地方是
null
的特殊性:typeof
null
返回"object"
,这是因为 JavaScript 在早期的设计中有一个 bug,直到现在依然没有修复typeof
对数组和普通对象都返回"object"
,所以无法通过typeof
来区分数组和对象
如果想知道一个变量是否是数组,最好使用 Array.isArray()
方法:
Array.isArray([]); // true
Array.isArray({}); // false
为什么 typeof 对数组和 null 都返回 "object"
在 JavaScript 中,数组本质上是对象,它们继承自 Object
类型的原型,所以 typeof
返回 "object"
。但你可以使用 Array.isArray()
来检测一个对象是否是数组
typeof
的底层实现
typeof
实际上是如何实现的呢?不同的 JavaScript 引擎可能有不同的优化实现,但大体的思路是相似的。
原始类型判断: 对于 undefined
、boolean
、number
、string
和 symbol
类型,typeof
直接通过内存的类型标识来返回类型。例如,布尔值的内存格式与其他类型有明显区别,JavaScript 引擎可以通过内存的存储方式直接判断出来
对象类型判断: 对于对象类型(包括 object
、null
、数组、函数等),typeof
会检查对象的内部类型标签。在 JavaScript 中,所有对象的底层实现通常会维护一个 内部属性,它标识了该对象的具体类型。这样,typeof
就能识别出哪些是数组,哪些是函数,哪些是普通对象。
- 数组:所有数组都是对象,因此
typeof
无法区分数组与普通对象。为了区分数组,可以使用Array.isArray()
,它是专门设计来识别数组的。 - 函数:函数类型的对象在 JavaScript 引擎中会被赋予一个特殊的内部标签,通常在底层是
[[Class]]: Function
。typeof
会检查这个标签并返回"function"
。
instanceof
操作符
instanceof
就像是一个“家谱侦探”,专门用来查一个对象是不是某个“家族”的后代。换句话说,它能告诉你这个对象是不是某个类的实例。
是用来检查对象是否是某个构造函数的实例。它会检查对象的原型链是否包含某个构造函数的 prototype
属性。
<object> instanceof <constructor>
如果对象是构造函数的实例,返回 true
;否则,返回 false
。
const obj = new Date();
obj instanceof Date; // true
obj instanceof Object; // true
obj instanceof Array; // false
这段代码中的 obj
是 Date
类型的对象,它是 Date
构造函数的实例,因此 obj instanceof Date
返回 true。但 obj
不是 Array
的实例,所以 obj instanceof Array
返回 false。
instanceof
也可以用于判断自定义的构造函数或类实例。比如,当你定义了一个类,你就可以用 instanceof
来判断一个对象是否是这个类的实例。
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
}
const dog = new Dog("杜宾犬", "伯恩山犬");
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
dog
是 Dog
类的实例,但由于 Dog
继承了 Animal
,所以 dog
也是 Animal
类的实例
注意点:
instanceof
依赖于对象的原型链。它会检查对象的__proto__
属性,直到找到指定的构造函数的prototype
属性为止。对于基本类型无效:
instanceof
不能用于基本类型的值(如数字、字符串、布尔值等)。它只对对象有效。如果你在多个窗口(例如
iframe
)之间传递对象,instanceof
可能无法正常工作,因为不同的窗口有各自的全局上下文。即使两个对象看起来相同,如果它们属于不同的执行环境,instanceof
可能会返回false
42 instanceof Number; // false
"hello" instanceof String; // false
如果你需要检查变量是否是某种基本类型,应该使用 typeof
操作符
instanceof 的实现机制
在 JavaScript 中,每个对象都有一个内部属性叫 __proto__
(现在标准里叫 [[Prototype]]
,但我们可以用 __proto__
来理解)。这个属性指向了这个对象的“爹”。
instanceof
的实现机制其实就是一个 “往上爬” 的过程,也就是沿着原型链往上找
假设你有一个对象 obj
和一个构造函数 Car
,当你写下 obj instanceof Car
时,JavaScript 会根据以下步骤来判断:
- 从
obj
的[[Prototype]]
属性开始,查找其原型链。 - 如果在原型链上的某个对象的
constructor.prototype
属性和Car.prototype
相同,则返回true
。 - 如果沿着原型链走到顶端(
null
)还没有找到,返回false
。
这个过程其实挺简单的,就是 “爬梯子” 的过程
自定义代码实现 instanceof
function customInstanceOf(obj, constructor) {
// 获取对象的原型链
let prototype = Object.getPrototypeOf(obj);
// 循环查找原型链
while (prototype) {
if (prototype === constructor.prototype) return true; // 找到匹配的构造函数的原型
prototype = Object.getPrototypeOf(prototype); // 向上遍历原型链
}
return false; // 没有找到,返回 false
}
Object.getPrototypeOf()
:用来获取对象的原型(即对象的[[Prototype]]
)。constructor.prototype
:构造函数的prototype
属性。while (prototype)
:一直沿着原型链向上遍历,直到找到原型链的顶端(null
)。
function Animal() {}
function Dog() {}
Dog.prototype = new Animal();
const gou = new Dog();
console.log(customInstanceOf(gou, Dog)); // true
console.log(customInstanceOf(gou, Animal)); // true
console.log(customInstanceOf(gou, Object)); // true
console.log(customInstanceOf(gou, Array)); // false
gou
是 Dog
的实例,所以 gou instanceof Dog
返回 true
。 Dog
的原型是 Animal
,所以 gou instanceof Animal
也返回 true
。 最后,gou instanceof Object
也返回 true
,因为 Animal
最终继承自 Object
。
typeof
和 instanceof
的区别
类型检查范围不同
typeof
主要用于原始类型(如string
、number
、boolean
、undefined
等)的判断,不能区分对象类型之间的差异(如数组与普通对象)。instanceof
用于检查对象是否是某个构造函数的实例,它关注的是原型链,适用于对象类型的判断,尤其是自定义对象和内建对象(如Array
、Date
、Error
)之间的区分。
判断类型的精确度不同
typeof
对于大多数情况可以准确地判断基本数据类型,但对于对象类型 (包括数组和 null),它并不能做到很精准的判断。instanceof
更加精准地判断对象是否是某个构造函数的实例,尤其在多层继承或类继承中非常有用
const arr = [];
typeof arr; // "object" - 无法区分数组和对象
arr instanceof Array; // true - 正确判断数组
arr instanceof Object; // true - 数组也是对象的子类
const obj = new Error("出现错误");
typeof obj; // "object"
obj instanceof Error; // true
obj instanceof Object; // true