likes
comments
collection
share

【JS】从源码的角度了解 typeof null 为 object 的原理

作者站长头像
站长
· 阅读数 27

1. 背景

我之前一直知道 typeof null 为 object ,但是一直没深究为什么?在查阅了相关的文档,汇总为以下文档

 

2. 汇总解释

我们先来看 《JavaScript高级程序设计》 里面是怎么说的吧?

Null 类型同样只有一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回 "object" 的原因

这里我们就可以解释通了,null 其实是一个 空对象指针,所以 typeof 会检测 nullobject

【JS】从源码的角度了解 typeof null 为 object 的原理

 

如果单纯是这样必然是不对的,是 空对象指针 就一定会被检测为 object 吗?

这其实就是一句总结,里面肯定有更加深层的原因,我看了一眼 ECMAScript 的文档,发现没有说这一点(可能是我没找到),倒是在 MDN 发现了解释

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"

我来简单解释一遍,对象类型表示为 0null 在大部分机器上面都是 0x00...000,所以 null0,所以 nulltypeof 下 会被检测为 object

 

3. 早期解释

这个解释已经能解答很多疑惑了,如果我们再往下面看一层呢?为什么对象类型为 0,null 为 0x00typeof 底层分析类型是通过什么手段?以及这部分源码是什么样子的?

其实 JavaScript 的数据类型其实是通过 数据标签 + 数据值 的形式存在的,下面就是常见的数据标签

数据标签类型
000object
1int
010double
100string
110boolean

在32位机器中,int 的 标签为 1,占了一位,那么后续的值为 2 ^ 30 ~2 ^ 30 - 1(根据上图推算,现代JS早就改);其中 object 的标签为 000

【JS】从源码的角度了解 typeof null 为 object 的原理

这就是 JavaScript 的数据类型如何去做处理和区分的,当然还有2个特殊的值

  • undefined 源码标记为 JSVAL_VOID
  • null 源码标记为 JSVAL_NULL :是机器代码的 NULL指针。或者说,它是一个对象类型标记加上一个引用为零的组合,大部分机器都认定为 0x000

 

我们来看下面的 typeof 源码部分

  • 发现没有 显式的判断 null值 的标记,只有 undefined,object,number,string,boolean 的判断
  • 再加上 null 在很多的机器中默认是 0x000,那么就会走 JSVAl_IS_OBJECT(v) 的逻辑,被认定为 object 类型
// 这是一个宏,它指定了函数的返回类型为 JSType,并且声明该函数是一个公共API
JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v) {
    JSType type = JSTYPE_VOID;
    JSObject *obj;
    JSObjectOps *ops;
    JSClass *clasp;
	
    // 这是一个宏,用于检查传入的 JSContext 是否有效
    CHECK_REQUEST(cx);
    
    if (JSVAL_IS_VOID(v)) { 
        type = JSTYPE_VOID;
    } else if (JSVAL_IS_OBJECT(v)) {
        // 如果 v 是对象类型,则检查它是否是函数对象,如果是函数对象则将 type 设置为 JSTYPE_FUNCTION
        // 否则将其设置为 JSTYPE_OBJECT
        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) : ops->call != 0)) {  
            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;
}

 

这也是网上大部分的解释,因为在 JavaScript 设计初期,时间很短,所以导致一些地方没考虑到,那如今的JS引擎怎么做的呢?

 

4. V8解释

V8 Blog 对于 typeof null 为 object 的解释。我来简单解释一下:V8 Blog 认为 null 表示 no object value,所以输出 object 没问题

当然这有一股王婆卖瓜,自卖自夸的感觉了,毕竟是 JavaScript 最初设计的问题

【JS】从源码的角度了解 typeof null 为 object 的原理

 

我们先下载 V8 的源码来看看:v8/v8.git - Git at Google (googlesource.com),根据知乎大佬的提示,找到了下面的源码部分

【JS】从源码的角度了解 typeof null 为 object 的原理

