国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript專題之如何判斷兩個對象相等

時飛 / 369人閱讀

摘要:其他判斷構造函數實例我們看個例子雖然和都是,但是和屬于不同構造函數的實例,為了做出區分,我們認為是不同的對象。

JavaScript 專題系列第十二篇,講解如何判斷兩個參數是否相等

前言

雖然標題寫的是如何判斷兩個對象相等,但本篇我們不僅僅判斷兩個對象相等,實際上,我們要做到的是如何判斷兩個參數相等,而這必然會涉及到多種類型的判斷。

相等

什么是相等?在《JavaScript專題之去重》中,我們認為只要 === 的結果為 true,兩者就相等,然而今天我們重新定義相等:

我們認為:

NaN 和 NaN 是相等

[1] 和 [1] 是相等

{value: 1} 和 {value: 1} 是相等

不僅僅是這些長得一樣的,還有

1 和 new Number(1) 是相等

"Curly" 和 new String("Curly") 是相等

true 和 new Boolean(true) 是相等

更復雜的我們會在接下來的內容中看到。

目標

我們的目標是寫一個 eq 函數用來判斷兩個參數是否相等,使用效果如下:

function eq(a, b) { ... }

var a = [1];
var b = [1];
console.log(eq(a, b)) // true

在寫這個看似很簡單的函數之前,我們首先了解在一些簡單的情況下是如何判斷的?

+0 與 -0

如果 a === b 的結果為 true, 那么 a 和 b 就是相等的嗎?一般情況下,當然是這樣的,但是有一個特殊的例子,就是 +0 和 -0。

JavaScript “處心積慮”的想抹平兩者的差異:

// 表現1
console.log(+0 === -0); // true

// 表現2
(-0).toString() // "0"
(+0).toString() // "0"

// 表現3
-0 < +0 // false
+0 < -0 // false

即便如此,兩者依然是不同的:

1 / +0 // Infinity
1 / -0 // -Infinity

1 / +0 === 1 / -0 // false

也許你會好奇為什么要有 +0 和 -0 呢?

這是因為 JavaScript 采用了IEEE_754 浮點數表示法(幾乎所有現代編程語言所采用),這是一種二進制表示法,按照這個標準,最高位是符號位(0 代表正,1 代表負),剩下的用于表示大小。而對于零這個邊界值 ,1000(-0) 和 0000(0)都是表示 0 ,這才有了正負零的區別。

也許你會好奇什么時候會產生 -0 呢?

Math.round(-0.1) // -0

那么我們又該如何在 === 結果為 true 的時候,區別 0 和 -0 得出正確的結果呢?我們可以這樣做:

function eq(a, b){
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    return false;
}

console.log(eq(0, 0)) // true
console.log(eq(0, -0)) // false
NaN

在本篇,我們認為 NaN 和 NaN 是相等的,那又該如何判斷出 NaN 呢?

console.log(NaN === NaN); // false

利用 NaN 不等于自身的特性,我們可以區別出 NaN,那么這個 eq 函數又該怎么寫呢?

function eq(a, b) {
    if (a !== a) return b !== b;
}

console.log(eq(NaN, NaN)); // true
eq 函數

現在,我們已經可以去寫 eq 函數的第一版了。

// eq 第一版
// 用來過濾掉簡單的類型比較,復雜的對象使用 deepEq 函數進行處理
function eq(a, b) {

    // === 結果為 true 的區別出 +0 和 -0
    if (a === b) return a !== 0 || 1 / a === 1 / b;

    // typeof null 的結果為 object ,這里做判斷,是為了讓有 null 的情況盡早退出函數
    if (a == null || b == null) return false;

    // 判斷 NaN
    if (a !== a) return b !== b;

    // 判斷參數 a 類型,如果是基本類型,在這里可以直接返回 false
    var type = typeof a;
    if (type !== "function" && type !== "object" && typeof b != "object") return false;

    // 更復雜的對象使用 deepEq 函數進行深度比較
    return deepEq(a, b);
};

也許你會好奇是不是少了一個 typeof b !== function?

試想如果我們添加上了這句,當 a 是基本類型,而 b 是函數的時候,就會進入 deepEq 函數,而去掉這一句,就會進入直接進入 false,實際上 基本類型和函數肯定是不會相等的,所以這樣做代碼又少,又可以讓一種情況更早退出。

String 對象

