JavaScript类型判断:从"疑犯追踪"到"真相大白"

107 阅读4分钟

大家好,我是你们的老朋友JS侦探社社长,今天我们要破解一桩悬案——"这个变量到底是什么类型?"。在JavaScript这个充满谜团的世界里,掌握类型判断技巧就像拥有了一把万能钥匙,让我们开始这场侦探之旅吧!

第一案:typeof的"近视眼"

我们的第一位目击证人typeof先生,他声称:

console.log(typeof 42); // "number" 
console.log(typeof "hello"); // "string" 
console.log(typeof true); // "boolean" 

看起来很不错?但当我们问到null时...

console.log(typeof null); // "object" 

这对吗,很明显不对,在JavaScript 中的原始类型包括:numberstringbooleanundefinednullsymbolbigint(后两项为ES6 新增)。其中的 typeof 可以准确判断除了null 以外的所有原始类型。这明显是个bug!"typeof先生辩解道,"这是JS历史遗留问题,null的二进制表示全是0,被误认为对象了!"那为什么null的二进制全为零就会被误认为是对象呢,这个问题我们放到后面再讨论。 更糟的是,面对对象家族:

console.log(typeof []); // "object" 
console.log(typeof {}); // "object" 
console.log(typeof function(){}); // "function" 

我们又发现了问题,在js中引用类型包括:ArrayObjectFunctionDateSetMap 等,但是typeof是个"近视眼",只能看清基本类型(除了null),对对象家族几乎全员"脸盲"(除了函数)。我们要探究其原因首先要了解typeof工作原理,其原理为通过将值转换为二进制进行判断,对于二进制前三位是 0 的统一识别为对象,而所有的引用类型转换成二进制前三位都是 0, null 的二进制表示是全 0,所以识别为 object 类型,但是在这里你可能要问了为什么Function也是引用类型它为什么能判断正确,这是因为函数之所以能被typeof正确识别为"function",是因为 JavaScript 引擎对可调用对象做了特殊处理,不会单纯依据前三位二进制来判断。而null之所以被误判为"object",是由于其全 0 的二进制表示和对象的前三位 000 相匹配。到这里你肯定清楚了typeof的类型判断原理。

第二案:instanceof的"家族血统检测"

接下来是instanceof探长,他专门研究"家族血统":

console.log([] instanceof Array); // true 
console.log({} instanceof Object); // true 
console.log(function(){} instanceof Function); // true 

//当他遇到基本类型时
console.log("hello" instanceof String); // false 

你肯定会好奇这是为什么,首先我们要了解instanceof的基本语法

object instanceof Constructor
  • object:待检测的对象。
  • Constructor:构造函数。
  • 返回值为true或者false

核心原理为:instanceof会检查Constructor.prototype是否存在于object的原型链([[Prototype]])中。具体来说,它会执行以下步骤:

  1. 获取object的原型链,也就是object.__proto__

  2. 获取Constructor的原型,即Constructor.prototype

  3. 比较这两个原型:

    • 若相等,返回true
    • 若不相等,则继续向上查找object原型链的上一层(即object.__proto__.__proto__),重复比较操作。
    • 当查找到原型链的顶端(Object.prototype.__proto__ === null)时,若仍未找到相等的原型,就返回false

"这不怪我!"instanceof探长摊手,"基本类型没有原型链,除非它们被临时包装成对象...",所以在instanceof使用基本类型得到的都是false

第三案:toString的"DNA鉴定"

最后登场的是法医Object.prototype.toString,提供最精准的"DNA鉴定":

console.log(Object.prototype.toString.call(42)); // [object Number] 
console.log(Object.prototype.toString.call(null)); // [object Null] 
console.log(Object.prototype.toString.call([])); // [object Array] 

"看到没?"法医得意地说,"我连null都能认出来!秘密在于读取对象的内部[[Class]]属性。" 每个 JavaScript 对象(包括基本类型的包装对象)都有一个内部属性[[Class]],这是一个内部属性,无法直接访问。它的值是一个字符串,表示对象的具体类型(如"Array""Date""Number"等)。所有内置对象的[[Class]]都由 JavaScript 引擎自动设置。当调用Object.prototype.toString.call(value)时,JavaScript 引擎会:

  1. 获取value[[Class]]属性。

  2. 或调用value[Symbol.toStringTag]方法获取自定义标签。

  3. 将结果格式化为"[object Type]"字符串。

Object.prototype.toString.call(value)之所以能精准判断类型,是因为它直接读取了 JavaScript 对象内部的[[Class]]属性或Symbol.toStringTag,这使得它能区分所有内置类型,包括nullundefinedDate等。在需要严格类型检测的场景(如工具库、框架开发)中,这是最可靠的方法。

特别行动组:Array.isArray

对于数组这个特殊"嫌疑人",我们有专门的特工:

console.log(Array.isArray([])); // true 
console.log(Array.isArray({})); // false 
  • 专门用于判断是否为数组类型,是目前最直接、最准确的数组类型判断方式。
  • 该方法未挂载在 Array.prototype 上,而是直接作为静态方法存在,只能通过 Array.isArray() 调用

"简单粗暴,专案专办!"特工骄傲地说。

破案手册(总结)

  1. 基本类型检查:用typeof(记得null会撒谎)
  2. 引用类型检查:用instanceof(基本类型会逃过检测)
  3. 终极鉴定Object.prototype.toString.call(全能但写法麻烦)
  4. 数组专项:直接用Array.isArray

记住,在JS这个充满谜题的世界里,没有完美的类型判断方法,只有最合适的工具组合。现在,带上你的侦探装备,去破解代码中的类型谜案吧!