2500字深度解析JavaScript 类型检测:typeof、instanceof与Object.prototype.toString()
前言
在JavaScript编程的世界里,准确地识别和处理变量类型是编写健壮、高效代码的基础。尽管JavaScript是一门弱类型语言,允许我们在不严格指定变量类型的情况下进行操作,但这并不意味着我们可以在类型问题上掉以轻心。事实上,对变量类型的正确理解和判断,往往能够帮助我们避免许多难以追踪的错误和异常行为。
我们将深入探讨JavaScript中三种常用且重要的类型检测方法:typeof
、instanceof
以及 Object.prototype.toString()
。我们将揭开这些方法背后的机制,理解它们的优势、局限性及适用场景,从而在实际开发中更加得心应手。
typeof()
console.log(typeof(123))//number
console.log(typeof('123'))//string
console.log(typeof(true))//boolean
console.log(typeof(undefined))//undefined
console.log(typeof(null))//object
typeof对于基本数据类型的判断知道大家都清楚,但是疑问最多的就是关于为什么typeof(null) 得到的却是object。在一些基础的JS书中有解释为:typeof()将值转为二进制后看其前三位是不是0,除了函数外的所有引用类型的二进制前三位都是0,null的二进制也是全部为0,因此返回的也成为了object。
但是这是并不准确的,官方文档中也并没有准确的解释,内部说明了调用了Type(val),IsUnresolvableReference(val)和GetValue(val)内部函数。
但是我们可以确定的是现在的typeof(null)
返回 "object"
是JavaScript设计历史中的一个的异常情况但出于向后兼容性的考虑,这个行为被保留至今。但是建议理解为:在JavaScript的早期设计阶段,null
被视为一个空的对象引用。按照最初的意图,null
应当表示一个未指向任何对象的指针,因此在设计 typeof
运算符时,可能认为将其归类为 "object"
是合理的。然而,随着语言的发展,null
被明确为一个独立的原始值,用来表示缺少值或者表示变量尚未被赋予有效的对象引用。
instanceof()
instanceof
是JavaScript中的一个运算符,用于判断一个对象是否为某个构造函数的实例,或者说是该构造函数(或其原型链上的任何构造函数)实例化的。因此instanceof()只能用于引用类型判断。
object instanceof constructor
object
:需要检查的对象。constructor
:用来对比的构造函数或者函数对象。
实例
console.log(123 instanceof Number)//false
console.log(123 instanceof Object)//false
console.log(123 instanceof String)//false
let a=new Number(123)
console.log(a instanceof Number)//true
console.log(a instanceof Object)//true
console.log(a instanceof String)//false
console.log('123' instanceof String)//false
console.log('123' instanceof Object)//false
console.log('123' instanceof Function)//false
let str=new String('123')
console.log(a instanceof String)//true
console.log(a instanceof Object)//true
console.log(a instanceof Function)//false
判断步骤:
- 原型链检查:引擎会检查
object
的原型链(__proto__
)是否包含constructor
的原型对象(constructor.prototype
)。 - 如果包含:如果在
object
的原型链上找到了constructor.prototype
,那么instanceof
表达式返回true
,表明object
是constructor
的实例或派生实例。 - 如果不包含:如果在
object
的原型链上没有找到constructor.prototype
,那么返回false
。
手写功能复原
根据上面的判断步骤我们有两个中实现的方法:
1.递归实现intanceof:
function MyInstance(L,M){
if (typeof(L)!== 'object' || L === null) {
return false; // L不是对象或为null,直接返回false
}
if(L.__proto__==null)
return false;//原型对象为空
if(L.__proto__==M.prototype)
return true;
return MyInstance(L.__proto__,M)
}
缺点:递归函数可能会导致栈溢出错误
2.循环实现intanceof:
function MyInstance(L,M){
if (typeof(L)!== 'object' || L === null) {
return false; // L不是对象或为null,直接返回false
}
while(1){
if(L.__proto__==M.prototype)
return true;
if(L.__proto__==null)
return false;
L=L.__proto__
}
}
Object.prototype.toString()
Object.prototype.toString()
是JavaScript中一个非常强大且灵活的方法,用于获取一个对象的类型信息。这个方法最初设计用于生成对象的字符串表示,但通过覆盖,它可以被用于多种用途,最著名的是用于精确地检测对象的类型。
// 基本类型
// 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(undefined)); // "[object Undefined]"
// console.log(Object.prototype.toString.call(null)); // "[object Null]"
//引用类型
console.log(Object.prototype.toString.call({})); // [object Object]
2console.log(Object.prototype.toString.call([])); // [object Array]
3console.log(Object.prototype.toString.call(new Date())); // [object Date]
4console.log(Object.prototype.toString.call(/regex/)); // [object RegExp]
5console.log(Object.prototype.toString.call(null)); // [object Null]
6console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
7console.log(Object.prototype.toString.call(function(){})); // [object Function]
为什么在使用Object.prototype.toString()
时需要加上call()方法呢?
因为直接在原始值(如字符串、数字、布尔值)上调用 toString()
会返回它们的字符串表示,而不是类型信息。因此,使用 Object.prototype.toString.call()
是必要的,这样可以确保即使是原始值也会被正确转换为对象后再进行类型检测。
而对于任何对象(包括数组、函数、正则等),直接调用 toString()
通常会返回一个表示该对象的字符串。但是,当通过 Object.prototype.toString.call()
形式调用时,它会返回一个特定格式的字符串,这个字符串能够准确地表示该值的类型。基本格式为 [object Type]
,其中 Type
是对象的具体类型。
如果不加上call()来修改这个对象this的指向,那么内部的参数实际上是没有意义的,因为调用的算是对对象Object.prototype的类型判定。
文档解释
从官方文档中我们可以了解到,当toString方法被调用时,将执行以下步骤:
- 如果this值未定义,返回"[object undefined]" : 这意味着如果你尝试对
undefined
调用toString()
方法(例如,toString.call(undefined)
),它会返回字符串"[object Undefined]"
。这是因为undefined
没有自己的属性或方法,所以它会遵循原型链找到Object.prototype.toString
,但实际操作的对象是undefined
,因此返回的是表示undefined
的特殊字符串。 - 如果this值为null,则返回"[object null]" : 同样地,当你尝试对
null
使用toString()
方法(如toString.call(null)
),它会返回"[object Null]"
。这里,尽管null
也是一个对象类型的特殊值,它的toString
行为也是由Object.prototype.toString
定义的,并且特例化为返回表示null
的字符串。 - 设0为传递this值作为参数调用ToObject的结果: 这一步是说,在实际执行
toString
的具体逻辑之前,首先会确保this
值是一个对象。如果this
不是对象(比如原始值如数字、字符串等),JavaScript引擎会使用内部操作ToObject
将其转换成相应的对象形式。例如,数字5会被转换成Number对象,字符串"hello"会被转换成String对象。但对于null
和undefined
,这一步不会执行,因为前面已经处理了这两种特殊情况。 - 设class为0的[[Class]]内部属性的值: 每个JavaScript对象都有一个内部属性
[[Class]]
,它表示对象的类型。这个属性用户不可见,但在内部用于区分不同的对象类型,比如"Array"、"Date"、"Function"等。这一步就是获取这个内部属性的值,它代表了对象的具体类型。 - 返回三个字符串"[object ", class和"]"连接后的String值: 最后方法将拼接一个字符串,以
"[object "
开始,紧接着是第4步得到的类型名,最后以"]"
结束。这样就形成了类似"[object Array]"
、"[object String]"
的输出,清晰地表明了对象的类型。
重点解释
具体到数组的例子,即使数组有自己的 toString
方法(默认情况下主要用于将数组元素连接成字符串),通过 Object.prototype.toString.call
调用会绕过数组的 toString
实现,直接使用 Object.prototype.toString
,它会基于对象的内部类型信息(即 [[Class]] 属性),返回如 "[object Array]"
这样的字符串,精确地表示了对象的类型,而不是简单地将数组元素转换为字符串。
let arr=new Array(1,2,3,4)
console.log(arr.toString())//调用的是Array.prototype.toString()方法
let obj={
name:'Li'
}
console.log(obj.toString())//调用的是Object.prototype.toString()方法
console.log(Object.prototype.toString.call(arr));
总结
在JavaScript中理解并正确应用类型检测方法对于编写高效、可靠的代码至关重要。typeof
、instanceof
以及 Object.prototype.toString()
是三种常用的类型检测手段,各自有不同的应用场景和特点。
-
typeof:用于获取变量的基本类型信息,如
"number"
、"string"
、"boolean"
、"undefined"
、"function"
(注意对于对象,包括数组和函数,typeof
会返回"object"
,而对于null
,则存在一个历史遗留的异常,返回"object"
)。typeof
的判断并非基于二进制表示,而是遵循JavaScript引擎内部的类型识别逻辑。简单快捷,但无法区分具体对象类型和对null
的处理存在异常。 -
instanceof:用于判断一个对象是否为某个构造函数的实例。它通过检查对象的原型链是否包含构造函数的
prototype
属性来工作。instanceof
仅适用于对象类型,对于基本类型如数字或字符串,直接使用会返回false
。适用于检测对象的构造函数原型链,但不适用于基本类型,且在涉及多态或原型继承的复杂场景下可能不够直观。 -
Object.prototype.toString() :提供了一种非常精确的类型检测方法。通过修改
this
指向(通常使用call()
或apply()
方法),可以在任意对象上调用此方法,它会返回形如[object Type]
的字符串,其中Type
准确地指明了对象的类型,包括数组、正则表达式等特殊对象。此方法直接访问对象的内部 [[Class]] 属性,从而能够超越typeof
的限制,提供更详细的类型信息。但使用相对复杂,需要通过call()
或apply()
调用来改变上下文。
转载自:https://juejin.cn/post/7378146751067537423