lodash 是如何作类型检测的

欢迎Star:github.com/FatGe/FatGe…html

js 基本数据类型

JS 的基本数据类型有 NumberStringBooleanSymbolNullUndefined,六种数据类型。一种引用类型 objectjava

基本数据类型

Number

数值,根据 ECMAScript 标准,JavaScript 中只有一种数字类型:基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(263 -1) 到 263 -1)。它并无为整数给出一种特定的类型node

除了可以表示浮点数外,还有一些带符号的值:+Infinity-InfinityNaN (非数值,Not-a-Number)。git

对应 lodash 中的检测函数有github

  • isNumber 检查 value 是不是原始 Number 数值型 或者 对象;
  • isInteger 检查 value 是否为一个整数;
  • isNaN 检测 value 是否为 NaN
  • isFinite 检测 value 是不是原始有限数值。
isNumber
function isNumber(value) {
  return typeof value == 'number' ||
    (isObjectLike(value) && getTag(value) == '[object Number]')
}
复制代码

typeof 操做符能够返回一个字符串,表示未经计算的操做数的类型。对于 Number、String、Boolean、Undefined、String 能够很明确的获得它的类型。web

那么 lodash 为何还要添加 (isObjectLike(value) && getTag(value) == '[object Number]')数组

缘由在于,JS 中也容许咱们以以下形式建立一个数值数据结构

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()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。ui

isInteger
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,而 lodashfalse。这是由于若是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"
复制代码

因此检测字符串,除了基本字符串之外还要注意字符串对象。

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 中使用得最多的一种类型,该类型只有两个字面值:truefalse。一样也须要区分基本的 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 开始再也不被支持,如今能够利用以下代码来模拟,虽然没什么用。

const sym = Symbol("foo");
typeof sym;     // "symbol"
const symObj = Object(sym);
typeof symObj;  // "object"
复制代码

Undefined

Undefined 类型只有一个值,即特殊的 undefined。在使用 letvar 声明变量但未对其加以初始化时,这个变量的值就是 undefined

function isUndefined(value) {
    return value === undefined;
}
复制代码

Null

Null 类型是只有一个值的数据类型,这个特殊的值是 null 。与 undefined 不一样的是,它是一个字面量,而 undefined 是全局对象的一个属性。

从逻辑角度来看,null 值表示一个空对象指针,null 是表示缺乏的标识,指示变量未指向任何对象。而这也正是使用typeof 操做符检测null 值时会返回"object"的缘由。

对其的判断也很是的简单,只须要

function isNull(value) {
  return value === null
}
复制代码

固然你也可使用

console.log(Object.prototype.toString.call(null))
// [object Null]
复制代码

以上是基本数据类型的判断,总结一下,主要是利用 typeOf 以及 Object.prototype.toString ,还有一些特殊值的特性。下面开始分析引用类型 Object

引用类型

引用类型的值(对象)是引用类型的一个实例。在ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一块儿。具体的有 ObjectArrayDateErrorRegExpFunction,还有ES2015 引入 SetMapWeakSetWeakMap

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.isArrayLodash 也是使用这个 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 类基础上构建的。

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。它相似于数组,可是成员的值都是惟一的,没有重复的值。

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 有两个区别。

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的实例对象会被抛出。

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

  • 具有 messagename 属性,且值为 string
  • 是普通对象。 也就是说该对象由 Object 构造函数建立,或者 [[Prototype]]null

那么如何检测普通对象呢?

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]] 作判断。

相关文章
相关标签/搜索