null是一个空对象的引用?
红宝书第30页和32页,有写特殊值null被认为是一个对空对象的引用。
但从ecma262规范来看null的值表示不包含任何对象值的基本值,Null的类型只有一个值null。
MDN里关于null是这样说的
值 null 是一个字面量,不像 undefined,它不是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用。
可能这样解释更好理解。
typeof null
给typeof传一个null会返回Object,红宝书给出的原因是因为null是一个空对象的引用。
但上面说了这个说法只是为了更容易理解。那typeof为什么会返回Object呢?
可以参考 the history of "typeof null" 这篇文章,也就是第一版的JavaScript是用32位比特来存储值的,且是通过值的低1位或3位来识别类型的。
- 1:整型(int)
- 000:引用类型(object)
- 010:双精度浮点型(double)
- 100:字符串(string)
- 110:布尔型(boolean)
另外还用两个特殊值:
- undefined,用整数−2^30(负2的30次方,不在整型的范围内)
- null,机器码空指针(C/C++ 宏定义),低三位也是000
也就是C语言的空指针低三位是000,而JavaScript的Object类型低三位也是000。
而在JS_TypeOfValue(也就是typeof的源代码)里,是没有先过滤null的,导致在判断Object阶段产生了误会。
也就是说给typeof传一个null会返回Object这其实是一个Bug。
第一版JavaScript中关于typeof的源码:
JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
JSType type = JSTYPE_VOID;
JSObject *obj;
JSObjectOps *ops;
JSClass *clasp;
CHECK_REQUEST(cx);
if (JSVAL_IS_VOID(v)) { // (1)
type = JSTYPE_VOID;
} else if (JSVAL_IS_OBJECT(v)) { // (2)
obj = JSVAL_TO_OBJECT(v);
if (obj &&
(ops = obj->map->ops,
ops == &js_ObjectOps
? (clasp = OBJ_GET_CLASS(cx, obj),
clasp->call || clasp == &js_FunctionClass) // (3,4)
: ops->call != 0)) { // (3)
type = JSTYPE_FUNCTION;
} else {
type = JSTYPE_OBJECT;
}
} else if (JSVAL_IS_NUMBER(v)) {
type = JSTYPE_NUMBER;
} else if (JSVAL_IS_STRING(v)) {
type = JSTYPE_STRING;
} else if (JSVAL_IS_BOOLEAN(v)) {
type = JSTYPE_BOOLEAN;
}
return type;
}
typeof null === 'null'的提案
是Bug当然得修复啦,所以在ECMA6中,曾经有提案为历史平反。将typeof null的值纠正为null, 但最后提案被拒了。
原因是这样会让现存的所有站点出现破坏。
提案详情:harmony:typeof_null
所以Bug就变成feature了。
后续
当然这是个历史遗留问题,因为种种原因又没办法修改。所以现在的解释器都会兼容这个feature。
比如V8中,不是用3个bit来表示类型的,typeof null 是当做一个特殊情况处理的。
V8中 typeof 的源码:
TNode<String> CodeStubAssembler::Typeof(SloppyTNode<Object> value) {
// null 的 instance_type 为 ODDBALL_TYPE,不再做后续判断,直接拦截掉
// 如果删掉下面一行,typeof null 返回 undefined
GotoIf(InstanceTypeEqual(instance_type, ODDBALL_TYPE), &if_oddball); // 就是这一行
GotoIfNot(Word32Equal(callable_or_undetectable_mask, Int32Constant(0)),
&return_undefined);
GotoIf(IsJSReceiverInstanceType(instance_type), &return_object);
GotoIf(IsStringInstanceType(instance_type), &return_string);
GotoIf(IsBigIntInstanceType(instance_type), &return_bigint);
// 前后源码都有删减
}
instance_type 表示 JS 对象的类型,可能的值为 SYMBOL_TYPE、BIGINT_TYPE 等,但 null 的 instance_type 为 ODDBALL_TYPE(译为奇葩类型?),typeof 的处理函数会对 ODDBALL_TYPE 优先判断并返回。