JavaScript專題之類型判斷(下)

JavaScript專題系列第五篇,講解更加複雜的類型判斷,好比 plainObject、空對象、類數組對象、Window對象、DOM 元素等node

前言

在上篇《JavaScript專題之類型判斷(上)》中,咱們抄襲 jQuery 寫了一個 type 函數,能夠檢測出常見的數據類型,然而在開發中還有更加複雜的判斷,好比 plainObject、空對象、Window 對象等,這一篇就讓咱們接着抄襲 jQuery 去看一下這些類型的判斷。git

plainObject

plainObject 來自於 jQuery,能夠翻譯成純粹的對象,所謂"純粹的對象",就是該對象是經過 "{}" 或 "new Object" 建立的,該對象含有零個或者多個鍵值對。github

之因此要判斷是否是 plainObject,是爲了跟其餘的 JavaScript對象如 null,數組,宿主對象(documents)等做區分,由於這些用 typeof 都會返回object。數組

jQuery提供了 isPlainObject 方法進行判斷,先讓咱們看看使用的效果:性能優化

function Person(name) {
    this.name = name;
}

console.log($.isPlainObject({})) // true

console.log($.isPlainObject(new Object)) // true

console.log($.isPlainObject(Object.create(null))); // true

console.log($.isPlainObject(Object.assign({a: 1}, {b: 2}))); // true

console.log($.isPlainObject(new Person('yayu'))); // false

console.log($.isPlainObject(Object.create({}))); // false複製代碼

由此咱們能夠看到,除了 {} 和 new Object 建立的以外,jQuery 認爲一個沒有原型的對象也是一個純粹的對象。函數

實際上隨着 jQuery 版本的提高,isPlainObject 的實現也在變化,咱們今天講的是 3.0 版本下的 isPlainObject,咱們直接看源碼:性能

// 上節中寫 type 函數時,用來存放 toString 映射結果的對象
var class2type = {};

// 至關於 Object.prototype.toString
var toString = class2type.toString;

// 至關於 Object.prototype.hasOwnProperty
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;

    // 排除掉明顯不是obj的以及一些宿主對象如Window
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }

    /** * getPrototypeOf es5 方法,獲取 obj 的原型 * 以 new Object 建立的對象爲例的話 * obj.__proto__ === Object.prototype */
    proto = Object.getPrototypeOf(obj);

    // 沒有原型的對象是純粹的,Object.create(null) 就在這裏返回 true
    if (!proto) {
        return true;
    }

    /** * 如下判斷經過 new Object 方式建立的對象 * 判斷 proto 是否有 constructor 屬性,若是有就讓 Ctor 的值爲 proto.constructor * 若是是 Object 函數建立的對象,Ctor 在這裏就等於 Object 構造函數 */
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

    // 在這裏判斷 Ctor 構造函數是否是 Object 構造函數,用於區分自定義構造函數和 Object 構造函數
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}複製代碼

注意:咱們判斷 Ctor 構造函數是否是 Object 構造函數,用的是 hasOwn.toString.call(Ctor),這個方法可不是 Object.prototype.toString,不信咱們在函數里加上下面這兩句話:優化

console.log(hasOwn.toString.call(Ctor)); // function Object() { [native code] }
console.log(Object.prototype.toString.call(Ctor)); // [object Function]複製代碼

發現返回的值並不同,這是由於 hasOwn.toString 調用的實際上是 Function.prototype.toString,畢竟 hasOwnProperty 但是一個函數!ui

並且 Function 對象覆蓋了從 Object 繼承來的 Object.prototype.toString 方法。函數的 toString 方法會返回一個表示函數源代碼的字符串。具體來講,包括 function關鍵字,形參列表,大括號,以及函數體中的內容。this

EmptyObject

jQuery提供了 isEmptyObject 方法來判斷是不是空對象,代碼簡單,咱們直接看源碼:

function isEmptyObject( obj ) {

        var name;

        for ( name in obj ) {
            return false;
        }

        return true;
}複製代碼

其實所謂的 isEmptyObject 就是判斷是否有屬性,for 循環一旦執行,就說明有屬性,有屬性就會返回 false。

可是根據這個源碼咱們能夠看出isEmptyObject實際上判斷的並不只僅是空對象。

舉個栗子:

