打造屬於本身的underscore系列 ( 二 ) - 數據類型診斷

在上一篇 打造屬於本身的underscore系列 ( 一 )的文章中,咱們介紹了underscore 的一些設計思想和理念,並對框架的結構進行了詳細的介紹,這一節的源碼打造,咱們會深刻javascript的數據類型,並會對underscore對各類數據類型的斷定方法進行分析。javascript

二, 數據類型診斷

2.1. isArray - 判斷數組

判斷一個對象是否爲數組的方法經常使用的有:java

  • ES5 方法: Array.isArray(obj)
  • instanceof: obj instanceof Array
  • Object.prototype.toString.call(obj) === "[object Array]"

前面的兩個方法或多或少存在缺陷,低版本瀏覽器不支持ES5 Array.isArray()的新方法,而instanceof 斷定規則在跨iframe 中也存在問題。好比,一個頁面(父頁面)有一個框架,框架中引用了一個頁面(子頁面),在子頁面中聲明瞭一個array,並將其賦值給父頁面的一個變量,這時判斷該變量時使用 instanceOf便不許確了。所以最正確的方法是使用Object.prototype.toString.call(obj)來判斷數組。node

// 判斷數組
  _.isArray = function (obj) {
    return Array.isArray(obj) || toString.call(obj) === '[object Array]'
  }
複製代碼
2.2. isObject - 判斷對象

若是object是一個對象,返回true。須要注意的是JavaScript數組和函數是對象,字符串和數字不是。算法

typeof 能夠用來判斷數據類型屬於Object,同時,Function 類型的數據一樣屬於對象。而null 雖然是對象,可是須要排除數組

// 判斷對象
  _.isObject = function (obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj
  }
複製代碼
2.3 深刻Object.prototype.toString.call()

咱們知道在js中,一切都是對象,而Object原型對象上都有一個 toString()方法,toString() 方法調用會返回"[object type]", 其中type 是對象的類型,在ES6之前,js內置對象類型 主要有'Arguments', 'Function', 'String', 'Boolean', 'Number', 'Date', 'RegExp', 'Error', ES6以後增長了諸如'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet' 的數據類型。那既然toString 的方法能夠用來判斷對象的具體類型,爲何還須要經過Object.prototype.toString.call(obj) 的方式來調用呢?瀏覽器

原來toString()雖然做爲Object原型上的方法,可是Array ,Function等類型做爲Object的實例,都重寫了toString方法。所以直接調用對象的toString()方法並不會返回數據類型,而是返回重寫後的結果。咱們能夠舉幾個例子bash

var a = function(){console.log(2)}
a.toString() // 'function(){console.log(2)}'

var b = [2,5,6];
b.toString() // "2,5,6"

var f = new Date()
f.toString() // "Thu Jan 10 2019 14:33:08 GMT+0800 (中國標準時間)"

var p = /d/g
p.toString() // "/d/g"

var h = new Error('33')
h.toString() // "Error: 33"

var i = Symbol(3);
i.toString() // "Symbol(3)"
···

複製代碼

所以 Arguments', 'Function', 'String', 'Boolean', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'對象類型的斷定方法,咱們能夠統一用Object.prototype.toString.call(obj) 來實現框架

// 對象類型判斷方法
  _.each(['Arguments', 'Function', 'String', 'Boolean', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function (name) {
    _['is' + name] = function (obj) {
      return toString.call(obj) === '[object ' + name + ']';
    };
  });
複製代碼
2.4 isFinite - 判斷是不是一個有限的數字

javascript原生提供了一個isFinite() 的函數來判斷number(或者可轉成number 的值) 是否爲無窮大。注意,判斷條件包括可轉化爲number 類型的值,也就是針對 true,false的布爾值,以及null的特殊值,能夠經過隱式轉換爲數字,inFinite(true)返回的是true, 所以咱們使用isFinite來判斷一個純的有限數字並不穩當。而且爲了不Symbol類型作類型轉換時報錯,咱們須要先排除Symbol的數據類型。dom

// 判斷數字是否爲有限值
  _.isFinite = function(obj) {
    return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
  }
複製代碼
2.5 _.keys - 對象所擁有的可枚舉屬性的集合,不包括原型鏈

往下介紹以前,先介紹一下一個重要的方法:_.keys(), _.keys()是用來枚舉 對象中可枚舉屬性,並以數組的形式返回。咱們知道,要遍歷對象的屬性能夠經過 for in 來遍歷,ES5中也有新增Object.keys方法,Object.getOwnProperty的方法一樣能獲取對象的屬性,那三者的區別在哪裏呢?函數

  • for in 遍歷對象自身和原型上的可枚舉屬性
  • Object.keys 是ES5新增的方法, 他能夠遍歷對象自身的可枚舉屬性,但不包括原型鏈上的屬性
  • Object.getOwnProperty() 遍歷對象自身可枚舉,不可枚舉屬性,不包括原型鏈上的屬性
  • 在不支持ES5的條件下,只要在for in 基礎上,能夠經過 obj.hasOwnProperty(屬性) 即可以來排除原型鏈上的屬性方法。

認清出這幾點後,keys方法的設計就很簡單了

// 遍歷對象自身可枚舉屬性
  _.keys = function(obj) {
    if(!_.isObject(obj)) return []; // 非對象則返回空數組
    if(Object.keys) return Object.keys(obj); // 支持ES5方法,則使用Object.keys()
    var keys = []
    for(var i in obj) {            // 不支持,經過for in 遍歷並排除原型鏈上的屬性
      if(obj.hasOwnProperty(i)) keys.push(i)
    }
    return keys
  }
複製代碼

在underscore源碼中,咱們看到了這樣的一段兼容性代碼。 對於IE9如下而言,forin 遍歷對象存在着某種程度的缺陷,咱們知道,諸如 valueof,toString這些定義在Ojbect原型上的方法是不可枚舉的,而當咱們重寫這些方法後,咱們訪問的是這些可枚舉的自定義方法。而對於IE9 如下而言。即便重寫了不可枚舉的方法後,依然沒法在可枚舉屬性中遍歷。因此咱們須要作對低版本的進行兼容。

_.keys = function(obj) {
    ···
    if (hasEnumBug) collectNonEnumProps(obj, keys); // 收集不可枚舉屬性
    return keys
}

var hasEnumBug = !{
    toString: null
  }.propertyIsEnumerable('toString'); // 重寫toString 方法,並判斷是不是可枚舉的。
  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
    'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'
  ]; //  枚舉全部Object原型上不可枚舉的屬性方法。

