花费28800秒,从源码的角度我终于知道了 typeof null 为 object 的原理

1. 背景

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

 

2. 汇总解释

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

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

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

在这里插入图片描述

 

如果单纯是这样必然是不对的,是 空对象指针 就一定会被检测为 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

在这里插入图片描述

这就是 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 最初设计的问题

在这里插入图片描述

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

在这里插入图片描述

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

  • 首先,创建了一个 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)](https://2ality.com/2013/10/typeof-null.html#:~:text=In JavaScript%2C typeof null is,it would break existing code)

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

he history of “typeof null” (2ality.com)](https://2ality.com/2013/10/typeof-null.html#:~:text=In JavaScript%2C typeof null is,it would break existing code)

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

  • 36
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JsonUtility是Unity内置的用于序列化和反序列化JSON数据的工具类,以下是它的部分源码: ```c# public static class JsonUtility { // 将一个对象序列化为JSON格式的字符串 public static string ToJson(object obj, bool prettyPrint = false) { if (obj == null) return "null"; var jsonWriter = new JsonWriter { prettyPrint = prettyPrint }; SerializeValue(obj, jsonWriter); return jsonWriter.ToString(); } // 将一个JSON格式的字符串反序列化为指定类型的对象 public static T FromJson<T>(string json) { if (string.IsNullOrEmpty(json)) return default(T); var reader = new JsonReader(json); return (T)DeserializeValue(reader, typeof(T)); } // 序列化对象 private static void SerializeValue(object value, JsonWriter writer) { if (value == null) { writer.Write(null); } else if (value is bool) { writer.Write((bool)value); } else if (value is string) { writer.Write((string)value); } else if (value is char) { writer.Write((char)value); } else if (value is Enum) { writer.Write((int)value); } else if (value is int) { writer.Write((int)value); } else if (value is uint) { writer.Write((uint)value); } else if (value is long) { writer.Write((long)value); } else if (value is ulong) { writer.Write((ulong)value); } else if (value is float) { writer.Write((float)value); } else if (value is double) { writer.Write((double)value); } else if (value is decimal) { writer.Write((decimal)value); } else if (value is Array) { SerializeArray((Array)value, writer); } else if (value is IList) { SerializeList((IList)value, writer); } else if (value is IDictionary) { SerializeDictionary((IDictionary)value, writer); } else if (value is UnityEngine.Object) { SerializeUnityObject((UnityEngine.Object)value, writer); } else { SerializeObject(value, writer); } } // 反序列化对象 private static object DeserializeValue(JsonReader reader, Type type) { if (type == typeof(bool)) { return reader.ReadBoolean(); } else if (type == typeof(string)) { return reader.ReadString(); } else if (type == typeof(char)) { return reader.ReadChar(); } else if (type.IsEnum) { return Enum.ToObject(type, reader.ReadInt32()); } else if (type == typeof(int)) { return reader.ReadInt32(); } else if (type == typeof(uint)) { return reader.ReadUInt32(); } else if (type == typeof(long)) { return reader.ReadInt64(); } else if (type == typeof(ulong)) { return reader.ReadUInt64(); } else if (type == typeof(float)) { return reader.ReadSingle(); } else if (type == typeof(double)) { return reader.ReadDouble(); } else if (type == typeof(decimal)) { return reader.ReadDecimal(); } else if (type.IsArray) { return DeserializeArray(reader, type.GetElementType()); } else if (typeof(IList).IsAssignableFrom(type)) { return DeserializeList(reader, type); } else if (typeof(IDictionary).IsAssignableFrom(type)) { return DeserializeDictionary(reader, type); } else if (typeof(UnityEngine.Object).IsAssignableFrom(type)) { return DeserializeUnityObject(reader, type); } else { return DeserializeObject(reader, type); } } // 序列化数组 private static void SerializeArray(Array array, JsonWriter writer) { writer.WriteArrayStart(); for (int i = 0; i < array.Length; i++) { SerializeValue(array.GetValue(i), writer); } writer.WriteArrayEnd(); } // 序列化列表 private static void SerializeList(IList list, JsonWriter writer) { writer.WriteArrayStart(); for (int i = 0; i < list.Count; i++) { SerializeValue(list[i], writer); } writer.WriteArrayEnd(); } // 序列化字典 private static void SerializeDictionary(IDictionary dictionary, JsonWriter writer) { writer.WriteObjectStart(); foreach (DictionaryEntry entry in dictionary) { writer.WritePropertyName(entry.Key.ToString()); SerializeValue(entry.Value, writer); } writer.WriteObjectEnd(); } // 序列化Unity对象 private static void SerializeUnityObject(UnityEngine.Object obj, JsonWriter writer) { writer.WriteObjectStart(); writer.WritePropertyName("instanceID"); writer.Write(obj.GetInstanceID()); writer.WriteObjectEnd(); } // 序列化普通对象 private static void SerializeObject(object obj, JsonWriter writer) { writer.WriteObjectStart(); var fields = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (var field in fields) { if (field.IsNotSerialized) continue; writer.WritePropertyName(field.Name); SerializeValue(field.GetValue(obj), writer); } writer.WriteObjectEnd(); } // 反序列化数组 private static Array DeserializeArray(JsonReader reader, Type elementType) { var list = new List<object>(); reader.ReadArrayStart(); while (!reader.IsArrayEnd) { list.Add(DeserializeValue(reader, elementType)); } reader.ReadArrayEnd(); var array = Array.CreateInstance(elementType, list.Count); for (int i = 0; i < list.Count; i++) { array.SetValue(list[i], i); } return array; } // 反序列化列表 private static object DeserializeList(JsonReader reader, Type type) { var elementType = type.GetGenericArguments()[0]; var list = Activator.CreateInstance(type) as IList; reader.ReadArrayStart(); while (!reader.IsArrayEnd) { list.Add(DeserializeValue(reader, elementType)); } reader.ReadArrayEnd(); return list; } // 反序列化字典 private static object DeserializeDictionary(JsonReader reader, Type type) { var keyType = type.GetGenericArguments()[0]; var valueType = type.GetGenericArguments()[1]; var dictionary = Activator.CreateInstance(type) as IDictionary; reader.ReadObjectStart(); while (!reader.IsObjectEnd) { var key = DeserializeValue(reader, keyType); var value = DeserializeValue(reader, valueType); dictionary.Add(key, value); } reader.ReadObjectEnd(); return dictionary; } // 反序列化Unity对象 private static UnityEngine.Object DeserializeUnityObject(JsonReader reader, Type type) { var instanceID = 0; reader.ReadObjectStart(); while (!reader.IsObjectEnd) { var propertyName = reader.ReadPropertyName(); if (propertyName == "instanceID") { instanceID = reader.ReadInt32(); } else { reader.SkipValue(); } } reader.ReadObjectEnd(); return UnityEngine.Object.FindObjectFromInstanceID(instanceID); } // 反序列化普通对象 private static object DeserializeObject(JsonReader reader, Type type) { var obj = Activator.CreateInstance(type); reader.ReadObjectStart(); while (!reader.IsObjectEnd) { var propertyName = reader.ReadPropertyName(); var field = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(obj, DeserializeValue(reader, field.FieldType)); } else { reader.SkipValue(); } } reader.ReadObjectEnd(); return obj; } } ``` 以上是JsonUtility的部分源码,它主要包含了ToJson和FromJson两个方法,分别用于将一个对象序列化为JSON格式的字符串和将一个JSON格式的字符串反序列化为指定类型的对象。在序列化和反序列化时,JsonUtility会根据对象的类型选择不同的处理方式。例如,如果对象是一个数组,则会调用SerializeArray方法进行序列化;如果对象是一个Unity对象,则会调用SerializeUnityObject方法进行序列化。在反序列化时也是同样的方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值