現在我們開始寫 deepEq 函數,一個要處理的重大難題就是 "Curly" 和 new String("Curly") 如何判斷成相等?

兩者的類型都不一樣吶!不信我們看 typeof 的操作結果:

console.log(typeof "Curly"); // string
console.log(typeof new String("Curly")); // object

可是我們在《JavaScript專題之類型判斷上》中還學習過更多的方法判斷類型,比如 Object.prototype.toString:

var toString = Object.prototype.toString;
toString.call("Curly"); // "[object String]"
toString.call(new String("Curly")); // "[object String]"

神奇的是使用 toString 方法兩者判斷的結果卻是一致的,可是就算知道了這一點,還是不知道如何判斷字符串和字符串包裝對象是相等的呢?

那我們利用隱式類型轉換呢?

console.log("Curly" + "" === new String("Curly") + ""); // true

看來我們已經有了思路:如果 a 和 b 的 Object.prototype.toString的結果一致,并且都是"[object String]",那我們就使用 "" + a === "" + b 進行判斷。

可是不止有 String 對象吶,Boolean、Number、RegExp、Date呢?

更多對象

跟 String 同樣的思路,利用隱式類型轉換。

Boolean

var a = true;
var b = new Boolean(true);

console.log(+a === +b) // true

Date

var a = new Date(2009, 9, 25);
var b = new Date(2009, 9, 25);

console.log(+a === +b) // true

RegExp

var a = /a/i;
var b = new RegExp(/a/i);

console.log("" + a === "" + b) // true

Number

var a = 1;
var b = new Number(1);

console.log(+a === +b) // true

嗯哼?你確定 Number 能這么簡單的判斷?

var a = Number(NaN);
var b = Number(NaN);

console.log(+a === +b); // false

可是 a 和 b 應該被判斷成 true 的吶~

那么我們就改成這樣:

var a = Number(NaN);
var b = Number(NaN);

function eq() {
    // 判斷 Number(NaN) Object(NaN) 等情況
    if (+a !== +a) return +b !== +b;
    // 其他判斷 ...
}

console.log(eq(a, b)); // true
deepEq 函數

現在我們可以寫一點 deepEq 函數了。

var toString = Object.prototype.toString;

function deepEq(a, b) {
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;

    switch (className) {
        case "[object RegExp]":
        case "[object String]":
            return "" + a === "" + b;
        case "[object Number]":
            if (+a !== +a) return +b !== +b;
            return +a === 0 ? 1 / +a === 1 / b : +a === +b;
      case "[object Date]":
      case "[object Boolean]":
            return +a === +b;
    }

    // 其他判斷
}
構造函數實例

我們看個例子:

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

function Animal() {
    this.name = name
}

var person = new Person("Kevin");
var animal = new Animal("Kevin");

eq(person, animal) // ???

雖然 personanimal 都是 {name: "Kevin"},但是 personanimal 屬于不同構造函數的實例,為了做出區分,我們認為是不同的對象。

如果兩個對象所屬的構造函數對象不同,兩個對象就一定不相等嗎?

并不一定,我們再舉個例子:

var attrs = Object.create(null);
attrs.name = "Bob";
eq(attrs, {name: "Bob"}); // ???

盡管 attrs 沒有原型,{name: "Bob"} 的構造函數是 Object,但是在實際應用中,只要他們有著相同的鍵值對,我們依然認為是相等。

從函數設計的角度來看,我們不應該讓他們相等,但是從實踐的角度,我們讓他們相等,所以相等就是一件如此隨意的事情嗎?!對啊,我也在想:undersocre,你怎么能如此隨意呢!!!

哎,吐槽完了,我們還是要接著寫這個相等函數,我們可以先做個判斷,對于不同構造函數下的實例直接返回 false。

function isFunction(obj) {
    return toString.call(obj) === "[object Function]"
}

function deepEq(a, b) {
    // 接著上面的內容
    var areArrays = className === "[object Array]";
    // 不是數組
    if (!areArrays) {
        // 過濾掉兩個函數的情況
        if (typeof a != "object" || typeof b != "object") return false;

        var aCtor = a.constructor, bCtor = b.constructor;
        // aCtor 和 bCtor 必須都存在并且都不是 Object 構造函數的情況下,aCtor 不等于 bCtor, 那這兩個對象就真的不相等啦
        if (aCtor == bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ("constructor" in a && "constructor" in b)) {
            return false;
        }
    }

    // 下面還有好多判斷
}
數組相等

