翻譯:瘋狂的技術宅
原文: http://2ality.com/2018/12/cre...
本文首發微信公衆號:jingchengyideng
歡迎關注,天天都給你推送新鮮的前端技術文章html
建立數組的最佳方法是經過字面方式:前端
const arr = [0,0,0];
不過這並非長久之計,好比當咱們須要建立大型數組時。這篇博文探討了在這種狀況下應該怎麼作。程序員
在大多數編程語言中,數組是連續的值序列。在 JavaScript 中,Array 是一個將索引映射到元素的字典。它能夠存在空洞(holes) —— 零和數組長度之間的索引沒有映射到元素(「缺失索引」)。例如,下面的 Array 在索引 1 處有一個空洞:es6
> Object.keys(['a',, 'c']) [ '0', '2' ]
沒有空洞的數組也稱爲 dense 或 packed。密集數組每每表現更好,由於它們能夠連續存儲(內部)。一旦出現了空洞,內部表示就必須改變。咱們有兩種選擇:面試
不論是哪一種狀況,若是引擎遇到一個空洞,它不能只返回 undefined
,它必須遍歷原型鏈並搜索一個名稱爲「空洞索引」的屬性,這須要花費更多時間。編程
在某些引擎中,例如V8,若是切換到性能較低的數據結構,這種改變將會是永久性的。即便全部空洞都被填補,它們也不會再切換回來了。segmentfault
關於 V8 是如何表示數組的,請參閱Mathias Bynens的文章「V8中的元素類型」。數組
Array
構造函數若是要建立具備給定長度的 Array,經常使用的方法是使用 Array
構造函數 :安全
const LEN = 3; const arr = new Array(LEN); assert.equal(arr.length, LEN); // arr only has holes in it assert.deepEqual(Object.keys(arr), []);
這種方法很方便,可是有兩個缺點:微信
Array
構造函數後面加上 .fill()
方法.fill()
方法會更改當前的 Array 並使用指定的值去填充它。這有助於在用 new Array()
建立數組後對其進行初始化:
const LEN = 3; const arr = new Array(LEN).fill(0); assert.deepEqual(arr, [0, 0, 0]);
警告:若是你用對象做爲參數去 .fill()
一個數組,全部元素都會引用同一個實例(也就是這個對象沒有被克隆多份):
const LEN = 3; const obj = {}; const arr = new Array(LEN).fill(obj); assert.deepEqual(arr, [{}, {}, {}]); obj.prop = true; assert.deepEqual(arr, [ {prop:true}, {prop:true}, {prop:true} ]);
稍後咱們會遇到的一種填充方法( Array.from()
)則沒有這個問題。
.push()
方法const LEN = 3; const arr = []; for (let i=0; i < LEN; i++) { arr.push(0); } assert.deepEqual(arr, [0, 0, 0]);
這一次,咱們建立並填充了一個數組,同時裏面沒有出現漏洞。因此操做這個數組時應該比用構造函數建立的更快。不過 建立 數組的速度比較慢,由於引擎可能須要隨着數組的增加屢次從新分配連續的內存。
undefined
填充數組Array.from()
將 iterables 和相似數組的值轉換爲 Arrays ,它將空洞視爲 undefined
元素。這樣能夠用它將每一個空洞都轉換爲 undefined
:
> Array.from({length: 3}) [ undefined, undefined, undefined ]
參數 {length:3}
是一個長度爲 3 的相似 Array 的對象,其中只包含空洞。也可使用 new Array(3)
,但這樣通常會建立更大的對象。
下面這種方式僅適用於可迭代的值,而且與 Array.from()
具備相似的效果:
> [...new Array(3)] [ undefined, undefined, undefined ]
不過 Array.from()
經過 new Array()
建立它的結果,因此你獲得的仍然是一個稀疏數組。
Array.from()
進行映射若是提供映射函數做爲其第二個參數,則可使用 Array.from()
進行映射。
使用小整數建立數組:
> Array.from({length: 3}, () => 0) [ 0, 0, 0 ]
使用惟一(非共享的)對象建立數組:
> Array.from({length: 3}, () => ({})) [ {}, {}, {} ]
用升序整數數列建立數組:
> Array.from({length: 3}, (x, i) => i) [ 0, 1, 2 ]
用任意範圍的整數進行建立:
> const START=2, END=5; > Array.from({length: END-START}, (x, i) => i+START) [ 2, 3, 4 ]
另外一種建立升序整數數組的方法是用 .keys()
,它也將空洞看做是 undefined
元素:
> [...new Array(3).keys()] [ 0, 1, 2 ]
.keys()
返回一個可迭代的序列。咱們將其展開並轉換爲數組。
用空洞或 undefined
填充:
new Array(3)
[ , , ,]
Array.from({length: 2})
[undefined, undefined]
[...new Array(2)]
[undefined, undefined]
填充任意值:
const a=[]; for (let i=0; i<3; i++) a.push(0);
[0, 0, 0]
new Array(3).fill(0)
[0, 0, 0]
Array.from({length: 3}, () => ({}))
[{}, {}, {}]
(惟一對象)用整數範圍填充:
Array.from({length: 3}, (x, i) => i)
[0, 1, 2]
const START=2, END=5; Array.from({length: END-START}, (x, i) => i+START)
[2, 3, 4]
[...new Array(3).keys()]
[0, 1, 2]
我更喜歡下面的方法。個人側重點是可讀性,而不是性能。
你是否須要建立一個空的數組,之後將會徹底填充?
new Array(LEN)
你須要建立一個用原始值初始化的數組嗎?
new Array(LEN).fill(0)
你須要建立一個用對象初始化的數組嗎?
Array.from({length: LEN}, () => ({}))
你須要建立一系列整數嗎?
Array.from({length: END-START}, (x, i) => i+START)
若是你正在處理整數或浮點數的數組,請考慮Typed Arrays —— 它就是爲這個目的而設計的。它們不能存在空洞,而且老是用零進行初始化。