也许你曾被 js 中的类型判断搞的晕头转向的,下面我们来从头梳理一下 js 中的类型判断问题。
基本类型
JavaScript 中的基本类型有 6 种:
- null
- undefined
- boolean
- number
- string
- symbol
还有 object。我们可以通过 typeof 判断它们的类型,如:
typeof null === 'object' // true
typeof undefined === 'undefined' // true
typeof 11 === 'number' // true
...
其中 typeof null === 'object'
,所以并不能单纯的通过 typeof 判断 null ,这是 js 实现的问题。如果想判断是否是 null 的话,可以这样做:
var a = null
// 复合判断
(!a && typeof a === 'object') // true
// 判断相等性
a === null // true
还有一种情况就是,对于 function,它是 object 的子类型,但是 typeof function 会返回 function。
typeof function(){} === 'function' // true
由于 function 是一个 object,所以 function 可以拥有普通对象属性。它的内部有一个 [[Call]] 属性,使得它可以被调用。
但是判断 Array,就不能这样了,虽然 Array 也是 Object 的子类型。
typeof [] // object
对于一个变量,如果它没有值的时候, typeof 会返回 undefined,否则则会返回它的值的类型。对于 undefined 我们也可以直接判断相等性。
var a
a === undefined // true
但是 undefined 是可以被赋值改写的。为了防范这种情况,我们通过 void 运算符,进行操作,void __ 总会返回 undefined
var a
a === void 0 // true
对于一个未声明(undeclared)的变量,直接访问会报错,但是 typeof 依然会返回 undefined
nnnnnnnn // Uncaught ReferenceError: nnnnnnnn is not defined
typeof nnnnnnnn // undefined
这个特性可以帮助我们做一些防御性编程,因为直接 if(xxxxxx) 会抛错,但是 if(typeof xxxxxx !== 'undefined') 不会。
宽松相等和严格相等
你应该会经常听到很多规范要求你不要用 == 而用 ===,那么这两个相等之间有什么区别呢。简单的说来就是 == 会发生隐式类型转化,而 === 不会。
下面是 == 隐式类型转换的规则:

www.ecma-international.org/ecma-262/5.…
== 有时候是会带来一些便利的,比如说 null 和 undefined 比较是 true。字符串和数字比较字符串会被转换数字再比较。
比较令人费解的地方是布尔值和其他类型比较,布尔值先被转换为数字 0 或者 1,再进行比较。对象和非对象之间比较,对象会被先转换为原始值。
或许你以为 == 就应该照你想的那样工作,没有符合你的预期的话,你会觉得是坑。但其实不是这样的,它有着一套完善的规则。我的意见是,如果你十分清楚了解隐式转换的规则的话,== 是完全可以用的,你知道自己在干什么。否则使用 === 。
特殊的值和数字判断
NaN 表示一个无效的数字,它依然是数字类型:
typeof NaN == 'number' // true
如果你简单的通过 == 或 NaN 判断一个数字是否是 NaN:
NaN === NaN // false
NaN !== NaN // true
这样是不行的!window 有一个全局的函数 isNaN 可以用来判断是否是 NaN,它的检查方式是判断一个值是否不是一个 NaN,也不是数字。也就是说:
isNaN('aaa') // true
isNaN(NaN) // true
isNaN(100) // false
isNaN('100') // false
ES6 新增 Number.isNaN 会判断一个数字类型是否是 NaN:
Number.isNaN('aaa') // false
Number.isNaN(NaN) // true
Number.isNaN(100) // false
Number.isNaN('100') // false
除此之外还有一个 Infinity -- 无穷大。 1 / 0 就是 ∞,Infinity。同理 isFinite 可以判断是否不是 Infinity,它同样有一个全局函数和一个 Number 类型的成员函数:
isFinite('aaa') // false
isFinite(NaN) // false
isFinite(Infinity) // false
isFinite('100') // true
isFinite(100) // true
Number.isFinite('aaa') // false
Number.isFinite(NaN) // false
Number.isFinite(100) // false
Number.isFinite('100') // false
Number.isFinite(100) // true
对于零值,我们来看看一下一个例子:
0 * -1 // -0
+0 -0 的存在是在数学上合理的。但在程序中判断它们并不是那么容易的,。好在 ES6 提供了 Object.is 函数,我们可以用它来处理这些特殊值:
Object.is(0, -0) // false
Object.is(-0, -0) // false
Object.is(NaN, NaN) // true
Object.is(Infinity, Infinity) // true
Object.is(Infinity, -Infinity) // false
详细见 developer.mozilla.org/zh-CN/docs/…
了解知道了这么多特殊值,那么我们该怎么判断
内置类型的判断
如 RegExp Function Date Array 它们是 js 引擎自带的函数,那么该怎么判断它们的类型呢。
它们内部实现,有一个 [[class]] 属性,通过 Object.prototype.toString(...),可以间接访问到这个属性,从而实现类型判断:
Object.prototype.toString.call(/./) // [object RegExp]
Object.prototype.toString.call(() => {}) // [object Function]
Object.prototype.toString.call(new Date()) // [object Date]
Object.prototype.toString.call([]) // [object Array]
你也可以使用 instanceof 进行判断,如 [] instanceof Array === true
但是请注意,这样在跨宿主环境时会有问题的(如跨 iframe),因为两个不同环境的 Array 对象并不是同一个。
类和对象
a instanceof Foo 可以用来判断 a 的整条原型链 [[prototype]] 中,是否有指向 Foo.prototype 的引用:
function Foo() {}; Foo.prototype.a = '';
function Bar() {}; Bar.prototype = new Foo(); Bar.prototype.b = '';
new Bar instanceof Foo // true
new Bar instanceof Bar // true
但是如果你是采用 Object.create(...)
的方式创建原型链,那么 instanceof 就不是那么管用了,因为此时并不存在什么构造函数。
a.isPrototypeOf(b)
可以判断 a 是否出现在 b 的原型链中。 a instanceof Foo
可以用 Foo.prototype.isPrototypeOf(a)
代替,这样更清晰的显示了他们的关系。
Promise 中是通过检查返回结果是否具有 then 方法,来判断返回结果是否是一个 thenable 这样做很方便,但同时也很脆弱。我们不得不注意要避免给对象加上 then 属性,不然你在 Promise 中返回这个对象的时候,会被当作一个 Promise,即使它不是。
参考资料