1.4.2 typeof运算符

typeof运算符用于返回操作数的数据类型,有以下两种使用形式。


typeof operand
typeof (operand)

其中operand表示需要返回数据类型的操作数,可以是引用类型,也可以是基本数据类型。

括号有的时候是必须的,如果不加上括号将会因为优先级的问题得不到我们想要的结果。

typeof运算符在处理不同数据类型时会得到不同的结果,图1-2总结出了可能的返回值。

图1-2

针对图1-2中不同的数据类型,下面总结了一些使用场景。

1. 处理Undefined类型的值

虽然Undefined类型的值只有一个undefined,但是typeof运算符在处理以下3种值时都会返回“undefined”。

· undefined本身。

· 未声明的变量。

· 已声明未初始化的变量。


var declaredButUndefinedVariable;
typeof undefined === 'undefined';    // true
typeof declaredButUndefinedVariable === 'undefined';  // true,已声明未初始化的变量
typeof undeclaredVariable === 'undefined';  // true,未声明的变量

2. 处理Boolean类型的值

Boolean类型的值只有两个,分别是true和false。typeof运算符在处理这两个值以及它们的包装类型时都会返回“boolean”,但是不推荐使用包装类型的写法。


typeof true === 'boolean';          // true
typeof false === 'boolean';         // true
typeof Boolean(true) === 'boolean'; // true,不推荐这么写

3. 处理Number类型的值

对于Number类型的数据,可以概括为以下这些值,typeof运算符在处理时会返回“number”。

· 数字,如1、123、145。

· Number类型的静态变量,如Number.MAX_VALUE、Number.EPSILON等。

· Math对象的静态变量值,如Math.PI、Math.LN2(以e为底,2的对数)。

· NaN,虽然NaN是Not a Number的缩写,但它是Number类型的值。

· Infinity和-Infinity,表示的是无穷大和无穷小的数。

· 数值类型的包装类型,如Number(1)、Number(123),虽然它们也会返回“number”,但是并不推荐这么写。

通过上述的总结,我们可以快速完成以下这些测试。


typeof 37 === 'number';        // true
typeof 3.14 === 'number';      // true
typeof Math.LN2 === 'number';  // true
typeof Infinity === 'number';   // true
typeof NaN === 'number';       // true
typeof Number(1) === 'number'; // true,不推荐这么写

4. 处理String类型的值

对于String类型的数据,可以概括为以下这些值,typeof运算符在处理时会返回“string”。

· 任何类型的字符串,包括空字符串和非空字符串。

· 返回值为字符串类型的表达式。

· 字符串类型的包装类型,例如String('hello')、String('hello' + 'world'),虽然它们也会返回“String”,但是并不推荐这么写。

通过上述的总结,我们可以快速完成以下这些测试。


typeof "" === 'string';            // true
typeof "bla" === 'string';         // true
typeof (typeof 1) === 'string';    // true,因为typeof会返回一个字符串
typeof String("abc") === 'string'; // true,不推荐这么写

5. 处理Symbol类型的值

Symbol类型是在ES6中新增的原生数据类型,表示一个独一无二的值,typeof运算符处理后得到的返回值为“symbol”。


typeof Symbol() === 'symbol';      // true
typeof Symbol('foo') === 'symbol'; // true

6. 处理Function类型的值

对于Function类型的数据,可以概括为以下这些值,typeof运算符在处理时会返回“function”。

· 函数的定义,包括函数声明或者函数表达式两种形式。

· 使用class关键字定义的类,class是在ES6中新增的关键字,它不是一个全新的概念,原理依旧是原型继承,本质上仍然是一个Function。

· 某些内置对象的特定函数,例如Math.sin()函数、Number.isNaN()函数等。

· Function类型对象的实例,一般通过new关键字得到。

通过上述的总结,我们可以快速完成以下这些测试。


var foo = function () {};
function foo2() {}

