大家好!今天我们来聊聊 Object.prototype.toString.call() 这位“类型判断大师”的独门秘籍!
要说 JavaScript 里类型判断,那真是一场“雾里看花”的游戏。typeof 有时候靠不住,instanceof 又容易认错干爹,直到 Object.prototype.toString.call() 出场,才算有了个相对靠谱的裁判。
我们先来热个身,回顾一下 JavaScript 里都有哪些类型:
| 类型 | 描述 | 例子 |
|---|---|---|
| Undefined | 表示未定义,声明了变量但未赋值。 | let a; |
| Null | 表示空值,是一个只有一个值的特殊类型。 | let b = null; |
| Boolean | 表示真或假。 | let c = true; |
| Number | 表示数字,包括整数和浮点数。 | let d = 123; |
| String | 表示字符串。 | let e = "hello"; |
| Symbol | ES6 新增,表示独一无二的值。 | let f = Symbol(); |
| BigInt | ES2020 新增,表示任意精度的整数。 | let g = 123n; |
| Object | 表示对象,包括普通对象、数组、函数等。 | let h = {}; |
为什么 typeof 不靠谱?
typeof 虽然简单好用,但它有几个致命的缺陷:
-
对
null的误判:typeof null返回"object",这简直是 JavaScript 历史遗留问题,没办法。 -
对所有对象类型的简化: 除了函数,
typeof对所有对象类型都返回"object"。比如数组、Date 对象、RegExp 对象,统统都是"object"。这让我们很难区分它们。
console.log(typeof null); // "object"
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof new Date()); // "object"
console.log(typeof function(){}); // "function"
instanceof 的局限性
instanceof 用来判断一个对象是否是某个构造函数的实例。它通过原型链来查找,如果对象的原型链上存在该构造函数的 prototype,就返回 true。
但是,instanceof 有两个问题:
-
多框架问题: 在有多个框架的页面中,不同的框架有自己的构造函数和原型链,
instanceof可能会出错。 -
原型链修改问题: 如果原型链被修改过,
instanceof的结果可能不准确。
let arr = [];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true // 因为 Array.prototype 继承自 Object.prototype
function MyArray() {}
MyArray.prototype = []; // 故意修改原型链
let myArray = new MyArray();
console.log(myArray instanceof Array); // true // 看起来像是 Array 的实例
console.log(myArray instanceof MyArray); // false // 但实际上不是 MyArray 的实例
Object.prototype.toString.call() 的原理
Object.prototype.toString 方法原本是用来返回对象的字符串表示的。比如 ({}).toString() 会返回 "[object Object]"。 关键在于,这个方法内部会根据 this 的指向来确定返回的类型信息。
重点来了: call() 方法可以改变 this 的指向。 所以,我们可以用 call() 方法把 Object.prototype.toString 方法的 this 指向我们要判断类型的变量。 这样,Object.prototype.toString 就能返回该变量的准确类型信息了。
具体来说,Object.prototype.toString.call(value) 会返回一个形如 "[object Type]" 的字符串,其中 Type 就是 value 的类型。
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(123)); // "[object Number]"
console.log(Object.prototype.toString.call("hello")); // "[object String]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call({})); // "[object Object]"
console.log(Object.prototype.toString.call(new Date())); // "[object Date]"
console.log(Object.prototype.toString.call(/abc/)); // "[object RegExp]"
console.log(Object.prototype.toString.call(function(){})); // "[object Function]"
console.log(Object.prototype.toString.call(Symbol())); // "[object Symbol]"
console.log(Object.prototype.toString.call(123n)); // "[object BigInt]"
拆解一下 Object.prototype.toString.call()
-
Object.prototype.toString: 这是Object原型上的一个方法,所有对象都可以继承使用。 -
.call(): 这是函数的一个方法,用来改变函数执行时的this指向。 -
call(value):call()方法的第一个参数是要绑定的this值。在这里,我们将this绑定为我们要判断类型的变量value。 -
返回值:
Object.prototype.toString方法根据this的类型,返回一个字符串,形如"[object Type]"。
为什么它比 typeof 和 instanceof 更准确?
-
对
null的正确判断:Object.prototype.toString.call(null)返回"[object Null]",而不是"object"。 -
区分对象类型:
Object.prototype.toString.call([])返回"[object Array]",Object.prototype.toString.call({})返回"[object Object]",Object.prototype.toString.call(new Date())返回"[object Date]", 能够区分不同的对象类型。 -
不受原型链修改的影响:
Object.prototype.toString.call()直接读取对象的内部[[Class]]属性,不受原型链的影响。
[[Class]] 是什么?
[[Class]] 是一个内部属性,它存储了对象的类型信息。 这个属性是 JavaScript 引擎内部使用的,我们无法直接访问它。 Object.prototype.toString 方法就是通过读取这个 [[Class]] 属性来确定对象的类型。
如何封装一个类型判断函数?
为了方便使用,我们可以把 Object.prototype.toString.call() 封装成一个类型判断函数:
function typeOf(obj) {
const toString = Object.prototype.toString;
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object',
'[object Symbol]': 'symbol',
'[object BigInt]': 'bigint',
};
return map[toString.call(obj)];
}
console.log(typeOf(null)); // "null"
console.log(typeOf(undefined)); // "undefined"
console.log(typeOf(123)); // "number"
console.log(typeOf("hello")); // "string"
console.log(typeOf(true)); // "boolean"
console.log(typeOf([])); // "array"
console.log(typeOf({})); // "object"
console.log(typeOf(new Date())); // "date"
console.log(typeOf(/abc/)); // "regExp"
console.log(typeOf(function(){})); // "function"
console.log(typeOf(Symbol())); // "symbol"
console.log(typeOf(123n)); // "bigint"
这个 typeOf 函数接收一个参数 obj,然后使用 Object.prototype.toString.call(obj) 获取类型字符串,最后从 map 对象中查找对应的类型名称并返回。
兼容性考虑
Object.prototype.toString.call() 的兼容性非常好,几乎所有浏览器都支持。 可以放心使用。
总结
Object.prototype.toString.call() 是 JavaScript 中一种非常可靠的类型判断方法。 它能准确地区分各种数据类型,不受 typeof 和 instanceof 的局限性影响。 通过理解它的原理,我们可以更好地掌握 JavaScript 的类型系统,编写更健壮的代码。
让我们用一张表格来总结一下三种类型判断方式的优缺点:
| 方法 | 优点 | 缺点 |
|---|---|---|
typeof |
简单易用 | 对 null 误判,对所有对象类型(除了函数)都返回 "object" |
instanceof |
可以判断对象是否是某个构造函数的实例 | 受多框架和原型链修改的影响 |
Object.prototype.toString.call() |
准确区分各种数据类型,不受 typeof 和 instanceof 的局限性影响,兼容性好 |
稍微复杂一些 |
记住,没有万能的工具,只有适合的工具。 在实际开发中,我们需要根据具体情况选择合适的类型判断方法。 对于需要准确区分对象类型的情况,Object.prototype.toString.call() 绝对是你的不二之选!
好了,今天的讲座就到这里。 希望大家对 Object.prototype.toString.call() 有了更深入的了解。 下次再见!