JS-對(duì)象無(wú)效屬性與forEach——一個(gè)考題引起的思考

NEC前端課的JS考試出成績(jī)了,趕緊去看了下主觀題錯(cuò)了哪些,發(fā)現(xiàn)幾個(gè)遺漏點(diǎn):
1.forEach方法的內(nèi)部實(shí)現(xiàn)
2.對(duì)象的未賦值屬性是否有效

原題

// 以下代碼執(zhí)行完后,`obj`和`count`的值分別是
var obj = {}, count = 0;
function logArray(value, index, array) {
    count++;
    obj[count] = value;
}
[1, 2, , 4].forEach(logArray);
得分/總分
A.{1: 1, 2: 2, 3: 4}3
B.{}0
C.{1: 1, 2: 2, 3: , 4:4}3
D.{1: 1, 2: 2, 3: , 4:4}4 ×0.00/2.00

當(dāng)初沒(méi)有在console里跑一遍,自認(rèn)為對(duì)數(shù)組還算熟悉,答案出來(lái)后——“始驚次醉終狂”⊙▽⊙夸張了——不過(guò)確實(shí)刷新了對(duì)forEach和對(duì)象屬性的認(rèn)識(shí)。

真實(shí)的forEach

以前我模擬的forEach實(shí)現(xiàn)是醬紫的:

arr.myForEach = function (callback, thisArg) {
    for(var i = 0; i < this.length; i++){
        callback.call(thisArg, this[i], i, this);
    }
}

嗯,很簡(jiǎn)單易懂,但是沒(méi)有一定的錯(cuò)誤檢測(cè)機(jī)制。

來(lái)看看MDN上的一段polyfill[1]

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function(callback, thisArg) {
        var T, k;
        if (this == null) {//如果調(diào)用forEach方法的對(duì)象是null就拋錯(cuò)
            throw new TypeError(' this is null or not defined');
        }
        var O = Object(this);//獲取調(diào)用forEach的對(duì)象
        var len = O.length >>> 0;//手動(dòng)轉(zhuǎn)為32位整數(shù)
        if(typeof callback !== "function") {//callback檢查
            throw new TypeError(callback + ' is not a function');
        }
        if (arguments.length > 1) {//第二參檢查
            T = thisArg;
        }
        k = 0;
        while (k < len) {
            var kValue;
            if (k in O) {//某個(gè)數(shù)組項(xiàng)是否在數(shù)組中
                kValue = O[k];
                callback.call(T, kValue, k, O);
            }
            k++;
        }
    };
}

以上代碼省去了MDN上原有的注釋,另外添加了幾個(gè)注釋以跟前面精簡(jiǎn)版模擬的forEach對(duì)比。多了幾個(gè)關(guān)鍵點(diǎn):調(diào)用對(duì)象的檢查,數(shù)組長(zhǎng)度轉(zhuǎn)32位,callback檢查,第二參檢查,數(shù)組項(xiàng)有效性檢查。
這里的重點(diǎn)是 數(shù)組項(xiàng)的有效性檢查 ,我們從文章開(kāi)始給出的原題的運(yùn)行可以看出forEach對(duì)數(shù)組的未賦值項(xiàng)是忽略處理的。從MDN上的polyfill看出就是通過(guò)if (k in O)來(lái)判斷是否忽略的,這個(gè)后面會(huì)講到。
因此forEach的實(shí)現(xiàn)應(yīng)該至少還要注意忽略無(wú)效項(xiàng)。

對(duì)象的無(wú)效屬性

前面講到了用if (k in O)來(lái)判斷數(shù)組項(xiàng)的有效性,但是其原理是什么呢?
這里用到了in操作符來(lái)判斷某個(gè)屬性名k是否包含在一個(gè)對(duì)象O中,為何需要這么判斷呢?
首先我們知道數(shù)組也是對(duì)象,數(shù)組中的每個(gè)項(xiàng)其實(shí)就是這個(gè)對(duì)象中的某個(gè)屬性,只不過(guò)屬性名是"0""1""2"...這樣排列的。
然后回頭來(lái)看,難道不是所有的數(shù)組項(xiàng)(無(wú)論是否已經(jīng)賦值)都自動(dòng)成為數(shù)組的(有效)屬性/項(xiàng)么?
我們測(cè)試一段代碼

var arr = [1,,2,,3];
arr;//[1, undefined × 1, 2, undefined × 1, 3]
arr[1];//undefined
'0' in arr;//true
'1' in arr;//false
'3' in arr;//false
Object.getOwnPropertyNames(arr);//["0", "2", "4", "length"]

從以上測(cè)試代碼可以看出數(shù)組項(xiàng)未賦值或者說(shuō)值為undefined的,實(shí)際上都沒(méi)有被算到這個(gè)數(shù)組對(duì)象的屬性中,只不過(guò)是在數(shù)組表現(xiàn)時(shí),有一個(gè)“占坑”的跡象——undefined × 1
這個(gè)undefined × 1表示連續(xù)的值為undefined的數(shù)組項(xiàng)有1個(gè),如果是連續(xù)n個(gè)就是× n,想想ES數(shù)組的自動(dòng)更新特性,看看以下代碼:

var arr =[1,2];
arr[10] = 5;
arr;//[1, 2, undefined × 8, 5]

