【讀書筆記】:《編寫可維護(hù)的JavaScript》第08章 避免“空比較”

第08章 避免“空比較”

在JavaScript中,我們常??吹竭@樣的代碼:變量和null比較(這種做法很有問題),用來判斷變量是否被賦予了一個(gè)合理的值。比如:

var Controller = {
    process: function (items) {
        "use strict";
        if (items !== null) { // 不好的寫法
            items.sort();
            items.forEach(function (item) {
                // 執(zhí)行邏輯
                console.log(item);
            });
        }
    }
};

這段代碼中,process()方法顯然希望items是一個(gè)數(shù)組,但是僅僅和null比較并不能提供足夠的信息來判斷后續(xù)代碼的執(zhí)行是否真正安全,上面的代碼items可以使用數(shù)字 也可以是字符串,這些值都和null不相等。好在JavaScript為我們提供了多種方法來檢測(cè)變量的真實(shí)性。

8.1 檢測(cè)原始值

在JavaScript中有5中原始類型:字符串、數(shù)字、布爾值、null、undefined。如果希望一個(gè)值是字符串、數(shù)字、布爾值、null、undefined,最佳選擇是使用 typeof 運(yùn)算符。typeof運(yùn)算符會(huì)返回一個(gè)表示類型的字符串。

  • 字符串 string
  • 數(shù)字 number
  • 布爾值 boolean
  • undefined undefined

typeof的基本語(yǔ)法:

var str = "ddd";
typeof str; // 推薦使用這種形式
typeof(sre); // 這種用法讓 typeof 看起來像一個(gè)函數(shù)而非運(yùn)算符

使用typeof來檢測(cè)這4中原始類型時(shí)非常安全的做法。來看一下下面的例子:

// 檢測(cè)字符串
if (typeof name === "string") {
    anotherName = name.substring(3);
}

// 檢測(cè)數(shù)字
if (typeof count === "number") {
    updateCount(count);
}

// 檢測(cè)布爾值
if (typeof found === "boolean" && found){
    message("found!");
}

// 檢測(cè)undefined
if (typeof MyApp === "undefined"){
    MyApp = {

    };
}

typeof運(yùn)算符的獨(dú)特之處在于,將其用于一個(gè)未聲明的變量也不會(huì)報(bào)錯(cuò)。未定義的變量和值為undefined的變量通過typeof都會(huì)返回“undefined”。

最后一個(gè)原始值,null,一般不應(yīng)用于檢測(cè)語(yǔ)句。正如上文提到的,簡(jiǎn)單地和null比較通常不會(huì)包含足夠的信息判斷值得類型是否合法。
但有一個(gè)例外,如果所期望的值真的是null,則可以直接和null進(jìn)行比較。這時(shí)應(yīng)當(dāng)使用===或!==來和null進(jìn)行比較:

var element = document.getElementById("myDiv");
if (element !== null) {
    element.className = "found";
}

如果DOM元素不存,則document.getElementById()得到的值為null。這時(shí)null就是一個(gè)可預(yù)見的輸出,可以用!==來檢測(cè)返回結(jié)果。

運(yùn)行 typeof null 返回的是“object”,這是一個(gè)低效率的判斷null方法。如果需要檢測(cè)null,請(qǐng)直接使用===和!==。

8.2 檢測(cè)引用值

引用值也稱為對(duì)象(*object)。在JavaScript中除了原始值外都是引用值。
其中JavaScript內(nèi)置的引用類型有:Object、Array、Date、Error。typeof運(yùn)算符在判斷這些引用類型時(shí)都會(huì)返回“object”。

console.log(typeof {});             // "object"
console.log(typeof []);             // "object"
console.log(typeof new Date());     // "object"
console.log(typeof new RegExp());   // "object"

檢測(cè)某個(gè)引用值的類型的最好辦法是使用 instanceof 運(yùn)算符。instanceof的基本語(yǔ)法是:

value instanceof constructor

例如:

// 檢測(cè)日期
var value1 = new Date();
if (value1 instanceof Date) {
    console.log(value1.getFullYear());
}

// 檢測(cè)正則表達(dá)式
var value2 = new RegExp();
if (value2 instanceof RegExp) {
    console.log(value2);
}

// 檢測(cè)Error
if (value instanceof Error) {
    throw value;
}

instanceof的一個(gè)有意思的特性是它不僅檢測(cè)構(gòu)造這個(gè)對(duì)象的構(gòu)造器,還檢測(cè)原型鏈。原型鏈包含了很多信息,包括定義了對(duì)象所采用的繼承方式。比如,默認(rèn)情況下,每個(gè)對(duì)象都繼承自O(shè)bject,因此每個(gè)對(duì)象的 value instanceof Object 都會(huì)返回 true。

var now = new Date();
console.log(now instanceof Date);   // true
console.log(now instanceof Object); // true

因?yàn)檫@個(gè)原因,使用 value instanceof Object 來判斷對(duì)象是否屬于某個(gè)特定類型的做法并非最佳。

instanceof 運(yùn)算法也可以檢測(cè)自定義的類型,比如:

function Person(name) {
    "use strict";
    this.name = name;
}

var me = new Person("happyking");

console.log(me instanceof Object); // true 任何對(duì)象都是Object的實(shí)例
console.log(me instanceof Person); // true 因?yàn)閙e是Person的實(shí)例

在JavaScript中檢測(cè)自定義類型時(shí),最好的做法就是使用 instanceof 運(yùn)算符,這也是唯一的方法。同樣對(duì)于內(nèi)置JavaScript類型也是如此。但是,有一個(gè)嚴(yán)重的限制。

