Lodash 是如何作類型檢測的

原文: https://fatge.github.io/2019/...
歡迎Star: https://github.com/FatGe/FatG...

js 基本數據類型

JS 的基本數據類型有 NumberStringBooleanSymbolNullUndefined,六種數據類型。一種引用類型 objecthtml

基本數據類型

Number

數值,根據 ECMAScript 標準,JavaScript 中只有一種數字類型:基於 IEEE 754 標準的雙精度 64 位二進制格式的值(-(263 -1) 到 263 -1)。它並無爲整數給出一種特定的類型java

除了可以表示浮點數外,還有一些帶符號的值:+Infinity-InfinityNaN (非數值,Not-a-Number)。node

對應 lodash 中的檢測函數有git

  • 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 能夠很明確的獲得它的類型。github

那麼 lodash 爲何還要添加 (isObjectLike(value) && getTag(value) == '[object Number]')web

緣由在於,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()方法,當該對象被表示爲一個文本值時,或者一個對象以預期的字符串方式引用時自動調用。
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 是不是 NaNspa

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 構造函數建立的數組時才能正常工做。

具體請見,http://web.mit.edu/jwalden/ww...

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

相關文章
相關標籤/搜索