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的特殊性:typeofnull返回"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); // truedog 是 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)); // falsegou 是 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