typeof foo === 'function';       // true,函数表达式
typeof foo2 === 'function';      // true,函数声明
typeof class C{} === 'function'; // true
typeof Math.sin === 'function';  // true
typeof new Function() === 'function';  // true,new操作符得到Function类型的实例

7. 处理Object类型的值

对于Object类型的数据,可以概括为以下这些值,typeof运算符在处理时会返回“object”。

· 对象字面量形式,例如{name: 'kingx'}。

· 数组,例如[1, 2, 3]和Array(1, 2, 3)。

· 所有构造函数通过new操作符实例化后得到的对象,例如new Date()、new function(){},但是new Function(){}除外。

· 通过new操作符得到的基本数据类型的包装类型对象,如new Boolean(true)、newNumber(1),但不推荐这么写。

细心的读者可能发现了,与基本数据类型的包装类型相关的部分,我们都有写“不推荐这么写”,这是为什么呢?

因为涉及包装类型时,使用了new操作符与没有使用new操作符得到的值在通过typeof运算符处理后得到的结果是不一样的,很容易让人混淆。

通过上述的总结,我们可以快速完成以下这些测试。


typeof {a:1} === 'object';      // true,对象字面量
typeof [1, 2, 4] === 'object';  // true,数组
typeof new Date() === 'object'; // true,Date对象的实例
// 下面的代码容易令人迷惑,不要使用!
typeof new Boolean(true) === 'object';  // true
typeof new Number(1) === 'object';      // true
typeof new String("abc") === 'object';  // true

typeof运算符的使用在绝大部分情况下都是安全的,但是在ES6以后情况就不一样了。这里总结了使用typeof运算符时需要考虑的问题。

1. typeof运算符区分对待Object类型和Function类型

在Nicholas C.Zakas所著的《JavaScript高级程序设计》一书中讲到,从技术角度讲,函数在ECMAScript中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过typeof运算符来区分函数和其他对象是有必要的。

另外,在实际使用过程中,有必要区分Object类型和Function类型,而typeof运算符就能帮我们实现。

2. typeof运算符对null的处理

使用typeof运算符对null进行处理,返回的是“object”,这是一个让大家都感到惊讶的结果。因为null是一个原生类型的数据,为什么typeof运算符会返回“object”呢?

这是一个在JavaScript设计之初就存在的问题,这里简单介绍下。

在JavaScript中,每种数据类型都会使用3bit表示。

· 000表示Object类型的数据。

· 001表示Int类型的数据。

· 010表示Double类型的数据。

· 100表示String类型的数据。

· 110表示Boolean类型的数据。

由于null代表的是空指针,大多数平台中值为0x00,因此null的类型标签就成了0,所以使用typeof运算符时会判断为object类型,返回“object”。

虽然在后面的提案中有提出修复方案,但是因为影响面太大,所以并没有被采纳,从而导致这个问题一直存在。

3. typeof运算符相关语法的括号

在前文中有讲到,括号有时是必须存在的,如果不加上括号则会因为优先级的问题得不到我们想要的结果。

我们可以通过以下代码看看加不加括号在结果上的差异。


var number = 123;
typeof (number + ' hello');  // "string"
typeof number + ' hello';    // "number hello"

因为typeof运算符的优先级会高于字符串拼接运算符(+),但是优先级低于小括号(),所以在未使用括号时,会优先处理typeof number,返回的是"number",然后与"hello"字符串进行拼接,得到结果"number hello"。

下面是更能体现括号重要性的例子。


typeof 1 / 0;     // "NaN"
typeof (1 / 0);   // "number"

第一行代码中,因为没有小括号,实际会先运行typeof 1,返回的是"number",然后除以0,一个字符串除以0,得到的是"NaN"。

第二行代码中,因为使用了小括号,实际会先运行1/0,得到的是Infinity,而Infinity实际上为Number类型的值,通过typeof运算符处理后,得到的是"number"。

因此在处理某些表达式时,需要将这些表达式用括号括起来以保证先运算表达式,再使用typeof运算符进行运算。