JS 的基本数据类型有 Number
,String
,Boolean
,Symbol
,Null
,Undefined
,六种数据类型。一种引用类型 object
。
基本数据类型
Number
数值,根据 ECMAScript 标准,JavaScript 中只有一种数字类型:基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(263 -1) 到 263 -1)。它并没有为整数给出一种特定的类型。
除了能够表示浮点数外,还有一些带符号的值: Infinity
,-Infinity
和 NaN
(非数值,Not-a-Number)。
对应 lodash 中的检测函数有
isNumber
检查value
是否是原始Number
数值型 或者 对象;isInteger
检查value
是否为一个整数;isNaN
检测value
是否为NaN
;isFinite
检测value
是否是原始有限数值。
isNumber
代码语言:javascript复制function isNumber(value) {
return typeof value == 'number' ||
(isObjectLike(value) && getTag(value) == '[object Number]')
}
typeof
操作符可以返回一个字符串,表示未经计算的操作数的类型。对于 Number、String、Boolean、Undefined、String 可以很明确的得到它的类型。
那么 lodash 为什么还要添加 (isObjectLike(value) && getTag(value) == '[object Number]')
?
原因在于,JS 中也允许我们以如下形式创建一个数值
代码语言:javascript复制const value = new Number(1)
console.log(value) // log 1
console.log(typeof value) // log "object"
这时,单单只是使用 typeof
操作符就没法判断 value
的类型是否为数值。所以要结合以下两个函数来判断,value
是否为 object
然后再通过过 toString()
来获取每个对象的类型。
function getTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return Object.prototype.toString.call(value)
}
function isObjectLike(value) {
return typeof value == 'object' && value !== null
}
Object.prototype.toString.call
每个对象都有一个toString()
方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。
isInteger
代码语言:javascript复制function isInteger(value) {
return
typeof value == 'number'
&& value == toInteger(value);
}
检查 value
是否为一个整数,判断是否 value
的类型是否为数值,并且是否与 Int 型相同。其取整过程如下
function toInteger(value) {
var result = toFinite(value),
remainder = result % 1;
return result === result ?
(remainder ? result - remainder : result) : 0;
}
isNaN
检查 value
是否是 NaN
。
function isNaN(value) {
return isNumber(value) && value != value;
}
与 ES 2015 的 isNaN
不同的是,对于 undefined
,{}
,原生的结果是 true
,而 lodash 为 false
。这是因为如果isNaN
函数的参数不是Number
类型, isNaN
函数会首先尝试将这个参数转换为数值,然后才会对转换后的结果是否是NaN
进行判断。
// js native isNaN
var isNaN = function(value) {
var n = Number(value);
return n !== n;
};
但是无论是 ES 2015 还是 lodash,它们本质上都是利用 x != x
来判断 NaN
。
isFinite
检查 value
是否是原始有限数值。
function isFinite(value) {
return typeof value == 'number'
&& nativeIsFinite(value);
}
利用原生的 isFinite
结合 typeof
判断数字是否为有限值。
String
String 类型用于表示由零或多个16 位Unicode 字符组成的字符序列,即字符串。用于保存可以以文本形式表示的数据非常有用。
值得注意的是,不单单要注意基本字符串,还需要注意字符串对象,字符串字面量 (通过单引号或双引号定义) 和 直接调用 String 方法(没有通过 new 生成字符串对象实例)的字符串都是基本字符串。
JavaScript会自动将基本字符串转换为字符串对象,只有将基本字符串转化为字符串对象之后才可以使用字符串对象的方法。
与之前的 number 类似,利用构造函数 String
创建的字符串是一个 object
const s_prim = "foo";
const s_obj = new String(s_prim);
console.log(typeof s_prim); // Logs "string"
console.log(typeof s_obj); // Logs "object"
所以检测字符串,除了基本字符串以外还要注意字符串对象。
代码语言:javascript复制function isString(value) {
const type = typeof value
return
type == 'string' ||
(type == 'object'
&& value != null
&& !Array.isArray(value)
&& getTag(value) == '[object String]')
}
可以利用 typeof
检测基本字符串,对于模板字符串采用了之前介绍的方案 getTag
来获取 value
的类型。
Boolean
Boolean 类型是ECMAScript 中使用得最多的一种类型,该类型只有两个字面值:true
和 false
。同样也需要区分基本的 Boolean 类型以及 Boolean 对象。
function isBoolean(value) {
return
value === true || value === false ||
(isObjectLike(value)
&& getTag(value) == '[object Boolean]')
}
大部分在之前都已经涉及到了,这里出现了 isObjectLike
,那么它是做什么的。
function isObjectLike(value) {
return typeof value == 'object' && value !== null
}
原来只是检测是否是一个非 null
的对象。
Symbol
ES6 引入了一种新的原始数据类型Symbol
,表示独一无二的值。Symbol 值通过Symbol
函数生成。
function isSymbol(value) {
const type = typeof value
return type == 'symbol' ||
(isObjectLike(value) &&
getTag(value) == '[object Symbol]')
}
会发现 (isObjectLike(value) && getTag(value) == '[object Symbol]')
,也对 Symbol 对象进行检测,但是如果直接 new Symbol
会 log 出 TypeError
。
那么 lodash 为什么要对其进行检测,原来是创建一个显式包装器对象从 ECMAScript 6 开始不再被支持,现在可以利用如下代码来模拟,虽然没什么用。
代码语言:javascript复制const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"
Undefined
Undefined 类型只有一个值,即特殊的 undefined
。在使用 let
或 var
声明变量但未对其加以初始化时,这个变量的值就是 undefined
。
function isUndefined(value) {
return value === undefined;
}
Null
Null 类型是只有一个值的数据类型,这个特殊的值是 null
。与 undefined
不同的是,它是一个字面量,而 undefined
是全局对象的一个属性。
从逻辑角度来看,null 值表示一个空对象指针,null
是表示缺少的标识,指示变量未指向任何对象。而这也正是使用typeof 操作符检测null 值时会返回"object"的原因。
对其的判断也非常的简单,只需要
代码语言:javascript复制function isNull(value) {
return value === null
}
当然你也可以使用
代码语言:javascript复制console.log(Object.prototype.toString.call(null))
// [object Null]
以上是基本数据类型的判断,总结一下,主要是利用 typeOf
以及 Object.prototype.toString
,还有一些特殊值的特性。下面开始分析引用类型 Object
引用类型
引用类型的值(对象)是引用类型的一个实例。在ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。具体的有 Object
、Array
、Date
、Error
、RegExp
、Function
,还有ES2015 引入 Set
、Map
、WeakSet
、WeakMap
。
Object
ECMAScript 中的对象其实就是一组数据和功能的集合。它有一个很重要的用途,就是在 JavaScript 中的所有对象都来自 Object
;所有对象从Object.prototype
继承方法和属性,尽管它们可能被覆盖。即在ECMAScript 中,Object 类型是所有它的实例的基础。
所以 Lodash 去判断 value
是否为 Object
时,只使用了 typeOf
操作即可。
function isObject(value) {
const type = typeof value
return value != null &&
(type == 'object' || type == 'function')
}
Function
Function 构造函数 创建一个新的Function对象。 在 JavaScript 中, 每个函数实际上都是一个Function对象。
function isFunction(value) {
if (!isObject(value)) {
return false
}
const tag = getTag(value)
return tag == '[object Function]' ||
tag == '[object AsyncFunction]' ||
tag == '[object GeneratorFunction]' ||
tag == '[object Proxy]'
}
有个问题,typeOf
可以检测 Function对象的类型为 Function
, 那为什么还需要 Object.prototype.toString
呢?
// in Safari 9 which returns 'object' for typed arrays and other constructors.
Array
Array 在 ECMAScript 中代表数组,它的每一项可以保存任何类型的数据。
对它的常规检测就是 Array.isArray
,Lodash 也是使用这个 API,如果需要 Polyfill 方案的话,可以使用
// plan 1
Object.prototype.toString.call(value) === '[object Array]'
// plan 2
value.constructor === Array
之前还有 value instanceof Array
会什么问题么?
在存在不同全局变量的环境,通过语义
instanceof
检测数组的时候,value instanceof Array
只有当value
是由该页面的原始Array
构造函数创建的数组时才能正常工作。 具体请见,web.mit.edu/jwalden/www…
Date
ECMAScript 中的 Date 类型是在早期Java 中的java.util.Date 类基础上构建的。
代码语言:javascript复制const nodeIsDate = nodeTypes && nodeTypes.isDate
const isDate = nodeIsDate
? (value) => nodeIsDate(value)
: (value) => isObjectLike(value) && getTag(value) == '[object Date]'
Lodash 分为两个环境来处理这个问题,如果是 Node 就利用 util.types.isDate(value)
来检测,如果是在游览器,就还是通过 Object.prototype.toString
来判断。
Set
ES2015 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
代码语言:javascript复制const isSet = nodeIsSet
? (value) => nodeIsSet(value)
: (value) => isObjectLike(value) && getTag(value) == '[object Set]'
同样的还有 Map
const isMap = nodeIsMap
? (value) => nodeIsMap(value)
: (value) => isObjectLike(value) && getTag(value) == '[object Map]'
WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
代码语言:javascript复制function isWeakSet(value) {
return isObjectLike(value) && getTag(value) == '[object WeakSet]'
}
也是利用 Object.prototype.toString
,同样还有 WeakMap
function isWeakMap(value) {
return isObjectLike(value) && getTag(value) == '[object WeakMap]'
}
Error
当运行时错误产生时,Error的实例对象会被抛出。
代码语言:javascript复制function isError(value) {
if (!isObjectLike(value)) {
return false
}
const tag = getTag(value)
return tag == '[object Error]' ||
tag == '[object DOMException]' ||
(typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value))
}
有之前一致的 Object.prototype.toString
依然可以用来判断对象是否是一个 Error,除此之外,如果对象满足以下条件,也可以被视为一个 Error
- 具备
message
、name
属性,且值为string
; - 是普通对象。 也就是说该对象由
Object
构造函数创建,或者[[Prototype]]
为null
。
那么如何检测普通对象呢?
代码语言:javascript复制function isPlainObject(value) {
if (!isObjectLike(value) || getTag(value) != '[object Object]') {
return false
}
if (Object.getPrototypeOf(value) === null) {
return true
}
let proto = value
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(value) === proto
}
主要是利用 Object.getPrototypeOf()
方法返回指定对象的原型(内部[[Prototype]]
属性的值),同时和 value
本身的 [[Prototype]]
做判断。