回過(guò)頭來(lái),從這里就不難理解為何在forEach中可以通過(guò)if (k in O)判斷數(shù)組項(xiàng)的有效性了:in操作符可以判斷某個(gè)屬性名是否包含在一個(gè)對(duì)象或者對(duì)象從原型繼承來(lái)的屬性名列表中,而屬性值為undefined的屬性名是不會(huì)包含在這個(gè)屬性名列表中的,因此就可以判斷某個(gè)數(shù)組項(xiàng)是否未被賦值。從對(duì)象的角度來(lái)看,數(shù)組中的未賦值項(xiàng)是不存在數(shù)組對(duì)象中的。

那么是否真是如此呢?反過(guò)來(lái)想,是不是一個(gè)對(duì)象所有值為undefined的屬性就會(huì)從對(duì)象中“清除”呢?看看一段測(cè)試代碼:

var obj = {a:'tom',b:'jerry'};
obj.a = undefined;
'a' in obj;//true
obj.c = undefined
'c' in obj;//true
Object.getOwnPropertyNames(obj);//["a", "b", "c"]

從上面可以看出,并不是值為undefined的屬性就會(huì)被從對(duì)象的屬性名列表請(qǐng)清除出去,它們?nèi)匀皇菍?duì)象的屬性。
那么為何數(shù)組的就不一樣的呢?看看這個(gè),仍然是對(duì)前面的數(shù)組的例子進(jìn)行操作

arr[0] = undefined;
'0' in arr;//true
arr.push(undefined);//6
arr[5];//undefined
'5' in arr;//true
arr;//[undefined, undefined × 1, 2, undefined × 1, 3, undefined]

從這里也看到跟上面那段對(duì)象的例子相同的結(jié)果,被賦值為undefined的數(shù)組項(xiàng),也仍然會(huì)是數(shù)組對(duì)象的屬性。不過(guò)這里有一點(diǎn)很明顯的區(qū)別,undefinedundefined × 1,主動(dòng)添加或者賦值的是沒(méi)有后面的× 1的。

綜合來(lái)看

那么碼了這么多字,還是沒(méi)搞懂為何要通過(guò)if (k in O)來(lái)判斷無(wú)效屬性,而值為undefined的屬性也并非就是無(wú)效的?
那么還有種情況,就是未聲明的變量,值為undefined但是一般無(wú)法直接打印,只能通過(guò)typeof操作符來(lái)間接了解。也就是說(shuō)[1,,2,,3]中的第1項(xiàng)和第3項(xiàng)(從第0項(xiàng)開(kāi)始)都是為未聲明的變量。如果要使得數(shù)組項(xiàng)有效(即也成為數(shù)組對(duì)象的屬性),可以對(duì)其進(jìn)行賦值操作(賦值undefined也可以)。

arr[3] = 123;//123
Object.getOwnPropertyNames(arr);//["0", "2", "3", "4", "length"]

因?yàn)閷?duì)象中的屬性,不能真正的使用var來(lái)進(jìn)行變量聲明,所以都是以所賦的值為判斷標(biāo)準(zhǔn)的。所以你可以看到在對(duì)沒(méi)有d屬性一個(gè)對(duì)象obj使用obj.d來(lái)進(jìn)行屬性訪問(wèn),結(jié)果會(huì)返回undefined,因?yàn)檫@個(gè)屬性是未聲明的。
因此MDN的polyfill那段代碼要用if (k in O)來(lái)判斷數(shù)組中的項(xiàng)是否被賦值(已聲明)過(guò),來(lái)排除那些“占坑”的數(shù)組項(xiàng)。

看看下面這兩段的輸出差異:

var arr = [1,2,undefined,4];
arr.forEach(function(item,index,array){console.log(index,item);});
var arr = [1,2,,4];
arr.forEach(function(item,index,array){console.log(index,item);});

雖然兩段代碼訪問(wèn)arr[2]輸出都是undefined,但是本質(zhì)差別正如同上面的分析:前者是賦值為undefined(已聲明),后者是未聲明的。

bonus

要注意數(shù)組的中括號(hào)內(nèi)的最后一個(gè)逗號(hào)后面如果沒(méi)有值,這個(gè)最后項(xiàng)會(huì)被忽略,如下:

[1,2,];//[1, 2]
[1,2,].length;//2
[1,2,,];//[1, 2, undefined × 1]
[1,2,,].length;//3
[1,2,,,];//[1, 2, undefined × 2]
[1,2,,,].length;//4

  1. Array.prototype.forEach() ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 第5章 引用類型(返回首頁(yè)) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,270評(píng)論 0 4
  • 第三章 類型、值和變量 1、存取字符串、數(shù)字或布爾值的屬性時(shí)創(chuàng)建的臨時(shí)對(duì)象稱做包裝對(duì)象,它只是偶爾用來(lái)區(qū)分字符串值...
    坤少卡卡閱讀 656評(píng)論 0 1
  • 一、必做作業(yè):修改第一次練習(xí)的A1 片段 一: 選自《堅(jiān)持,一種可以養(yǎng)成的習(xí)慣》 【R:閱讀原文】 盡量找出不被侵...
    陽(yáng)光語(yǔ)錄閱讀 273評(píng)論 0 0
  • 如何確認(rèn)一家公司是否合規(guī)? 我想這是我們?cè)诳疾煲粋€(gè)全新的項(xiàng)目時(shí),最重要的一件事情。 沒(méi)有人希望奮斗打拼了一段時(shí)間后...
    弄月人閱讀 721評(píng)論 0 0
  • 2017年7月12日 18:00-20:00 膝旋轉(zhuǎn)與彎曲練習(xí) 直角式坐姿,兩腿向前伸直。 彎曲右膝,腳掌平貼墊...
    悠悠12321閱讀 398評(píng)論 0 0