JavaScript 數組不是連續(contiguous)的,其實現類似哈希映射(hash-maps)或字典(dictionaries)。覺得這有點像是一門 B 級語言,數組實現根本不恰當。
為什么說 JavaScript 數組不是真正的數組
數組是一串連續的內存位置,用來保存某些值。注意重點,“連續”(continuous,或 contiguous),這很重要。
 上圖展示了數組在內存中存儲方式。這個數組保存了 4 個元素,每個元素 4 字節。加起來總共占用了 16 字節的內存區。
假設聲明了 tinyInt arr[4];,分配到的內存區的地址從 1201 開始。一旦需要讀取 arr[2],只需要通過數學計算拿到 arr[2] 的地址即可。計算 1201 + (2 X 4),直接從 1209 開始讀取即可。
 JavaScript 中的數據是哈希映射,可以使用不同的數據結構來實現,如鏈表。所以,如果在 JavaScript 中聲明一個數組 var arr = new Array(4),計算機將生成類似上圖的結構。如果程序需要讀取 arr[2],則需要從 1201 開始遍歷尋址。
以上急速 JavaScript 數組與真實數組的不同之處。顯而易見,數學計算比遍歷鏈表快。就長數組而言,情況尤其如此。
JavaScript 數組的進化
JavaScript 這門語言也進化了不少。從 V8、SpiderMonkey 到 TC39 和與日俱增的 Web 用戶,巨大的努力已經使 JavaScript 成為世界級必需品。一旦有了龐大的用戶基礎,性能提升自然是硬需求。
現代 JavaScript 引擎是會給數組分配連續內存的 —— 如果數組是同質的(所有元素類型相同)。優秀的程序員總會保證數組同質,以便 JIT(即時編譯器)能夠使用 c 編譯器式的計算方法讀取元素。
想要在某個同質數組中插入一個其他類型的元素,JIT 將解構整個數組,并按照舊有的方式重新創建。
如果代碼寫得不太糟,JavaScript Array 對象在幕后依然保持著真正的數組形式,這對現代 JS 開發者來說極為重要。
數組跟隨 ES2015/ES6 有了更多的演進。TC39 決定引入類型化數組(Typed Arrays),于是我們就有了 ArrayBuffer。
ArrayBuffer 提供一塊連續內存供隨意操作。直接操作內存還是太復雜、偏底層。于是便有了處理 ArrayBuffer 的視圖(View)。目前已有一些可用視圖,未來還會有更多加入。
 了解更多關于類型化數組(Typed Arrays)的知識,請訪問 MDN 文檔。
高性能、高效率的類型化數組在 WebGL 之后被引入。WebGL 工作者遇到了極大的性能問題,即如何高效處理二進制數據。可以使用 SharedArrayBuffer 在多個 Web Worker 進程之間共享數據提升性能。
從簡單的哈希映射到現在的 SharedArrayBuffer
舊式數組 vs 類型化數組:性能
前面已經討論了 JavaScript 數組的演進,現在來測試現代數組到底能帶來多大收益。
 舊式數組和 ArrayBuffer 的性能不相上下,現代編譯器已經智能化,能夠將元素類型相同的傳統數組在內部轉換成內存連續的數組。第一個例子正是如此。盡管使用了 new Array(LIMIT),數組實際依然以現代數組形式存在。
接著修改第一例子,將數組改成異構型(元素類型不完全一致)的,來看看是否存在性能差異。
 改變發生在第 3 行,添加一條語句,將數組變為異構類型。其余代碼保持不變。性能差異表現出來了,慢了 22 倍。
舊式數組:讀取
 類型化數組的引入是 JavaScript 發展歷程中的一大步。Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,這些是類型化數組視圖,使用原生字節序(與本機相同)。我們還可以使用 DataView 創建自定義視圖窗口。希望未來會有更多幫助我們輕松操作 ArrayBuffer 的 DataView 庫。
JavaScript 數組的演進非常 nice。現在它們速度快、效率高、健壯,在內存分配時也足夠智能。
0 篇文章
如果覺得我的文章對您有用,請隨意打賞。你的支持將鼓勵我繼續創作!