Skip to content

2026-06-09

/ 12 分鐘閱讀

/ Deep JavaScript Foundations v3

相等性練習解析:findAll 的實作邏輯

本文是上一篇練習的解答說明。這個練習的核心目的不是教你實作 findAll 這個函式本身,而是透過逐一消除角落案例的過程,示範「受控的強制相等比較」應該長什麼樣子。

完整解法

javascript
function findAll(match, arr) {
    var ret = [];
    for (let v of arr) {

        // 規則一:完全相同(處理 NaN、-0 的正確比較)
        if (Object.is(match, v)) {
            ret.push(v);
        }

        // 規則二:null 與 undefined 互相匹配
        else if (match == null && v == null) {
            ret.push(v);
        }

        // 規則三:boolean 只匹配 boolean,且必須完全相同
        else if (typeof match == "boolean") {
            if (match === v) {
                ret.push(v);
            }
        }

        // 規則四:非空字串可以匹配數字(排除 -0)
        else if (
            typeof match == "string" &&
            match.trim() != "" &&
            typeof v == "number" &&
            !Object.is(-0, v)
        ) {
            if (match == v) {
                ret.push(v);
            }
        }

        // 規則五:非特殊數字可以匹配非空字串(排除 -0、NaN、Infinity)
        else if (
            typeof match == "number" &&
            !Object.is(match, -0) &&
            !Object.is(match, NaN) &&
            !Object.is(match, Infinity) &&
            !Object.is(match, -Infinity) &&
            typeof v == "string" &&
            v.trim() != ""
        ) {
            if (match == v) {
                ret.push(v);
            }
        }
    }
    return ret;
}

規則一:Object.is() 作為第一關

javascript
if (Object.is(match, v)) {
    ret.push(v);
}

Object.is() 是整個函式的基礎,優先於所有其他規則執行。它正確處理了兩個 === 說謊的角落案例:

  • Object.is(NaN, NaN)trueNaN 匹配自身)
  • Object.is(-0, -0)true-0 匹配自身)
  • Object.is(-0, 0)false-0 不匹配 0

這一條規則讓後續規則不需要再處理「完全相同」的情況。

規則二:nullundefined 的強制相等

javascript
else if (match == null && v == null) {
    ret.push(v);
}

利用 == null 同時匹配 nullundefined 的特性(規格書第 2、3 條保證這是安全的,且不會匹配其他任何值)。這是我們在相等性章節中提到的 == null 最佳使用場景。

規則三:boolean 嚴格配對

javascript
else if (typeof match == "boolean" && typeof v == "boolean") {
    if (match === v) {
        ret.push(v);
    }
}

設計為兩層判斷:外層確認 match 是 boolean,內層用 === 確保兩者完全相同。

為什麼內層可以用 == 替代 ===?因為外層已經確認 match 是 boolean,而如果 v 不是 boolean,match == v 會觸發 ToNumber(boolean 轉 0 或 1),導致 true == 1 這類不想要的匹配。所以這裡選擇在外層先確認兩者都是 boolean,再比較——此時 ===== 效果相同,用哪個都可以。

規則四:字串匹配數字(排除 -0

javascript
else if (
    typeof match == "string" &&
    match.trim() != "" &&       // 排除空字串和只有空白的字串
    typeof v == "number" &&
    !Object.is(-0, v)           // 排除 -0
) {
    if (match == v) { ret.push(v); }
}

為什麼要排除 -0

String(-0) 得到 "0",而 "-0" == -0 透過強制轉型會讓 "-0" 轉成 -0 再比較,但字串 "0" 對應的是 0 而非 -0。若不排除 -0"0" 可能意外匹配到 -0,產生錯誤結果。

外層先消除角落案例,內層才信任 ==:這是整個函式最核心的設計模式——不是直接用 ==,而是先用外層條件縮小安全範圍,確認沒有角落案例後,才讓 == 做強制相等比較。

規則五:數字匹配字串(排除 -0NaNInfinity

javascript
else if (
    typeof match == "number" &&
    !Object.is(match, -0) &&
    !Object.is(match, NaN) &&
    !Object.is(match, Infinity) &&
    !Object.is(match, -Infinity) &&
    typeof v == "string" &&
    v.trim() != ""              // 排除空字串和只有空白的字串
) {
    if (match == v) { ret.push(v); }
}

排除的值:

  • -0:字串化後變 "0",會造成混淆
  • NaNNaN == "NaN"false(NaN 不等於任何東西),但 NaN 也是 number,不排除會進入這個分支
  • Infinity-Infinity:不應參與字串強制轉型的比較

此文章是 FrontendMasters 上的 Deep JavaScript Foundations, v3 課程筆記

最後更新時間:

Buy Me A Coffee

系列章節 第 29 篇 / 共 51 篇

0 %
MIT Licensed | Copyright © 2025-present Wen-Hsiu's Blog
Photo by Federica Galli on Unsplash