現在終于可以進入我們期待已久的數組和對象的判斷,不過其實這個很簡單,就是遞歸遍歷一遍……

function deepEq(a, b) {
    // 再接著上面的內容
    if (areArrays) {

        length = a.length;
        if (length !== b.length) return false;

        while (length--) {
            if (!eq(a[length], b[length])) return false;
         }
    } 
    else {

        var keys = Object.keys(a), key;
        length = keys.length;

        if (Object.keys(b).length !== length) return false;

        while (length--) {
            key = keys[length];
            if (!(b.hasOwnProperty(key) && eq(a[key], b[key]))) return false;
        }
    }
    return true;

}
循環引用

如果覺得這就結束了,簡直是太天真,因為最難的部分才終于要開始,這個問題就是循環引用!

舉個簡單的例子:

a = {abc: null};
b = {abc: null};
a.abc = a;
b.abc = b;

eq(a, b)

再復雜一點的,比如:

a = {foo: {b: {foo: {c: {foo: null}}}}};
b = {foo: {b: {foo: {c: {foo: null}}}}};
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;

eq(a, b)

為了給大家演示下循環引用,大家可以把下面這段已經精簡過的代碼復制到瀏覽器中嘗試:

// demo
var a, b;

a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;

function eq(a, b, aStack, bStack) {
    if (typeof a == "number") {
        return a === b;
    }

    return deepEq(a, b)
}

function deepEq(a, b) {

    var keys = Object.keys(a);
    var length = keys.length;
    var key;

    while (length--) {
        key = keys[length]

        // 這是為了讓你看到代碼其實一直在執行
        console.log(a[key], b[key])

        if (!eq(a[key], b[key])) return false;
    }

    return true;

}

eq(a, b)

嗯,以上的代碼是死循環。

那么,我們又該如何解決這個問題呢?underscore 的思路是 eq 的時候,多傳遞兩個參數為 aStack 和 bStack,用來儲存 a 和 b 遞歸比較過程中的 a 和 b 的值,咋說的這么繞口呢?
我們直接看個精簡的例子:

var a, b;

a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;

function eq(a, b, aStack, bStack) {
    if (typeof a == "number") {
        return a === b;
    }

    return deepEq(a, b, aStack, bStack)
}

function deepEq(a, b, aStack, bStack) {

    aStack = aStack || [];
    bStack = bStack || [];

    var length = aStack.length;

    while (length--) {
        if (aStack[length] === a) {
              return bStack[length] === b;
        }
    }

    aStack.push(a);
    bStack.push(b);

    var keys = Object.keys(a);
    var length = keys.length;
    var key;

    while (length--) {
        key = keys[length]

        console.log(a[key], b[key], aStack, bStack)

        if (!eq(a[key], b[key], aStack, bStack)) return false;
    }

    // aStack.pop();
    // bStack.pop();
    return true;

}

console.log(eq(a, b))

之所以注釋掉 aStack.pop()bStack.pop()這兩句,是為了方便大家查看 aStack bStack的值。

最終的 eq 函數

最終的代碼如下:

var toString = Object.prototype.toString;

function isFunction(obj) {
    return toString.call(obj) === "[object Function]"
}

function eq(a, b, aStack, bStack) {

    // === 結果為 true 的區別出 +0 和 -0
    if (a === b) return a !== 0 || 1 / a === 1 / b;

    // typeof null 的結果為 object ,這里做判斷,是為了讓有 null 的情況盡早退出函數
    if (a == null || b == null) return false;

    // 判斷 NaN
    if (a !== a) return b !== b;

    // 判斷參數 a 類型,如果是基本類型,在這里可以直接返回 false
    var type = typeof a;
    if (type !== "function" && type !== "object" && typeof b != "object") return false;

    // 更復雜的對象使用 deepEq 函數進行深度比較
    return deepEq(a, b, aStack, bStack);
};

