如何在前端瀏覽器用JavaScript處理過長的字串變數?活用虛擬DOM作為快取 / How to use JavaScript to deal with long string variables in the front-end browser? Use Virtual DOM as cache
我會在網頁上使用SheetJS js-xlsx讀取ODS試算表檔案的內容,
研究之後,我想到可以將字串建立成虛擬DOM上的節點,
以下我用兩個實際上的網頁作為例子,
系統環境 / Environment
這個實驗用的電腦環境是Windows 7的64位元版本,CPU是Intel Core i7 3770,記憶體是DDR3 16GB。
如果你的硬體設備不一樣,而你瀏覽器不會當機的話,
瀏覽器當掉:沒有緩存的過長字串 / Browser crashed: Long string variables with no cache
這個網頁裡面的JavaScript會宣告一個out字串變數,然後加入347812747次「-」,那個「10%」就是現在加到幾次。為了避免整個畫面定住不能操作,我還加入了非同步的設計,但這跟這次的主題沒有直接關係。
我刪掉不相關的程式碼之後,主要程式碼如下:
var main = async function () {
console.log('start')
var out = ''
var limit = 347812747
for (var i = 0; i < limit; i++) {
out += '-'
}
document.write('Finish: ' + out.length)
}
按下「START」按鈕,網頁就會開始運作。當這個迴圈處理到一半左右,瀏覽器就會因為「Out of memory」(記憶體不足)而當機。整個流程動畫如下:
這可能是「out += '-'」導致的錯誤。我們也許不能在單一變數裡面加入太長的字串。
我試過拆開成多個變數,或是把它存成陣列,一樣是不能順利跑完迴圈,跑到一半就會當掉。後來我才想到,我們還有DOM可以拿來做快取呢。
成功處理過長字串:使用虛擬DOM緩存 / Deal with long string variables with virtual DOM cache
這個網頁的JavaScript跟前面做的事情差不多,差別在於,迴圈每10000000次的時候,就會將字串內容轉存到新的網頁節點<textarea>裡面,並擺在網頁<body>的最後面隱藏起來。主要程式碼如下,我用紅字標示跟前面程式碼主要差別的語法:
var main = async function () {
var out = ''
var cache
var cacheList = []
var limit = 347812747
for (var i = 0; i < limit; i++) {
if (i % 10000000 === 0 && out !== '') {
cache = document.createElement('textarea')
cache.value = out
cache.style.display = 'none'
cacheList.push(cache)
out = ''
}
out += '-'
}
var tempOut = out
out = ''
for (i = 0; i < cacheList.length; i++) {
out += cacheList[i].value
cacheList[i].remove()
}
out += tempOut
document.write('Finish: ' + out.length)
}
像是<textarea>這種HTML元素是網頁上的文件物件模型(Document Object Model, DOM)。對Google Chrome來說,處理DOM的記憶體和處理JavaScript的記憶體似乎是分開處理。將JavaScript中過長的字串變數改為DOM之後,就不會發生記憶體不足的情況。除了使用<textarea>之外,也可以使用<div>來保存資料,似乎是一樣的效果。
值得注意的是,我雖然宣告了<textarea>元素,但並沒有把它寫入到網頁上,而是讓它保持虛擬DOM (Virtual DOM)的狀態。反之,如果把它寫入到網頁上,網頁效率會被大量拖慢,甚至會讓瀏覽器看起來像是要當機一樣。所以還是讓它維持虛擬DOM就好。
接下來讓我們來看看整個操作流程。跟前面的程式碼一樣,按下「START」按鈕,網頁就會開始運作。整個操作流程如下面的錄影,最後看到「Finish: 347812747」就表示程式跑完了。
為什麼最後只顯示out的字串長度呢?我試過把out的內容用document.write(out)寫在網頁上,但這會導致網頁當機。這似乎也是表示一次處理過長的字串時會出現問題的意思。不過這個out變數在其他JavaScript程式碼中是可以正常使用,像是我可以用out.length來取得它的字串長度。
結語 / Conclusion
這篇文章說明了在網頁使用JavaScript處理過長字串中,使用虛擬DOM來做快取來避免記憶體不足、瀏覽器當機的情況。經過這次經驗,我才真正感受到現在主流的JavaScript技術採用虛擬DOM (Virtual DOM)的理由。出乎意料之外的好用。
其他技巧:非同步的睡眠 / sleep()
這篇除了用虛擬DOM來做緩存之外,我還在迴圈執行過程中加入了sleep(),使網頁不會因為迴圈執行過久導致網頁畫面結凍、被使用者誤認為當機。這個技巧在「JavaScript async/await 的奇淫技巧」這篇裡面也有講到,該篇將它命名為「delay()」。
我宣告的sleep()程式碼如下:
function sleep () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(true)
}, 10)
})
}
使用的語法如下:
await sleep()
因為這裡用到了async/await,你程式碼裡面的函式要記得改成非同步的async,而這個語法在Internet Explorer裡面無法運作之外,其他主流瀏覽器都能正常運作。關於瀏覽器相容性,請看MDN web docs的說明。
那麼這次關於過長字串的處理方式就到這裡了。寫到最後,我有些問題想問問大家:
- 你有遇到過JavaScript程式碼導致瀏覽器記憶體不足的情況嗎?那是什麼情況呢?
- 遇到上述狀況時,你都是怎麽處理的呢?
歡迎在下面的留言處跟我們分享你的想法。大家的意見是我繼續分享的動力喔!如果你覺得我這篇實用的話,請幫我在AddThis分享工具按讚、將這篇分享到Facebook等社群媒體吧!
感謝你的耐心閱讀,我是布丁,讓我們下一篇見。