假設(shè)一個(gè)瀏覽器幀(frame A)里的一個(gè)對(duì)象被傳入到另一個(gè)幀(frame B)中。兩個(gè)幀里都定義了構(gòu)造函數(shù)Person。如果來自幀A的對(duì)象是幀A的Person實(shí)例,則如下規(guī)則成立:

frameAPersonInstance instanceof frameAPerson; // true
frameAPersonInstance instanceof frameBPerson; // false

因?yàn)槊總€(gè)幀(frame)都擁有Person的一份拷貝,它被認(rèn)為是該幀(frame)中的Person的拷貝實(shí)例,盡管兩個(gè)定義可能是完全一樣的。
這個(gè)問題不僅出現(xiàn)在自定義類型上,其他兩個(gè)非常重要的內(nèi)置類型也有這個(gè)問題:函數(shù)和數(shù)組、對(duì)于這兩個(gè)類型來說,一般用不著使用instanceof。

8.2.1 檢測(cè)函數(shù)

從技術(shù)上講,JavaScript中的函數(shù)是引用類型,同樣存在Function構(gòu)造函數(shù),每個(gè)函數(shù)都是其實(shí)例:

function myFun() {}
// 不好的寫法
console.log(myFun instanceof Function); // true

然而,這個(gè)方法亦不能跨幀(frame)使用,因?yàn)槊總€(gè)幀都有各自的Function構(gòu)造函數(shù)。好在typeof運(yùn)算符也可以用于函數(shù),返回“function”。

function myFun(){}
// 好的寫法
console.log(typeof myFun === "function"); // true

檢測(cè)函數(shù)最好的方法就是使用 typeof ,因?yàn)樗强梢钥鐜╢rame)使用的

8.2.2 檢測(cè)數(shù)組

ECMAScript5將Array.isArray()正式引入JavaScript。該函數(shù)可以準(zhǔn)確的檢測(cè)一個(gè)值是否為數(shù)組。Array.isArray()可以檢測(cè)跨幀(frame)傳遞的值。

var arr = [1,2,3,4];
var arr32 = 22;
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(arr32)); // false

8.3 檢測(cè)屬性

另外一種用到null(以及undefined)的場(chǎng)景是檢測(cè)一個(gè)屬性是否在對(duì)象中存在時(shí),比如:

// 不好的寫法 檢測(cè)假值
if(object[propertyName]){
    // ...
}

// 不好的寫法 和null作比較
if(object[propertyName] != null){
    // ... 
}

// 不好的寫法 和undefined作比較
if(object[propertyName] != undefined){
    // ...
}

上面這段代碼里的每一個(gè)判斷,實(shí)際上都是通過給定的名字來檢測(cè)屬性的值,而非判斷給定的名字所指的屬性是否存在。因?yàn)楫?dāng)屬性的值為假(false)時(shí)結(jié)果會(huì)出錯(cuò)。比如0、""(空字符串)、false、null、undefined。畢竟這些都是屬性的合法值。比如屬性記錄了一個(gè)數(shù)字,則這個(gè)值可以為0,這樣的話,上段代碼的第一個(gè)判斷就會(huì)出錯(cuò)。

判斷屬性是否存在的最好辦法是使用 in 運(yùn)算符。in 運(yùn)算符僅僅會(huì)簡(jiǎn)單地判斷屬性是否存在,而不會(huì)去讀屬性的值。如果實(shí)例對(duì)象的屬性存在,或者繼承自對(duì)象的原型,in運(yùn)算符都會(huì)返回true。

var object = {
    count: 0,
    relate: null
};

// 好的寫法
if ("count" in object) {
    console.log(object.count);
}

// 不好的寫法 檢查假值
if (object["count"]) {
    console.log(object.count);
}

// 好的寫法
if ("relate" in object) {
    console.log(object.relate);
}

// 不好的寫法 檢測(cè)是否為null
if (object["relate"] !== null) {
    console.log(object.relate);
}

如果你只想檢測(cè)實(shí)例對(duì)象的某個(gè)屬性是否存在,則使用hasOwnProperty()方法,所有繼承自O(shè)bject的JavaScript對(duì)象都有這個(gè)方法,如果實(shí)例中存在這個(gè)屬性則返回true(如果這個(gè)屬性只存在于原型里,則返回false)。

var object = {
    count: 0,
    relate: null
};

// 好的寫法
if (object.hasOwnProperty("count")) {
    console.log(object.count);
}

// 好的寫法
if (object.hasOwnProperty("relate")) {
    console.log(object.relate);
}

需要注意的是,在IE8以及更早的版本的IE中,DOM對(duì)象并非繼承自O(shè)bject,因此也不包含這個(gè)方法。也就是說,你在調(diào)用DOM對(duì)象的hasOwnProperty()方法之前應(yīng)當(dāng)先檢查其是否存在(假如你已經(jīng)知道對(duì)象不是DOM,則可以省略這一步)。

// 對(duì)于所有非DOM對(duì)象來說,這時(shí)好的寫法
if (object.hasOwnProperty("related")) {
    // 執(zhí)行代碼
}

// 如果你不確定是否為DOM對(duì)象,則這樣來寫
if ("hasOwnProperty" in Object && object.hasOwnProperty("related")) {
    // 執(zhí)行代碼
}

不管什么時(shí)候需要檢測(cè)屬性的存在性,請(qǐng)使用 in 運(yùn)算符或者 hasOwnProperty()。這樣可以避免很多bug。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容