重读红宝书(一):关于Null你知道哪些?

406 阅读3分钟

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. 1:整型(int)
  2. 000:引用类型(object)
  3. 010:双精度浮点型(double)
  4. 100:字符串(string)
  5. 110:布尔型(boolean)

另外还用两个特殊值:

  1. undefined,用整数−2^30(负2的30次方,不在整型的范围内)
  2. null,机器码空指针(C/C++ 宏定义),低三位也是000

也就是C语言的空指针低三位是000,而JavaScriptObject类型低三位也是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 优先判断并返回。