NEC前端課的JS考試出成績了臣嚣,趕緊去看了下主觀題錯了哪些,發(fā)現(xiàn)幾個遺漏點:
1.forEach
方法的內(nèi)部實現(xiàn)
2.對象的未賦值屬性是否有效
原題
// 以下代碼執(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 |
當初沒有在console里跑一遍恋日,自認為對數(shù)組還算熟悉膀篮,答案出來后——“始驚次醉終狂”⊙▽⊙夸張了——不過確實刷新了對forEach
和對象屬性的認識。
真實的forEach
以前我模擬的forEach
實現(xiàn)是醬紫的:
arr.myForEach = function (callback, thisArg) {
for(var i = 0; i < this.length; i++){
callback.call(thisArg, this[i], i, this);
}
}
嗯岂膳,很簡單易懂誓竿,但是沒有一定的錯誤檢測機制。
來看看MDN上的一段polyfill[1]
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {//如果調(diào)用forEach方法的對象是null就拋錯
throw new TypeError(' this is null or not defined');
}
var O = Object(this);//獲取調(diào)用forEach的對象
var len = O.length >>> 0;//手動轉(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) {//某個數(shù)組項是否在數(shù)組中
kValue = O[k];
callback.call(T, kValue, k, O);
}
k++;
}
};
}
以上代碼省去了MDN上原有的注釋谈截,另外添加了幾個注釋以跟前面精簡版模擬的forEach
對比筷屡。多了幾個關(guān)鍵點:調(diào)用對象的檢查涧偷,數(shù)組長度轉(zhuǎn)32位,callback檢查毙死,第二參檢查燎潮,數(shù)組項有效性檢查。
這里的重點是 數(shù)組項的有效性檢查 扼倘,我們從文章開始給出的原題的運行可以看出forEach
對數(shù)組的未賦值項是忽略處理的确封。從MDN上的polyfill看出就是通過if (k in O)
來判斷是否忽略的,這個后面會講到唉锌。
因此forEach
的實現(xiàn)應(yīng)該至少還要注意忽略無效項隅肥。
對象的無效屬性
前面講到了用if (k in O)
來判斷數(shù)組項的有效性,但是其原理是什么呢袄简?
這里用到了in
操作符來判斷某個屬性名k
是否包含在一個對象O
中腥放,為何需要這么判斷呢?
首先我們知道數(shù)組也是對象绿语,數(shù)組中的每個項其實就是這個對象中的某個屬性秃症,只不過屬性名是"0"
、"1"
吕粹、"2"
...這樣排列的种柑。
然后回頭來看,難道不是所有的數(shù)組項(無論是否已經(jīng)賦值)都自動成為數(shù)組的(有效)屬性/項么匹耕?
我們測試一段代碼
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"]
從以上測試代碼可以看出數(shù)組項未賦值或者說值為undefined
的聚请,實際上都沒有被算到這個數(shù)組對象的屬性中,只不過是在數(shù)組表現(xiàn)時稳其,有一個“占坑”的跡象——undefined × 1
驶赏。
這個undefined × 1
表示連續(xù)的值為undefined
的數(shù)組項有1個,如果是連續(xù)n個就是× n
既鞠,想想ES數(shù)組的自動更新特性煤傍,看看以下代碼:
var arr =[1,2];
arr[10] = 5;
arr;//[1, 2, undefined × 8, 5]
回過頭來,從這里就不難理解為何在forEach
中可以通過if (k in O)
判斷數(shù)組項的有效性了:in
操作符可以判斷某個屬性名是否包含在一個對象或者對象從原型繼承來的屬性名列表中嘱蛋,而屬性值為undefined
的屬性名是不會包含在這個屬性名列表中的蚯姆,因此就可以判斷某個數(shù)組項是否未被賦值。從對象的角度來看洒敏,數(shù)組中的未賦值項是不存在數(shù)組對象中的龄恋。
那么是否真是如此呢?反過來想桐玻,是不是一個對象所有值為undefined
的屬性就會從對象中“清除”呢篙挽?看看一段測試代碼:
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
的屬性就會被從對象的屬性名列表請清除出去镊靴,它們?nèi)匀皇菍ο蟮膶傩浴?br>
那么為何數(shù)組的就不一樣的呢铣卡?看看這個链韭,仍然是對前面的數(shù)組的例子進行操作
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]
從這里也看到跟上面那段對象的例子相同的結(jié)果,被賦值為undefined
的數(shù)組項煮落,也仍然會是數(shù)組對象的屬性敞峭。不過這里有一點很明顯的區(qū)別,undefined
和undefined × 1
蝉仇,主動添加或者賦值的是沒有后面的× 1
的旋讹。
綜合來看
那么碼了這么多字,還是沒搞懂為何要通過if (k in O)
來判斷無效屬性轿衔,而值為undefined
的屬性也并非就是無效的沉迹?
那么還有種情況,就是未聲明的變量害驹,值為undefined
但是一般無法直接打印鞭呕,只能通過typeof
操作符來間接了解。也就是說[1,,2,,3]
中的第1項和第3項(從第0項開始)都是為未聲明的變量宛官。如果要使得數(shù)組項有效(即也成為數(shù)組對象的屬性)葫松,可以對其進行賦值操作(賦值undefined
也可以)。
arr[3] = 123;//123
Object.getOwnPropertyNames(arr);//["0", "2", "3", "4", "length"]
因為對象中的屬性底洗,不能真正的使用var
來進行變量聲明腋么,所以都是以所賦的值為判斷標準的。所以你可以看到在對沒有d
屬性一個對象obj
使用obj.d
來進行屬性訪問亥揖,結(jié)果會返回undefined
珊擂,因為這個屬性是未聲明的。
因此MDN的polyfill那段代碼要用if (k in O)
來判斷數(shù)組中的項是否被賦值(已聲明)過费变,來排除那些“占坑”的數(shù)組項未玻。
看看下面這兩段的輸出差異:
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);});
雖然兩段代碼訪問arr[2]
輸出都是undefined
,但是本質(zhì)差別正如同上面的分析:前者是賦值為undefined
(已聲明)胡控,后者是未聲明的。
bonus
要注意數(shù)組的中括號內(nèi)的最后一個逗號后面如果沒有值旁趟,這個最后項會被忽略昼激,如下:
[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