編寫代碼中通常會有快速初始化數組的需求,例如我們需要一個類似matlab里的zeros函數,假如這裡我們需要生成一個0-23的數組用於表示一天24小時。 最基本的做法如下: function(){ let hours = []; for(let k = 0; k < 24; k++ )hours.p ...
編寫代碼中通常會有快速初始化數組的需求,例如我們需要一個類似matlab里的zeros
函數,假如這裡我們需要生成一個0-23的數組用於表示一天24小時。
最基本的做法如下:
function(){
let hours = [];
for(let k = 0; k < 24; k++ )hours.push(k);
return hours;
}
下麵我們來思考如何用更優雅的方式實現。
考慮使用new Array(24)
+ map
的方法來實現。
代碼如下:
Array(24).map((_, h) => h);
註意,這裡map
的第二個參數是索引,平時用的少,這裡把索引作為數值。
結果與預期並不符合,為啥呢?
簡單搜索了一下,發現時因為js里的稀疏數組的邏輯導致的。
我們先看一下下麵的代碼:
let a = [];
a[1000] = 2;
console.log(a.length);
// 1000
a.forEach(x => console.log("hello"));
// only one "hello"
js的處理邏輯是,對於沒有主動賦值的位置進行“空置”處理,對於這些“空置”未知,迭代器是不會理會的,這麼做最主要的目的就是避免不合理的賦值操作導致的bug。
假設沒有這種邏輯,我們寫下了new Array(Date.now())
,這將導致系統新建一個非常大的數組,而實際上啥也沒存。
我們可以吧new Array(len)
乾的事情簡單理解為下麵的過程:
function(len){
let r = [];
r.length = len;
return r;
這就是為什麼對new Array(len)
調用map
或者forEach
的時候跟預期不一致了。
如何解決這一問題呢,除了使用new Array(len)
的形式,我們還可以使用new Array(1,2,3)
這種寫法來初始化數組,但是這麼寫就沒法實現我們編寫初始化數組的目的了。
這個時候我們想到了apply
,這個函數的第二個參數正好就是一個數組,於是我們寫下了下麵的代碼。
// 借用apply
Array.apply(null, Array(24)).map((_, h) => h);
// [0, 1, ..., 24]
得到了我們希望的結果。這就說明,Array(24)
在apply
中作為參數的時候是被當做24個值對待的,因為這一點就保證了最後得到的數組長度是24。
既然如此,我們當然同樣可以使用apply
的姊妹函數call
。
// 借用call
Array.call(null, ...Array(24)).map((_, h) => h);
// [0, 1... 23]
這也更確認了一件事,Array(len)
解構會得到len
個參數而非一個參數,當然call
的使用必須在支持解構操作符的環境中。
在熟悉了call
和apply
的原理後,我們可以進一步寫出下麵的代碼:
// Array本身
Array(...Array(24)).map((_, h) => h);
// [0, 1, ..., 24]
這種形式已經足夠優雅了。
另外,在ES6
中,Array
提供了新方法fill
,借用該方法填充那些“空置”位,進而保證後續的操作能順利進行。
具體代碼如下:
// 推薦
Array(24).fill(null).map((_, h) => h);
現在也比較推薦最後一種寫法,這種方法也最為直觀。
不過需要註意fill
方法的使用,應該儘量避免盲目地填充,因為這樣會上面提到了js設計“空置”邏輯為了避免的bug。
有興趣的可以嘗試一下下麵的代碼:
// no-fill
console.time("no-fill");
let t = Array(5e7);
console.timeEnd("no-fill");
// fill
console.time("fill");
let q = Array(5e7).fill(null);
console.timeEnd("fill");
// => no-fill: 0.240ms
// => fill: 3247.921ms
在瀏覽器中嘗試瀏覽器基本會不響應,沒有設置更大的數值為了避免得不到結果。假設一個int占用4個位元組的記憶體,這個fill會占用200M的記憶體,且需要迴圈5千萬次,這必然會占用大量的CPU和記憶體,在單線程的js中是絕對不允許的。