各位朋友,大家好!今天咱们来聊聊JavaScript里两个常用的类型判断小能手:typeof
和 instanceof
。听起来是不是挺简单的?但你要是觉得它们俩“人如其名”,那可就大错特错了!它们背后藏着不少玄机,用不好,可是会掉坑里的。准备好,咱们发车啦!
第一站:typeof
的“真面目”
typeof
,顾名思义,是用来判断变量类型的。但它判断的,其实是操作数的类型,而不是对象实例的类型。这一点很重要,一定要记住!
简单来说,typeof
会返回一个字符串,告诉你这个变量是啥“底子”。它能识别以下几种基本类型:
"undefined"
:未定义"boolean"
:布尔值 (true 或 false)"number"
:数值 (整数或浮点数)"string"
:字符串"bigint"
:BigInt"symbol"
:Symbol"object"
:对象 (包含 null、数组、对象等)"function"
:函数
看,种类还挺多的。咱们来举几个例子:
console.log(typeof undefined); // "undefined"
console.log(typeof true); // "boolean"
console.log(typeof 42); // "number"
console.log(typeof "Hello"); // "string"
console.log(typeof 12345678901234567890n); // "bigint"
console.log(typeof Symbol("foo")); // "symbol"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof null); // "object" <- 这是一个历史遗留问题!
console.log(typeof function() {}); // "function"
看起来挺靠谱的,对吧?但注意到了吗?数组和 null
用 typeof
判断,都变成了 "object"
! 这就是 typeof
的第一个坑:它无法区分对象和数组,也无法区分 null
和对象。
至于 null
被判断为 "object"
,这其实是JavaScript的一个历史遗留的 bug。最初的 JavaScript 实现中,null
被当做是一个指向不存在对象的指针,所以 typeof
把它判断为 "object"
。这个 bug 一直留到了现在,为了兼容性,无法修复。
更让人头疼的是,typeof
对所有对象类型(除了函数)都返回 "object"
。这意味着,自定义的类实例、Date 对象、正则表达式等等,都会被 typeof
误判。
class MyClass {}
const myInstance = new MyClass();
console.log(typeof myInstance); // "object"
console.log(typeof new Date()); // "object"
console.log(typeof /abc/); // "object"
总结一下 typeof
的优点和缺点:
特性 | 优点 | 缺点 |
---|---|---|
基础类型 | 可以准确判断 undefined 、boolean 、number 、string 、bigint 、symbol |
|
对象类型 | 可以判断是否为对象 | 无法区分对象、数组、null ,也无法区分自定义类实例、Date 对象、正则表达式等。 对所有对象类型(除了函数)都返回 "object"。 |
函数类型 | 可以判断是否为函数 | |
速度 | 很快 |
第二站:instanceof
的“套路”
instanceof
运算符用来检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。 换句话说,它检查的是 对象是否是某个构造函数的实例。
instanceof
的语法是:
object instanceof constructor
其中,object
是要检查的对象,constructor
是构造函数。
举个例子:
function Person(name) {
this.name = name;
}
const john = new Person("John");
console.log(john instanceof Person); // true
console.log(john instanceof Object); // true (因为 Person 的 prototype 继承自 Object)
console.log(john instanceof Array); // false
从上面的例子可以看出,instanceof
不仅会检查对象是否是直接由构造函数创建的,还会沿着原型链向上查找。如果 object
的原型链上存在 constructor.prototype
,那么 instanceof
就会返回 true
。
这也就意味着,instanceof
可以用来判断对象是否是某个类的实例,或者是否继承自某个类。
但是,instanceof
也有它的局限性。
局限性一:跨 Frame 或 iFrame 的问题
如果对象是在不同的 Frame 或 iFrame 中创建的,那么 instanceof
可能会失效。因为不同的 Frame 或 iFrame 有不同的全局环境,构造函数也会有所不同。
<!DOCTYPE html>
<html>
<head>
<title>instanceof 问题</title>
</head>
<body>
<iframe id="myFrame" src="iframe.html"></iframe>
<script>
const myFrame = document.getElementById('myFrame');
const myArray = myFrame.contentWindow.Array; // 获取 iFrame 中的 Array 构造函数
const arr = new myArray(1, 2, 3); // 在 iFrame 中创建一个数组
console.log(arr instanceof Array); // false (因为这里的 Array 是当前页面的 Array,而不是 iFrame 中的 Array)
console.log(arr instanceof myArray); // true
</script>
</body>
</html>
<!-- iframe.html -->
<!DOCTYPE html>
<html>
<head>
<title>iFrame</title>
</head>
<body>
</body>
</html>
在这个例子中,arr
是在 iFrame 中创建的,它的构造函数是 iFrame 中的 Array
。而 arr instanceof Array
返回 false
,是因为这里的 Array
是当前页面的 Array
,而不是 iFrame 中的 Array
。
局限性二:手动修改原型链
如果手动修改了对象的原型链,那么 instanceof
的结果可能会出人意料。
function Person(name) {
this.name = name;
}
const john = new Person("John");
// 修改 john 的原型链
Object.setPrototypeOf(john, null);
console.log(john instanceof Person); // false (因为 john 的原型链上已经没有 Person.prototype 了)
console.log(john instanceof Object); // false (因为 john 的原型链上已经没有 Object.prototype 了)
在这个例子中,我们通过 Object.setPrototypeOf(john, null)
将 john
的原型链设置为了 null
,导致 john instanceof Person
和 john instanceof Object
都返回 false
。
局限性三:基本类型和包装对象
instanceof
只能用于判断对象是否是某个构造函数的实例,不能用于判断基本类型。
console.log(123 instanceof Number); // false
console.log("abc" instanceof String); // false
console.log(true instanceof Boolean); // false
虽然基本类型可以使用包装对象来创建,但是 instanceof
仍然会返回 false
。
const num = new Number(123);
const str = new String("abc");
const bool = new Boolean(true);
console.log(num instanceof Number); // true
console.log(str instanceof String); // true
console.log(bool instanceof Boolean); // true
console.log(123 instanceof Number); // false
console.log("abc" instanceof String); // false
console.log(true instanceof Boolean); // false
这是因为 instanceof
检查的是对象的原型链,而基本类型没有原型链。
总结一下 instanceof
的优点和缺点:
特性 | 优点 | 缺点 |
---|---|---|
对象类型 | 可以判断对象是否是某个构造函数的实例,可以沿着原型链向上查找。 | 无法判断基本类型,跨 Frame 或 iFrame 可能失效,手动修改原型链可能导致结果不准确。 |
基本类型 | 无法判断基本类型 | |
原型链 | 可以沿着原型链向上查找 | 手动修改原型链可能导致结果不准确。 |
跨 Frame/iFrame | 跨 Frame 或 iFrame 可能失效 |
第三站:类型判断的“正确姿势”
既然 typeof
和 instanceof
都有各自的局限性,那我们该如何进行类型判断呢?别慌,这里有一些“正确姿势”供你参考:
-
判断基本类型: 使用
typeof
可以准确判断undefined
、boolean
、number
、string
、bigint
和symbol
。 -
判断
null
: 由于typeof null
返回"object"
,所以需要单独判断null
。function isNull(value) { return value === null; } console.log(isNull(null)); // true console.log(isNull(undefined)); // false console.log(isNull({})); // false
-
判断数组: 可以使用
Array.isArray()
方法来判断是否为数组。console.log(Array.isArray([])); // true console.log(Array.isArray({})); // false console.log(Array.isArray(new Array())); // true
Array.isArray()
的兼容性很好,几乎所有现代浏览器都支持。 -
判断对象: 如果需要判断一个变量是否为对象,并且排除
null
和数组,可以使用以下方法:function isObject(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); } console.log(isObject({})); // true console.log(isObject(null)); // false console.log(isObject([])); // false console.log(isObject("hello")); // false
-
判断自定义类实例: 使用
instanceof
可以判断对象是否是某个构造函数的实例。但要注意跨 Frame 或 iFrame 的问题,以及手动修改原型链的影响。class MyClass {} const myInstance = new MyClass(); console.log(myInstance instanceof MyClass); // true
-
终极方案:
Object.prototype.toString.call()
这个方法可以说是类型判断的瑞士军刀,几乎可以判断所有类型。
function getType(value) { return Object.prototype.toString.call(value).slice(8, -1); } console.log(getType(undefined)); // "Undefined" console.log(getType(null)); // "Null" console.log(getType(123)); // "Number" console.log(getType("abc")); // "String" console.log(getType(true)); // "Boolean" console.log(getType({})); // "Object" console.log(getType([])); // "Array" console.log(getType(new Date())); // "Date" console.log(getType(/abc/)); // "RegExp" console.log(getType(function() {})); // "Function" console.log(getType(new Error())); // "Error" console.log(getType(Symbol('foo'))); // "Symbol" console.log(getType(123n)); // "BigInt" class MyClass {} const myInstance = new MyClass(); console.log(getType(myInstance)); // "Object" (无法区分自定义类)
Object.prototype.toString.call(value)
会返回一个形如"[object Type]"
的字符串,其中Type
就是对象的类型。我们通过slice(8, -1)
将Type
提取出来。这个方法之所以强大,是因为
Object.prototype.toString
是一个通用的方法,可以应用于任何对象。通过call
方法,我们可以将this
指向要判断的对象,从而获取对象的类型信息。注意: 即使使用
Object.prototype.toString.call()
,也无法区分自定义类,因为它会把所有自定义类实例都判断为"Object"
。
总结:类型判断的“葵花宝典”
类型 | 判断方法 | 备注 |
---|---|---|
undefined |
typeof value === "undefined" |
|
null |
value === null |
typeof null 返回 "object" ,需要单独判断。 |
boolean |
typeof value === "boolean" |
|
number |
typeof value === "number" |
|
string |
typeof value === "string" |
|
bigint |
typeof value === "bigint" |
|
symbol |
typeof value === "symbol" |
|
array |
Array.isArray(value) |
|
object (排除null和数组) |
typeof value === "object" && value !== null && !Array.isArray(value) |
|
自定义类实例 | value instanceof MyClass |
注意跨 Frame/iFrame 问题,以及手动修改原型链的影响。 |
所有类型 (终极方案) | Object.prototype.toString.call(value).slice(8, -1) |
可以判断几乎所有类型,但无法区分自定义类。 |
最后一站:实战演练
现在,咱们来做几个小练习,巩固一下今天所学的知识:
-
判断一个变量是否为字符串,且长度大于 5。
function isLongString(value) { return typeof value === "string" && value.length > 5; } console.log(isLongString("Hello")); // false console.log(isLongString("Hello World")); // true console.log(isLongString(123)); // false
-
判断一个变量是否为数组,且第一个元素是数字。
function isNumberArray(value) { return Array.isArray(value) && typeof value[0] === "number"; } console.log(isNumberArray([1, 2, 3])); // true console.log(isNumberArray(["a", "b", "c"])); // false console.log(isNumberArray({})); // false
-
判断一个变量是否为
Error
类型的实例。function isError(value) { return value instanceof Error; } console.log(isError(new Error())); // true console.log(isError({})); // false console.log(isError("error")); // false
好了,今天的讲座就到这里。希望通过今天的学习,你对 typeof
和 instanceof
有了更深入的了解,也能在实际开发中更加准确地进行类型判断。记住,选择合适的类型判断方法,才能避免掉进坑里哦! 下次再见!