使用typeof和instanceof进行类型判断有局限性
typeof在进行类型判断时存在局限性,比如:
typeof null
// 'object'
typeof {}
// 'object'
typeof短点可以通过instanceof弥补:
({}) instanceof Object
// true
null instanceof Object
// false
但也存在一些问题:
[] instanceof Object
// true
[] instanceof Array
// true
toString.call()
toString.call()是一种比较通用的类型判断方法,它代表的是Object.prototype.toSting.call(),不同于Number.prototype.toString.call()或Array.prototype.toString.call()等,效果如下:
toString.call(null)
//"[object Null]"
toString.call(undefined)
//"[object Undefined]"
toString.call(Symbol(null))
// "[object Symbol]"
toString.call({})
//"[object Object]"
lodash类型判断核心文件-getTag.js
toString.call()也是lodash常用的类型判断方式,被封装在getTag.js文件下,源码如下:
const toString = Object.prototype.toString
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function getTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return toString.call(value)
}
export default getTag
源码
lodash不仅对js 常见的类型进行判断,还对Object进一步划分,所以需要将toString.call()代入不同的场景,以下为源码(内部中文注释为个人理解,英文注释属于源代码,源码中参数、返回值、示例也被我拷贝过来了,整篇文章会因此显乱,可直接看function部分):
Symbol
有一位大神在ES5上模拟Symbol:segmentfault.com/a/119000001… ,个人能力有限,想在大神基础上尝试Object.prototype.toString.call(SymbolPolyfill(1)) == "[Object SymbolPolyfill]"未果,有兴趣的同学可以尝试
import getTag from './.internal/getTag.js'
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* isSymbol(Symbol.iterator)
* // => true
*
* isSymbol('abc')
* // => false
*/
function isSymbol(value) {
const type = typeof value
// symbol是ES6新特性
return type == 'symbol' || (type === 'object' && value != null && getTag(value) == '[object Symbol]')
}
export default isSymbol
Set
import getTag from './.internal/getTag.js'
import nodeTypes from './.internal/nodeTypes.js'
import isObjectLike from './isObjectLike.js'
/* Node.js helper references. */
const nodeIsSet = nodeTypes && nodeTypes.isSet
/**
* Checks if `value` is classified as a `Set` object.
*
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a set, else `false`.
* @example
*
* isSet(new Set)
* // => true
*
* isSet(new WeakSet)
* // => false
*/
// nodeIsSet是对node.js版本的一个区分,版本分割线为V10,暂不深入
const isSet = nodeIsSet
? (value) => nodeIsSet(value)
// isObjectLike判断是否为非null的Object,下面有它的源码
: (value) => isObjectLike(value) && getTag(value) == '[object Set]'
export default isSet
Number
import getTag from './.internal/getTag.js'
import isObjectLike from './isObjectLike.js'
/**
* Checks if `value` is classified as a `Number` primitive or object.
*
* **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
* classified as numbers, use the `Number.isFinite` method.
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a number, else `false`.
* @see isInteger, toInteger, toNumber
* @example
*
* isNumber(3)
* // => true
*
* isNumber(Number.MIN_VALUE)
* // => true
*
* isNumber(Infinity)
* // => true
*
* isNumber('3')
* // => false
*/
function isNumber(value) {
return typeof value === 'number' ||
(isObjectLike(value) && getTag(value) == '[object Number]')
}
export default isNumber
Object
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* isObject({})
* // => true
*
* isObject([1, 2, 3])
* // => true
*
* isObject(Function)
* // => true
*
* isObject(null)
* // => false
*/
function isObject(value) {
const type = typeof value
// object定义很宽泛
return value != null && (type === 'object' || type === 'function')
}
export default isObject
String
import getTag from './.internal/getTag.js'
/**
* Checks if `value` is classified as a `String` primitive or object.
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a string, else `false`.
* @example
*
* isString('abc')
* // => true
*
* isString(1)
* // => false
*/
function isString(value) {
const type = typeof value
// !Array.isArray(value)没法代入它的场景。。。。。头大
return type === 'string' || (type === 'object' && value != null && !Array.isArray(value) && getTag(value) == '[object String]')
}
export default isString
RegExp
import getTag from './.internal/getTag.js'
import isObjectLike from './isObjectLike.js'
import nodeTypes from './.internal/nodeTypes.js'
/* Node.js helper references. */
const nodeIsRegExp = nodeTypes && nodeTypes.isRegExp
/**
* Checks if `value` is classified as a `RegExp` object.
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
* @example
*
* isRegExp(/abc/)
* // => true
*
* isRegExp('/abc/')
* // => false
*/
const isRegExp = nodeIsRegExp
? (value) => nodeIsRegExp(value)
: (value) => isObjectLike(value) && getTag(value) == '[object RegExp]'
export default isRegExp
Boolean
import getTag from './.internal/getTag.js'
import isObjectLike from './isObjectLike.js'
/**
* Checks if `value` is classified as a boolean primitive or object.
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
* @example
*
* isBoolean(false)
* // => true
*
* isBoolean(null)
* // => false
*/
function isBoolean(value) {
return value === true || value === false ||
(isObjectLike(value) && getTag(value) == '[object Boolean]')
}
export default isBoolean
Null
/**
* Checks if `value` is `null`.
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `null`, else `false`.
* @example
*
* isNull(null)
* // => true
*
* isNull(void 0)
* // => false
*/
function isNull(value) {
return value === null
}
export default isNull
undefined
/**
* Checks if `value` is `undefined`.
*
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
* @example
*
* isUndefined(void 0)
* // => true
*
* isUndefined(null)
* // => false
*/
function isUndefined(value) {
// 和null一样,简单明了
return value === undefined
}
export default isUndefined
Map
import getTag from './.internal/getTag.js'
import isObjectLike from './isObjectLike.js'
import nodeTypes from './.internal/nodeTypes.js'
/* Node.js helper references. */
const nodeIsMap = nodeTypes && nodeTypes.isMap
/**
* Checks if `value` is classified as a `Map` object.
*
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a map, else `false`.
* @example
*
* isMap(new Map)
* // => true
*
* isMap(new WeakMap)
* // => false
*/
const isMap = nodeIsMap
? (value) => nodeIsMap(value)
: (value) => isObjectLike(value) && getTag(value) == '[object Map]'
export default isMap
Function
/**
* 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(class Any{})
* // => true
*
* isFunction(() => {})
* // => true
*
* isFunction(async () => {})
* // => true
*
* isFunction(function * Any() {})
* // => true
*
* isFunction(Math.round)
* // => true
*
* isFunction(/abc/)
* // => false
*/
function isFunction(value) {
return typeof value === 'function'
}
export default isFunction
isObjectLike(上面提到的那个,被很多判断依赖)
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* isObjectLike({})
* // => true
*
* isObjectLike([1, 2, 3])
* // => true
*
* isObjectLike(Function)
* // => false
*
* isObjectLike(null)
* // => false
*/
function isObjectLike(value) {
return typeof value === 'object' && value !== null
}
export default isObjectLike
伪数组
除了上述这些常见的,lodash中还有isLength、isWeakSet、isWeakMap等一些类型判断,原理大致相同感兴趣的同学可以进一步了解github.com/lodash/loda…。对于数组的判断,在lodash(v5.0.0)中的isArrayLike.js:
import isLength from './isLength.js'
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* isArrayLike([1, 2, 3])
* // => true
*
* isArrayLike(document.body.children)
* // => true
*
* isArrayLike('abc')
* // => true
*
* isArrayLike(Function)
* // => false
*/
function isArrayLike(value) {
return value != null && typeof value !== 'function' && isLength(value.length)
}
export default isArrayLike
它并不能准确判断一个元素的类型是否为数组,所以命名为:像一个数组.js,即此方法没办法判断数组与伪数组(判断依据为length属性),这篇文章(www.jianshu.com/p/6e360a699…)有说明。
虽然typeof存在局限性,但lodash依旧将其做为类型判断的第一步,然后再以getTag、isObjectLike这类封装方法辅助判断以减少环境、标准带来的影响。 下班!