好的,各位听众老爷们,晚上好!欢迎来到“JavaScript类型判断漫谈”现场,我是今晚的主讲人,人送外号“Bug终结者”。今天咱们不搞那些枯燥乏味的理论,咱们要用段子和案例,把typeof
、instanceof
和 Object.prototype.toString
这三个JavaScript界的“老炮儿”给扒个底朝天,看看它们各自的能耐和局限,以及如何在实战中巧妙地运用它们。
首先,咱们先来热热场,讲个笑话:
程序员A:我最近写了个判断类型的函数,贼牛!
程序员B:哦?怎么个牛法?
程序员A:不管什么类型,都能准确判断!
程序员B:那你判断下自己是什么类型的?
程序员A:……(陷入沉思)
这个笑话告诉我们,类型判断这玩意儿,看似简单,实则水很深。搞不好,就把自己给绕进去了。
第一章:typeof
:江湖人称“类型速递员”,但经常送错件儿
typeof
,顾名思义,就是“类型是啥”的意思。它是一个一元运算符,就像一个快递员,你把一个变量扔给它,它会告诉你这个变量是什么类型的。但是,这个快递员经常送错件儿,尤其是遇到一些特殊情况。
咱们先来看看typeof
的正常表现:
typeof 123; // "number"
typeof "hello"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof Symbol(); // "symbol" (ES6新增)
typeof 123n; // "bigint" (ES2020新增)
这些都是基础类型,typeof
可以准确无误地识别出来,就像老司机认路一样,稳得很。
但是,当遇到引用类型时,typeof
就开始犯迷糊了:
typeof {}; // "object"
typeof []; // "object"
typeof null; // "object" 😱 (这是一个著名的bug!)
typeof function(){}; // "function"
看到没?除了函数(function)能被准确识别之外,其他的引用类型,比如对象(object)、数组(array)和 null
,都被无情地打上了“object”的标签。这就像快递员把所有的包裹都贴上“杂物”的标签,简直是丧心病狂!
为什么 typeof null
会返回 "object"
呢?
这是一个历史遗留问题,也是JavaScript设计上的一个缺陷。在JavaScript的早期版本中,所有值都是以32位存储的,其中前几位表示类型标签。null
的类型标签是全零,而对象的类型标签也是以零开头。为了性能考虑,typeof
直接检查了 null
的类型标签,结果就误判为 "object"
了。
这个bug一直保留至今,主要是为了兼容旧代码。所以,以后遇到 null
,千万不要相信 typeof
,它会忽悠你的!
总结一下 typeof
的优缺点:
特性 | 优点 | 缺点 |
---|---|---|
返回值 | 字符串,表示变量的类型 | 对于引用类型,除了函数外,都返回 "object";typeof null 返回 "object",这是一个bug |
适用场景 | 判断基本类型(number、string、boolean、undefined、symbol、bigint) | 无法准确判断引用类型的具体类型 |
使用方式 | typeof variable 或 typeof(variable) |
第二章:instanceof
:身世调查员,追根溯源,但认亲戚有局限
instanceof
是一个运算符,用来判断一个对象是否是某个构造函数的实例。它就像一个身世调查员,根据对象的原型链,追溯其祖宗八代,看看它是不是某个家族的成员。
举个例子:
let arr = [];
arr instanceof Array; // true
arr instanceof Object; // true (因为Array的原型链上也有Object)
let obj = {};
obj instanceof Object; // true
function Person(name) {
this.name = name;
}
let person = new Person("张三");
person instanceof Person; // true
person instanceof Object; // true (因为Person的原型链上也有Object)
可以看到,instanceof
不仅会判断对象是否是直接的构造函数的实例,还会沿着原型链向上查找,如果原型链上有该构造函数,也会返回 true
。
instanceof
的工作原理:
instanceof
运算符的左边是一个对象,右边是一个构造函数。它会检查左边对象的原型链上是否存在右边构造函数的 prototype
属性。如果存在,则返回 true
,否则返回 false
。
但是,instanceof
也有它的局限性:
-
无法判断基本类型:
instanceof
只能用于判断对象,不能用于判断基本类型。123 instanceof Number; // false (虽然123是数字,但它不是Number对象的实例)
-
原型链被修改的影响: 如果修改了对象的原型链,
instanceof
的结果可能会不准确。function Animal() {} function Dog() {} Dog.prototype = new Animal(); // Dog继承自Animal let dog = new Dog(); dog instanceof Animal; // true dog instanceof Dog; // true // 修改原型链 Dog.prototype = {}; dog instanceof Animal; // false (原型链被修改,Animal不在dog的原型链上了)
-
跨iframe的问题: 在多个iframe之间,由于每个iframe都有自己的全局环境,构造函数也不一样,
instanceof
可能会失效。<!-- iframe1.html --> <script> let arr = []; </script> <!-- iframe2.html --> <script> let iframe1 = document.getElementById('iframe1'); let arr = iframe1.contentWindow.arr; // 获取iframe1中的数组 arr instanceof Array; // false (因为iframe2中的Array构造函数和iframe1中的Array构造函数不一样) </script>
总结一下 instanceof
的优缺点:
特性 | 优点 | 缺点 |
---|---|---|
返回值 | 布尔值,表示对象是否是某个构造函数的实例 | 无法判断基本类型;原型链被修改会影响结果;跨iframe可能失效 |
适用场景 | 判断对象是否是某个构造函数的实例,尤其是在继承关系中 | 尽量避免在原型链被修改或者跨iframe的情况下使用 |
使用方式 | object instanceof Constructor |
第三章:Object.prototype.toString
:类型识别的“照妖镜”,终极解决方案
Object.prototype.toString
是一个终极武器,它可以准确地识别任何类型的值,包括基本类型和引用类型。它就像一面“照妖镜”,可以把各种妖魔鬼怪都照出原形。
为什么 Object.prototype.toString
这么厉害?
因为 Object.prototype.toString
方法会返回一个表示该对象的字符串,格式为 "[object Type]"
,其中 Type
是对象的类型。这个类型是由JavaScript引擎内部决定的,不会受到原型链的影响,也不会受到跨iframe的影响。
如何使用 Object.prototype.toString
?
我们需要使用 call
或 apply
方法来改变 this
的指向,让 Object.prototype.toString
方法作用于我们要判断类型的值。
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call("hello"); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function(){}); // "[object Function]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(new RegExp()); // "[object RegExp]"
Object.prototype.toString.call(Symbol()); // "[object Symbol]"
Object.prototype.toString.call(123n); // "[object BigInt]"
可以看到,Object.prototype.toString
可以准确地识别各种类型的值,包括 null
和 undefined
,简直是类型判断界的“良心”。
封装一个通用的类型判断函数:
为了方便使用,我们可以把 Object.prototype.toString
封装成一个通用的类型判断函数:
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
getType(123); // "Number"
getType("hello"); // "String"
getType(true); // "Boolean"
getType(undefined); // "Undefined"
getType(null); // "Null"
getType({}); // "Object"
getType([]); // "Array"
getType(function(){}); // "Function"
getType(new Date()); // "Date"
getType(new RegExp()); // "RegExp"
getType(Symbol()); // "Symbol"
getType(123n); // "BigInt"
这个 getType
函数可以准确地判断任何类型的值,而且不会受到原型链和跨iframe的影响,简直是类型判断界的“瑞士军刀”。
总结一下 Object.prototype.toString
的优缺点:
特性 | 优点 | 缺点 |
---|---|---|
返回值 | 字符串,表示对象的类型,格式为 "[object Type]" |
需要使用 call 或 apply 方法来改变 this 的指向,稍微繁琐 |
适用场景 | 准确判断任何类型的值,包括基本类型和引用类型 | |
使用方式 | Object.prototype.toString.call(value) |
第四章:实战演练:类型判断的最佳实践
现在,我们已经了解了 typeof
、instanceof
和 Object.prototype.toString
这三个类型判断工具的特性,接下来,我们来看看如何在实战中巧妙地运用它们。
1. 判断基本类型:
对于基本类型,我们可以使用 typeof
,简单快捷。
function isString(value) {
return typeof value === "string";
}
isString("hello"); // true
isString(123); // false
2. 判断引用类型:
对于引用类型,尤其是需要区分具体类型的时候,我们应该使用 Object.prototype.toString
。
function isArray(value) {
return Object.prototype.toString.call(value) === "[object Array]";
}
isArray([]); // true
isArray({}); // false
3. 判断自定义类型:
对于自定义类型,我们可以使用 instanceof
,方便快捷地判断对象是否是某个构造函数的实例。
function Person(name) {
this.name = name;
}
function isPerson(value) {
return value instanceof Person;
}
let person = new Person("张三");
isPerson(person); // true
isPerson({}); // false
4. 综合运用:
在实际项目中,我们可能需要综合运用这三个工具,根据不同的场景选择最合适的方案。
function getType(value) {
if (typeof value === "number") {
return "Number";
} else if (typeof value === "string") {
return "String";
} else if (typeof value === "boolean") {
return "Boolean";
} else if (typeof value === "undefined") {
return "Undefined";
} else if (typeof value === "symbol") {
return "Symbol";
} else if (typeof value === "bigint") {
return "BigInt";
} else if (value === null) {
return "Null"; // 特别注意:typeof null === "object",所以要单独判断
} else if (value instanceof Array) {
return "Array";
} else if (value instanceof Date) {
return "Date";
} else if (value instanceof RegExp) {
return "RegExp";
} else if (typeof value === "function") {
return "Function";
} else {
return "Object";
}
}
getType(123); // "Number"
getType("hello"); // "String"
getType([]); // "Array"
getType(new Date()); // "Date"
getType(null); // "Null"
getType({}); // "Object"
第五章:总结:类型判断的武林秘籍
经过今天的漫谈,相信各位听众老爷们对JavaScript中的类型判断已经有了更深入的了解。下面,我给大家总结一份类型判断的“武林秘籍”,希望对大家有所帮助:
typeof
: 擅长判断基本类型,但对引用类型束手无策,typeof null
是个坑。instanceof
: 擅长判断对象是否是某个构造函数的实例,但在原型链被修改或跨iframe的情况下会失效。Object.prototype.toString
: 类型判断的终极武器,可以准确识别任何类型的值,但使用起来稍微繁琐。
记住:
- 没有万能的工具,只有最合适的工具。
- 根据不同的场景选择最合适的类型判断方案。
- 多实践,多思考,才能真正掌握类型判断的精髓。
好了,今天的“JavaScript类型判断漫谈”就到这里,感谢各位听众老爷们的捧场!下次再见!😊