function deepEq(a, b, aStack, bStack) {

    // a 和 b 的內部屬性 [[class]] 相同時 返回 true
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;

    switch (className) {
        case "[object RegExp]":
        case "[object String]":
            return "" + a === "" + b;
        case "[object Number]":
            if (+a !== +a) return +b !== +b;
            return +a === 0 ? 1 / +a === 1 / b : +a === +b;
        case "[object Date]":
        case "[object Boolean]":
            return +a === +b;
    }

    var areArrays = className === "[object Array]";
    // 不是數組
    if (!areArrays) {
        // 過濾掉兩個函數的情況
        if (typeof a != "object" || typeof b != "object") return false;

        var aCtor = a.constructor,
            bCtor = b.constructor;
        // aCtor 和 bCtor 必須都存在并且都不是 Object 構造函數的情況下,aCtor 不等于 bCtor, 那這兩個對象就真的不相等啦
        if (aCtor == bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ("constructor" in a && "constructor" in b)) {
            return false;
        }
    }


    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;

    // 檢查是否有循環引用的部分
    while (length--) {
        if (aStack[length] === a) {
            return bStack[length] === b;
        }
    }

    aStack.push(a);
    bStack.push(b);

    // 數組判斷
    if (areArrays) {

        length = a.length;
        if (length !== b.length) return false;

        while (length--) {
            if (!eq(a[length], b[length], aStack, bStack)) return false;
        }
    }
    // 對象判斷
    else {

        var keys = Object.keys(a),
            key;
        length = keys.length;

        if (Object.keys(b).length !== length) return false;
        while (length--) {

            key = keys[length];
            if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
        }
    }

    aStack.pop();
    bStack.pop();
    return true;

}

console.log(eq(0, 0)) // true
console.log(eq(0, -0)) // false

console.log(eq(NaN, NaN)); // true
console.log(eq(Number(NaN), Number(NaN))); // true

console.log(eq("Curly", new String("Curly"))); // true

console.log(eq([1], [1])); // true
console.log(eq({ value: 1 }, { value: 1 })); // true

var a, b;

a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;

console.log(eq(a, b)) // true

真讓人感嘆一句:eq 不愧是 underscore 中實現代碼行數最多的函數了!

專題系列

JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。

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

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84789.html

相關文章

  • JavaScript專題系列文章

    摘要:專題系列共計篇,主要研究日常開發中一些功能點的實現,比如防抖節流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點是研究專題之函數組合專題系列第十六篇,講解函數組合,并且使用柯里化和函數組合實現模式需求我們需要寫一個函數,輸入,返回。 JavaScript 專題之從零實現 jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現一個 jQuery 的 ext...

    Maxiye 評論0 收藏0
  • JavaScript專題系列20篇正式完結!

    摘要:寫在前面專題系列是我寫的第二個系列,第一個系列是深入系列。專題系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 專題系列是我寫的第二個系列,第一個系列是 JavaScript 深入系列。 JavaScript 專題系列共計 20 篇,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里...

    sixleaves 評論0 收藏0
  • JavaScript專題遞歸

    摘要:專題系列第十八篇,講解遞歸和尾遞歸定義程序調用自身的編程技巧稱為遞歸。然而非尾調用函數,就會創建多個執行上下文壓入執行上下文棧。所以我們只用把階乘函數改造成一個尾遞歸形式,就可以避免創建那么多的執行上下文。 JavaScript 專題系列第十八篇,講解遞歸和尾遞歸 定義 程序調用自身的編程技巧稱為遞歸(recursion)。 階乘 以階乘為例: function factorial(n...

    asoren 評論0 收藏0
  • JS專題數組去重

    摘要:將元素作為對象的鍵,默認鍵對應的值為如果對象中沒有這個鍵,則將這個元素放入結果數組中去。 前言 數組去重在日常開發中的使用頻率還是較高的,也是網上隨便一抓一大把的話題,所以,我寫這篇文章目的在于歸納和總結,既然很多人都在提的數組去重,自己到底了解多少呢。又或者是如果自己在開發中遇到了去重的需求,自己能想到更好的解決方案嗎。 這次我們來理一理怎么做數組去重才能做得最合適,既要考慮兼容性,...

    only_do 評論0 收藏0
  • JavaScript專題數組去重

    摘要:專題系列第三篇,講解各種數組去重方法,并且跟著寫一個前言數組去重方法老生常談,既然是常談,我也來談談。它類似于數組,但是成員的值都是唯一的,沒有重復的值。 JavaScript 專題系列第三篇,講解各種數組去重方法,并且跟著 underscore 寫一個 unique API 前言 數組去重方法老生常談,既然是常談,我也來談談。 雙層循環 也許我們首先想到的是使用 indexOf 來循...

    fsmStudy 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<