1. JS 内部存储值的方式(type tag)
JavaScript 最早期的实现里,每个值在内存里都要有 类型标记(type tag) ,用来告诉 JS 引擎这个值是什么类型。
-
比如:
值类型 内存 type tag number 1 string 2 object 0 boolean 3 … … -
JS 引擎执行
typeof x时,并不是去理解x的语义,而是直接查这个 内存里的 type tag。 -
也就是说
typeof早期只是个 低成本内存检查操作。
2. 为什么 null 的 type tag 是 0
-
当时 JS 内部实现
null的方式是:- 它本质上是一个指针,指向 空对象引用(null pointer)。
- 空对象指针的 type tag 被标记为 0,也就是对象。
-
所以:
typeof null // 读取 type tag → 0 → 返回 "object" -
注意,这里不是在问“null 语义上是不是对象”,而是引擎只是根据 type tag 决定返回值。
3. 语义 vs 内存实现
| 角度 | 对 null 的理解 |
|---|---|
| 语义 | null 表示“没有对象引用”,是原始值,不是对象 |
| 内存实现 | JS 最初实现中,null 用空对象指针存储,type tag=0 |
| typeof 结果 | typeof 直接读取 type tag → "object" |
所以 typeof null === 'object' 是 历史遗留的实现细节,而不是语言逻辑设计的结果。
Object.prototype.toString.call(value)
一、基本用法
Object.prototype.toString.call(value)
value可以是任意 JS 值(原始值或对象)。- 输出格式:
"[object Type]"
其中 Type 表示值的内部类型标签([[Class]]),比 typeof 更准确。
二、示例
| 值 | typeof | Object.prototype.toString.call |
|---|---|---|
undefined | "undefined" | "[object Undefined]" |
null | "object" | "[object Null]" |
123 | "number" | "[object Number]" |
"abc" | "string" | "[object String]" |
true | "boolean" | "[object Boolean]" |
Symbol() | "symbol" | "[object Symbol]" |
[] | "object" | "[object Array]" |
{} | "object" | "[object Object]" |
function(){} | "function" | "[object Function]" |
new Date() | "object" | "[object Date]" |
/regex/ | "object" | "[object RegExp]" |
Promise.resolve() | "object" | "[object Promise]" |
new Map() | "object" | "[object Map]" |
new Set() | "object" | "[object Set]" |
可以看到,它可以准确区分数组、日期、正则、Map、Set、Promise 等,而
typeof全部都是"object"(除了函数和基本类型)。
三、原理
-
JS 对象都有一个内部属性
[[Class]](在现代规范中叫Symbol.toStringTag):- 这是一个隐藏属性,用来标识对象内部类别。
⚠️ 二、重点:引用类型 ≠ 都是“对象本身”,但 typeof 都返回 "object"
在 JavaScript 中:
所有引用数据类型(object、array、function、Date、RegExp、Map、Set…)
都是通过Object派生出来的。
因此:
typeof [] // "object"
typeof {} // "object"
typeof new Date() // "object"
typeof null // "object" ❗️bug 遗留
typeof /abc/ // "object"
所以你可以记:
✅ “除了函数是 function,其他引用类型 typeof 都是 object。”