var collectNonEnumProps = function (obj, keys) {
    var nonEnumIdx = nonEnumerableProps.length;
    var constructor = obj.constructor;
    var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

    // Constructor is a special case.
    var prop = 'constructor';
    if (has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

    while (nonEnumIdx--) {    // 核心: 舉例,判斷對象的toString 方法是否和 對象.constructor.protopye.toString 的內存地址是否相同,不相同,則斷定重寫了方法。
        prop = nonEnumerableProps[nonEnumIdx];
        if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
            keys.push(prop);
        }
    }
};
複製代碼
2.6 _.allKeys - 對象所擁有的可枚舉屬性的集合,包括原型鏈

allkeys方法和keys 方法惟一的不一樣在於,allkeys遍歷的集合包括了原型鏈上的屬性和方法,所以咱們能夠沿用keys的方法實現,並刪除是否爲自身屬性的判斷便可。

// 遍歷對象上可枚舉屬性和方法,包括原型鏈
_.allkeys = function(obj) {
    if(!_.isObject(obj)) return []; // 非對象則返回空數組
    if(Object.keys) return Object.keys(obj); // 支持ES5方法,則使用Object.keys()
    var keys = []
    return keys
  }
複製代碼
2.7 _.isEmpty

若是object 不包含任何值(沒有可枚舉的屬性),返回true。 對於字符串和類數組(array-like)對象,若是length屬性爲 0,那麼_.isEmpty檢查返回true。

//判斷對象是否有可枚舉屬性,字符串,類數組屬性length 是否爲0

  _.isEmpty = function(obj) {
    if(_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0
    return _.keys(obj).length === 0;
  }
複製代碼
2.8 _.isNaN - 判斷obj是否爲NaN

javascript 原生提供了一個isNaN() 的函數,該函數用於檢查其參數是否爲非數字值。通常狀況下,isNaN() 函數用於檢測 parseFloat() 和 parseInt() 的結果,以判斷它們表示的是不是合法的數字。而underscore 的isNaN 方法判斷的惟一標準是NaN 其餘狀況都會返回false,所以 _.isNaN 方法的實現以下:

// 判斷obj 是否爲NaN
  _.isNaN = function(obj) {
    return _.isNumber(obj) && isNaN(obj)  // 必須是數字,且爲NaN
  }
複製代碼
2.9 _.isNull - 判斷obj 是否爲Null
// 判斷obj 是否爲null
  _.isNull = function (obj) {
    return obj === null
  }
複製代碼
2.10 _.isUndefined - 判斷obj 是否爲undefined
// 
_.isUndefined = function(obj) {
    return obj === void 0
}
複製代碼

爲何undefined 咱們經過void 0 來判斷, 而不是直接和 "undefined"比較呢?

在ES5以前,undefined 是能夠被重寫的,在ES5以後修復了這個問題,可是即便修復了全局環境下重寫的問題,在局部環境下,依然能夠被重寫

(function() {
  var undefined;
  undefined = 1
  console.log(undefined) // 1
}())
複製代碼

而void 不管什麼值,返回的都是undefined

void function test() {
  console.log('boo!');
  // expected output: "boo!"
}();

try {
  test();
}
catch(e) {
  console.log(e);
  // expected output: ReferenceError: test is not defined
}

複製代碼
2.11 _.isElement 判斷dom元素
// 判斷obj 爲一個DOM元素
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  }
複製代碼



相關文章
相關標籤/搜索