我们来看下下面的整体流程:

  • 首先,创建了一个 TNode<String> 类型的函数 Typeof,它接受一个 TNode<Object> 类型的参数 value

  • 创建了一些标签,这些标签用于控制程序执行流程。例如,return_number 表示如果是数字类型就返回数字类型的字符串,return_function 表示如果是函数类型就返回函数类型的字符串,以此类推。

  • 使用 GotoIf 语句检查 value 的类型,并根据类型跳转到相应的标签处进行处理。比如,如果 value 是一个 Smi(表示一个被标记为小整数的对象),那么就跳转到 return_number 标签处。

  • 如果 value 不是 Smi,那么获取其对应的 HeapObject,并加载其 Map 对象。

  • 根据 Map 对象判断 value 的类型,并跳转到相应的标签进行处理。例如,如果是一个奇怪的对象(Oddball),则跳转到 if_oddball 标签处。

  • 在各个标签处,根据对象的具体类型设置 result_var 的值为相应类型的字符串,并跳转到 return_result 标签处,返回 result_var 的值。

  • 最后,返回 result_var 的值。

可以发现 V8 其实对 null 做了提前的处理,可能是为了兼容早期 V8 做的吧

// 定义一个函数,用于模拟 JavaScript 的 typeof 操作符
TNode<String> CodeStubAssembler::Typeof(TNode<Object> value) {
  TVARIABLE(String, result_var);

  // 定义标签,用于控制程序流程
  Label return_number(this, Label::kDeferred), if_oddball(this),
      return_function(this), return_undefined(this), return_object(this),
      return_string(this), return_bigint(this), return_symbol(this),
      return_result(this);

  GotoIf(TaggedIsSmi(value), &return_number);
  TNode<HeapObject> value_heap_object = CAST(value);
  TNode<Map> map = LoadMap(value_heap_object);
  GotoIf(IsHeapNumberMap(map), &return_number);
  TNode<Uint16T> instance_type = LoadMapInstanceType(map);

  // 如果是oddball,则跳转到 if_oddball 标签处,V8 对 null提前做了判断
  GotoIf(InstanceTypeEqual(instance_type, ODDBALL_TYPE), &if_oddball);
  TNode<Int32T> callable_or_undetectable_mask =
      Word32And(LoadMapBitField(map),
                Int32Constant(Map::Bits1::IsCallableBit::kMask |
                              Map::Bits1::IsUndetectableBit::kMask));
  GotoIf(Word32Equal(callable_or_undetectable_mask,
                     Int32Constant(Map::Bits1::IsCallableBit::kMask)),
         &return_function);
  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);
  GotoIf(IsSymbolInstanceType(instance_type), &return_symbol);

  // 如果类型未知,则终止程序执行
  Abort(AbortReason::kUnexpectedInstanceType);

  BIND(&return_number);
  {
    result_var = HeapConstantNoHole(isolate()->factory()->number_string());
    Goto(&return_result);
  }

  BIND(&if_oddball);
  {
    TNode<String> type =
        CAST(LoadObjectField(value_heap_object, offsetof(Oddball, type_of_)));
    result_var = type;
    Goto(&return_result);
  }

  BIND(&return_function);
  {
    result_var = HeapConstantNoHole(isolate()->factory()->function_string());
    Goto(&return_result);
  }

  BIND(&return_undefined);
  {
    result_var = HeapConstantNoHole(isolate()->factory()->undefined_string());
    Goto(&return_result);
  }

  BIND(&return_object);
  {
    result_var = HeapConstantNoHole(isolate()->factory()->object_string());
    Goto(&return_result);
  }

  BIND(&return_string);
  {
    result_var = HeapConstantNoHole(isolate()->factory()->string_string());
    Goto(&return_result);
  }

  BIND(&return_bigint);
  {
    result_var = HeapConstantNoHole(isolate()->factory()->bigint_string());
    Goto(&return_result);
  }

  BIND(&return_symbol);
  {
    result_var = HeapConstantNoHole(isolate()->factory()->symbol_string());
    Goto(&return_result);
  }

  BIND(&return_result);
  return result_var.value();
}

 

5. 总结

到这里继续深究下去已经没必要了,最初设计的缺陷导致的,当然这次的查询告诉我们对于高级语言的设计并不一定都是对的,如果想深究,也能看到 JavaScript 很多不合理的地方

 

参考文章

javascript - Why is typeof null "object"? - Stack Overflow

The story of a V8 performance cliff in React · V8

typeof - JavaScript | MDN (mozilla.org)

[The history of “typeof null” (2ality.com)](2ality.com/2013/10/typ… JavaScript%2C typeof null is,it would break existing code)

typeof 与 Javascript 类型源码分析 - 知乎 (zhihu.com)

转载自:https://juejin.cn/post/7357921175559847977
评论
请登录