console.log(isEmptyObject({})); // true
console.log(isEmptyObject([])); // true
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // true
console.log(isEmptyObject(1)); // true
console.log(isEmptyObject('')); // true
console.log(isEmptyObject(true)); // true複製代碼

以上都會返回 true。

可是既然 jQuery 是這樣寫,多是由於考慮到實際開發中 isEmptyObject 用來判斷 {} 和 {a: 1} 是足夠的吧。若是真的是隻判斷 {},徹底能夠結合上篇寫的 type 函數篩選掉不適合的狀況。

Window對象

Window 對象做爲客戶端 JavaScript 的全局對象,它有一個 window 屬性指向自身,這點在《JavaScript深刻之變量對象》中講到過。咱們能夠利用這個特性判斷是不是 Window 對象。

function isWindow( obj ) {
    return obj != null && obj === obj.window;
}複製代碼

isArrayLike

isArrayLike,看名字可能會讓咱們以爲這是判斷類數組對象的,其實不只僅是這樣,jQuery 實現的 isArrayLike,數組和類數組都會返回 true。

由於源碼比較簡單,咱們直接看源碼:

function isArrayLike(obj) {

    // obj 必須有 length屬性
    var length = !!obj && "length" in obj && obj.length;
    var typeRes = type(obj);

    // 排除掉函數和 Window 對象
    if (typeRes === "function" || isWindow(obj)) {
        return false;
    }

    return typeRes === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}複製代碼

重點分析 return 這一行,使用了或語句,只要一個爲 true,結果就返回 true。

因此若是 isArrayLike 返回true,至少要知足三個條件之一:

  1. 是數組
  2. 長度爲 0
  3. lengths 屬性是大於 0 的數組,而且obj[length - 1]必須存在

第一個就不說了,看第二個,爲何長度爲 0 就能夠直接判斷爲 true 呢?

那咱們寫個對象:

var obj = {a: 1, b: 2, length: 0}複製代碼

isArrayLike 函數就會返回 true,那這個合理嗎?

回答合不合理以前,咱們先看一個例子:

function a(){
    console.log(isArrayLike(arguments))
}
a();複製代碼

若是咱們去掉length === 0 這個判斷,就會打印 false,然而咱們都知道 arguments 是一個類數組對象,這裏是應該返回 true 的。

因此是否是爲了放過空的 arguments 時也放過了一些存在爭議的對象呢?

第三個條件:length 是數字,而且 length > 0 且最後一個元素存在。

爲何僅僅要求最後一個元素存在呢?

讓咱們先想下數組是否是能夠這樣寫:

var arr = [,,3]複製代碼

當咱們寫一個對應的類數組對象就是:

var arrLike = {
    2: 3,
    length: 3
}複製代碼

也就是說當咱們在數組中用逗號直接跳過的時候,咱們認爲該元素是不存在的,類數組對象中也就不用寫這個元素,可是最後一個元素是必定要寫的,要否則 length 的長度就不會是最後一個元素的 key 值加 1。好比數組能夠這樣寫

var arr = [1,,];
console.log(arr.length) // 2複製代碼

可是類數組對象就只能寫成:

var arrLike = {
    0: 1,
    length: 1
}複製代碼

因此符合條件的類數組對象是必定存在最後一個元素的!

這就是知足 isArrayLike 的三個條件,其實除了 jQuery 以外,不少庫都有對 isArrayLike 的實現,好比 underscore:

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};複製代碼

isElement

isElement 判斷是否是 DOM 元素。

isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
};複製代碼

結語

這一篇咱們介紹了 jQuery 的 isPlainObject、isEmptyObject、isWindow、isArrayLike、以及 underscore 的 isElement 實現。咱們能夠看到,即便是 jQuery 這樣優秀的庫,一些方法的實現也並非很是完美和嚴密的,可是最後爲何這麼作,其實也是一種權衡,權衡所失與所得,正如玉伯在《從 JavaScript 數組去重談性能優化》中講到:

全部這些點,都必須腳踏實地在具體應用場景下去分析、去選擇,要讓場景說話。

專題系列

JavaScript專題系列目錄地址:github.com/mqyqingfeng…

JavaScript專題系列預計寫二十篇左右,主要研究平常開發中一些功能點的實現,好比防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特色是研(chao)究(xi) underscore 和 jQuery 的實現方式。

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。

相關文章
相關標籤/搜索