4.1.4 检测类型
要检测一个变量是不是基本数据类型?第 3 章介绍的 typeof 操作符是最佳的工具。说得更具体一
点, typeof 操作符是确定一个变量是字符串、数值、布尔值,还是 undefined 的最佳工具。如果变
量的值是一个对象或 null,则 typeof 操作符会像下面例子中所示的那样返回"object":
var s = "Nicholas";
var b = true;
var i = 22;
var u;
var n = null;
var o = new Object();
alert(typeof s); //string
alert(typeof i); //number
alert(typeof b); //boolean
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof o); //object
DeterminingTypeExample01.htm
虽然在检测基本数据类型时 typeof 是非常得力的助手,但在检测引用类型的值时,这个操作符的
用处不大。 通常, 我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为此, ECMAScript
提供了 instanceof 操作符,其语法如下所示:
result = variable instanceof constructor
如果变量是给定引用类型(根据它的原型链来识别;第 6 章将介绍原型链)的实例,那么
instanceof 操作符就会返回 true。请看下面的例子:
alert(person instanceof Object); // 变量 person 是 Object 吗?
alert(colors instanceof Array); // 变量 colors 是 Array 吗?
alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?
根据规定,所有引用类型的值都是 Object 的实例。因此,在检测一个引用类型值和 Object 构造
函数时, instanceof 操作符始终会返回 true。当然,如果使用 instanceof 操作符检测基本类型的
值,则该操作符始终会返回 false,因为基本类型不是对象。
使用 typeof 操作符检测函数时,该操作符会返回"function"。在 Safari 5 及
之前版本和 Chrome 7 及之前版本中使用 typeof 检测正则表达式时,由于规范的原
因,这个操作符也返回"function"。 ECMA-262 规定任何在内部实现[[Call]]方法
的对象都应该在应用 typeof 操作符时返回"function"。由于上述浏览器中的正则
表达式也实现了这个方法,因此对正则表达式应用 typeof 会返回"function"。在
IE 和 Firefox 中,对正则表达式应用 typeof 会返回"object"。
22.1.1 安全的类型检测
JavaScript 内置的类型检测机制并非完全可靠。事实上,发生错误否定及错误肯定的情况也不在少
数。比如说 typeof 操作符吧,由于它有一些无法预知的行为,经常会导致检测数据类型时得到不靠谱
的结果。 Safari(直至第 4 版)在对正则表达式应用 typeof 操作符时会返回"function",因此很难确
定某个值到底是不是函数。
再比如, instanceof 操作符在存在多个全局作用域(像一个页面包含多个 frame)的情况下,也
是问题多多。一个经典的例子(第 5 章也提到过)就是像下面这样将对象标识为数组。
var isArray = value instanceof Array;
以上代码要返回 true, value 必须是一个数组,而且还必须与 Array 构造函数在同个全局作用域
中。(别忘了, Array 是 window 的属性。)如果 value 是在另个 frame 中定义的数组,那么以上代码
就会返回 false。
在检测某个对象到底是原生对象还是开发人员自定义的对象的时候,也会有问题。出现这个问题的
原因是浏览器开始原生支持 JSON 对象了。因为很多人一直在使用 Douglas Crockford 的 JSON 库,而该
库定义了一个全局 JSON 对象。于是开发人员很难确定页面中的 JSON 对象到底是不是原生的。
解决上述问题的办法都一样。大家知道,在任何值上调用 Object 原生的 toString()方法,都会
返回一个[object NativeConstructorName]格式的字符串。每个类在内部都有一个[[Class]]属
性,这个属性中就指定了上述字符串中的构造函数名。举个例子吧。
alert(Object.prototype.toString.call(value)); //"[object Array]"
由于原生数组的构造函数名与全局作用域无关,因此使用 toString()就能保证返回一致的值。利
用这一点,可以创建如下函数:
function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]";
}
同样,也可以基于这一思路来测试某个值是不是原生函数或正则表达式:
function isFunction(value){
return Object.prototype.toString.call(value) == "[object Function]";
}
function isRegExp(value){
return Object.prototype.toString.call(value) == "[object RegExp]";
}
不过要注意,对于在 IE 中以 COM 对象形式实现的任何函数, isFunction()都将返回 false(因
为它们并非原生的 JavaScript 函数,请参考第 10 章中更详细的介绍)。
这一技巧也广泛应用于检测原生 JSON 对象。 Object 的 toString()方法不能检测非原生构造函
数的构造函数名。因此,开发人员定义的任何构造函数都将返回[object Object]。有些 JavaScript 库会包
含与下面类似的代码。
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) ==
"[object JSON]";
在 Web 开发中能够区分原生与非原生 JavaScript 对象非常重要。只有这样才能确切知道某个对象到
底有哪些功能。这个技巧可以对任何对象给出正确的结论。
请注意, Object.prototpye.toString()本身也可能会被修改。本节讨论的
技巧假设 Object.prototpye.toString()是未被修改过的原生版本。
jQuery和lodash都是采用安全的类型检测的,下面是lodash代码:
lodash/.internal/baseGetTag.js
const objectProto = Object.prototype
const hasOwnProperty = objectProto.hasOwnProperty
const toString = objectProto.toString
const symToStringTag = typeof Symbol != 'undefined' ? Symbol.toStringTag : undefined
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
if (!(symToStringTag && symToStringTag in Object(value))) {
return toString.call(value)
}
const isOwn = hasOwnProperty.call(value, symToStringTag)
const tag = value[symToStringTag]
let unmasked = false
try {
value[symToStringTag] = undefined
unmasked = true
} catch (e) {}
const result = toString.call(value)
if (unmasked) {
if (isOwn) {
value[symToStringTag] = tag
} else {
delete value[symToStringTag]
}
}
return result
}
export default baseGetTag
lodash/isFunction.js
import baseGetTag from './.internal/baseGetTag.js'
import isObject from './isObject.js'
/**
* Checks if `value` is classified as a `Function` object.
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* isFunction(_)
* // => true
*
* isFunction(/abc/)
* // => false
*/
function isFunction(value) {
if (!isObject(value)) {
return false
}
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 9 which returns 'object' for typed arrays and other constructors.
const tag = baseGetTag(value)
return tag == '[object Function]' || tag == '[object AsyncFunction]' ||
tag == '[object GeneratorFunction]' || tag == '[object Proxy]'
}
export default isFunction
网友评论