Typeof 的设计思路

841

typeof 操作符返回一个字符串,表示检测变量的数据类型。

用法:

typeof operand
typeof(operand)

举几个列子:

typeof 9007199254740991n // "bigint"
typeof null // "object"
typeof [] // "object"
typeof Symbol() // "symbol"
typeof new Function() // "function"

在这里有几个特别的地方,比如 typeof nulltypeof array 都会返回 object

返回值

JS 中有八种数据类型(numberstringbooleannullundefinedsymbolbigintobject),typeof 的返回值也有八种。

"number""boolean""string""object""undefined""symbol""function""bigint"
  1. 由于历史原因typeof null 会返回 object ,这是一个预计永远不会在 JS 中被修复的 bug。这就意味着不能使用 typeof 来检查值为 null 的类型。但是比较方便的是可以使用严格相等运算符(===)来检查。

    maybeNull === null
    
  2. 由于 typeof 检查函数类型都会返回 function,所以可以很简单的判断函数类型。

  3. typeof array 返回 object,所以就出现了一个问题,如何能准确的确定变量是否为数组:一种方法是使用 instanceof,一种使用 Array.isArray(),一种是使用 Object.prototype.toString.call(arr),一种是 arr.constructor === Array

  4. 使用对象包装器 Number()String() 等创建的变量会返回 object

    typeof new Number(12) // "object"
    typeof new String('str') // "object"
    

为什么 typeof null 是 object

这是一个无法修复的 bug,因为它会影响现有的代码。这个 bug 是第一个 JS 版本留下的问题,在早期的版本中,值被存在 32bit 单元中,由 1-3bits 类型标签和实际数据组成。类型标签存储在单元的较低位,有五种:

  1. 000 object,数据是对对象的引用
  2. 1 int,数据是31位有符号整数
  3. 010 double,数据是对双浮点数的引用
  4. 100 string,数据是对字符串的引用
  5. 110 boolean,数据是布尔值

也就是说,最低位是1的话,这个类型标签只有1位,如果最低位是0的话,那么类型标签有3位,为4种类型提供两个附加位。

有两个特殊的值:

  1. undefined 一个超出整数范围的数字
  2. null 是机器代码 NULL 指针,或者说是对象类型标记加上一个为零的引用。

所以当 typeof null 时检查了它的类型标签,而类型标签说 "object"。以下是该引擎的 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)
                 : 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;
    }

上面的代码执行的步骤是:

(1) 引擎首先检查值v是否未定义(VOID)。通过用等号比较值来执行此检查

#define JSVAL_IS_VOID(v)  ((v) == JSVAL_VOID)

(2) 检查该值是否具有对象标签,如果满足(3),则将其标记为一个函数,否则为对象。

(4) 然后检查数值,字符串,布尔类型。

总结

  1. 对于基本类型,除了 null 之外,均可以返回正确的结果
  2. 对于引用类型,除了 function 之外,一律返回 object 类型
  3. 对于 null,返回 object 类型
  4. 对于 function,返回 function 类型