大家好!今天我们来聊聊 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()
有了更深入的了解。 下次再见!