typeof最新原理解析

"薛定谔的对象"

我们都知道 typeof(null) === 'object',关于原因,在小黄书《你不知道的JavaScript》中有这么一段解释:

原理是这样的, 不同的对象在底层都表示为二进制, 在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型, null 的二进制表示是全 0, 自然前三位也是 0, 所以执行 typeof 时会返回“object”。

javascript 中的 null:既是对象,又不是对象,史称「薛定谔的对象」。

typeof null === 'object';
null instanceof Object === false

null instanceof null

会抛出异常:

Uncaught TypeError: Right-hand side of 'instanceof' is not an object

这是一个历史遗留下来的 feature(or bug?),The history of “typeof null”

在 javascript 的最初版本中,使用的 32 位系统,为了性能考虑使用低位存储了变量的类型信息:

  • 000:对象
  • 1:整数
  • 010:浮点数
  • 100:字符串
  • 110:布尔

有 2 个值比较特殊:

  • undefined:用 - (−2^30)表示。
  • null:对应机器码的 NULL 指针,一般是全零。

在第一版的 javascript 实现中,判断类型的代码是这么写的:

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;
}

(1):判断是否为 undefined (2):如果不是 undefined,判断是否为对象 (3):如果不是对象,判断是否为数字 (4):。。。

这样一来,null 就出了一个 bug。根据 type tags 信息,低位是 000,因此 null 被判断成了一个对象。这就是为什么 typeof null 的返回值是 object

关于 null 的类型在 MDN 文档中也有简单的描述:typeof - javascript | MDN

在 ES6 中曾有关于修复此 bug 的提议,提议中称应该让 typeof null === 'null'http://wiki.ecmascript.org/do...:typeof_null 但是该提议被无情的否决了,自此 typeof null 终于不再是一个 bug,而是一个 feature,并且永远不会被修复。


这是 JavaScript 最初实现的一个 bug,目前的 JavaScript 引擎已经不这么去实现了,但是这个 bug 却一直流传了下来。

至于对象的内部表示,不同的 JavaScript 引擎实现起来都是不一样的,单说说 V8 吧。

V8 类型继承图:

其实在堆管理模型中, v8会把类型信息和数据“捆绑”在一起, 如果一个一个变量作为一个对象的成员而存在, 虽然它的值为原始类型, 但也是可能被存储在堆(HeapObject)上的, 变量存储在栈上, 会提高变量的查找和访问速度.

那v8具体是如何判断js的数据类型的呢? 简单来说,猜和推导。

这里举个例子:

var a = 5, b = 3, c = "phark";
var d = a + b;
var e = c + d;

那么一个很简单的推测就是,a和b是number,c是字符串;也很容易推导出d是number,e是字符串。然后引擎保存变量的时候既有它的值的信息,也有它的类型信息。然后把整个程序推导出来。

这是其一。

其二,因为javascript是动态类型,可能出现如下情况:

addFunction = function(a, b) { return a + b; }
addFunction(1, "233");
addFunction(1, 233);

两个函数实际上行为会出现很大差异。实际上,这两个函数你可以认为就是Java中的重载。只不过它的重载时间有时可以在编译器确定,有时则要等到运行期的时候才能确定。(例如来自用户输入)我们等它什么时候确定了,再来做JIT之类的事情。当然,编译器或者JIT Engine可能需要一个LUT来保存一些特例形式,或者干脆就用解释器来做。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

让你的Python代码更加pythonic

何为pythonic? pythonic如果翻译成中文的话就是很python。很+名词结构的用法在中国不少,比如:很娘,很国足,很CCTV等等。 我的理解为...

22140
来自专栏码洞

《快学 Go 语言》第 3 课 —— 分支与循环

上面这个等式每一个初学编程的同学都从老师那里听说过。它并不是什么严格的数据公式,它只是对一般程序的简单认知。数据结构是内存数据关系的静态表示,算法是数据结构从一...

11530
来自专栏编程之旅

数据结构——优先队列(C++和Java实现)

十几天没有更新自己的博客了,因为目前在算法和数据结构的学习中,碰到了一些问题,例如之前就在优先队列,堆这个数据结构面前,感觉到有点吃不透概念,而使用的那本书上写...

16330
来自专栏写代码的海盗

脱掉Golang的第一层衣裳 golang入坑系列

海鳖曾欺井内蛙,大鹏张翅绕天涯。强中更有强中手,莫向人前满自夸。 各位看官,现在开始脱衣裳。你不用脱,自个衣裳要穿好了,别脱下来。我们是来学Golang的,不...

30030
来自专栏小勇DW3

奇偶数线程交替执行问题

一个面试题:两个线程,一个打印偶数,一个打印奇数,并且轮流打印,我们可以看到这种场景模式肯定是需要通过同步来实现,

25120
来自专栏JetpropelledSnake

Python面试题之Python中type和object的关系

下面是jeff kit的回答: 给别人讲解过很多次,但写成文字是第一次。试一试吧,自己主要也是看了这篇文章(Python Types and Objects...

7310
来自专栏ml

java SE学习之线程同步(详细介绍)

       java程序中可以允许存在多个线程,但在处理多线程问题时,必须注意这样一个问题:               当两个或多个线程同时访问同一个变...

28050
来自专栏琯琯博客

设计模式详解

需要说明的一点是,文中的 UML 类图和规范的 UML 类图不大相同,其中组合关系使用以下箭头表示:

12130
来自专栏Fundebug

JavaScript正则表达式进阶指南

13180
来自专栏工科狗和生物喵

【我的漫漫跨考路】数据结构之队列的线性实现

正文之前 最近在家流年不利,先是昨天家里路由器还是ADSL调制解调器坏了。反正是没网了!然后,今天上午还停电了,昨晚因为熬夜写码,所以忘了给手机充电了。早上起来...

28980

扫码关注云+社区

领取腾讯云代金券