大家好,我是你们的老朋友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 中的原始类型包括:number、string、boolean、undefined、null、symbol、bigint(后两项为ES6 新增)。其中的 typeof 可以准确判断除了null 以外的所有原始类型。这明显是个bug!"typeof先生辩解道,"这是JS历史遗留问题,null的二进制表示全是0,被误认为对象了!"那为什么null的二进制全为零就会被误认为是对象呢,这个问题我们放到后面再讨论。
更糟的是,面对对象家族:
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof function(){}); // "function"
我们又发现了问题,在js中引用类型包括:Array、Object、Function、Date、Set、Map 等,但是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]])中。具体来说,它会执行以下步骤:
-
获取
object的原型链,也就是object.__proto__。 -
获取
Constructor的原型,即Constructor.prototype。 -
比较这两个原型:
- 若相等,返回
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 引擎会:
-
获取
value的[[Class]]属性。 -
或调用
value[Symbol.toStringTag]方法获取自定义标签。 -
将结果格式化为
"[object Type]"字符串。
Object.prototype.toString.call(value)之所以能精准判断类型,是因为它直接读取了 JavaScript 对象内部的[[Class]]属性或Symbol.toStringTag,这使得它能区分所有内置类型,包括null、undefined、Date等。在需要严格类型检测的场景(如工具库、框架开发)中,这是最可靠的方法。
特别行动组:Array.isArray
对于数组这个特殊"嫌疑人",我们有专门的特工:
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
- 专门用于判断是否为数组类型,是目前最直接、最准确的数组类型判断方式。
- 该方法未挂载在 Array.prototype 上,而是直接作为静态方法存在,只能通过 Array.isArray() 调用
"简单粗暴,专案专办!"特工骄傲地说。
破案手册(总结)
- 基本类型检查:用
typeof(记得null会撒谎) - 引用类型检查:用
instanceof(基本类型会逃过检测) - 终极鉴定:
Object.prototype.toString.call(全能但写法麻烦) - 数组专项:直接用
Array.isArray
记住,在JS这个充满谜题的世界里,没有完美的类型判断方法,只有最合适的工具组合。现在,带上你的侦探装备,去破解代码中的类型谜案吧!