啊啊啊今天我是來認錯的 ^_^
首先,昨天用 localStorage 保存翻譯資料的程式碼,
其實有個地方寫錯了 ^_^
其實也不是寫錯了,
而是採用了一種很沒效率的做法。
一開始還沒發現,
但後來嘗試六、七千句的 HTML 時,
(比如整本 epub 電子書轉成 html,就會有這麼多句子)
程式就掛掉了。
Debug 的時候才發現,
原來昨天在保存整頁的原文、譯文時,
是採用一句一句保存的做法:
// 備份各句原始文字
document.querySelectorAll("sent").forEach((node, i)=>{
orig_htmls[i] = node.innerHTML
...
// 保存到 localStorage
...
localStorage.setItem('orig_htmls', JSON.stringify(orig_htmls))
...
}
像這種保存資料的動作,通常都比較耗時。
如果只有幾十句、上百句,
做幾十次、上百次耗時操作可能還沒感覺,
但做六、七千次就會出問題了。
在編寫程式時,
對於各種操作耗時的程度,
都應該要有點概念才行。
否則很容易寫出沒效率的程式。
J. Dean 在 2010 年列出了
每個程式設計師都應該知道的幾個耗時數字,
這些數字隨著各種技術演進,
也在持續不斷進化。
各位有興趣可以參考一下 ^_^
至於這裡理想的做法,
應該是把耗時的動作拉到迴圈外面,
在迴圈內先把六、七千筆資料收集好了,
到了迴圈外面,再做一次耗時的資料保存動作,
這樣就只會耗時一次,動作肯定快多了。
// 備份各句原始文字
document.querySelectorAll("sent").forEach((node, i)=>{
orig_htmls[i] = node.innerHTML
...
}
// 保存到 localStorage
...
localStorage.setItem('orig_htmls', JSON.stringify(orig_htmls))
...
像這樣只是把程式碼換了個位置,
程式效能就大大提升了!
之前的做法,就像是怕事的小員工,
每做一個小動作,就要去問一下老闆的意見。
這樣不但老闆會煩死,小員工自己也會累死。
理想的做法,應該是老闆放手讓小員工去發揮,
做到一定程度再向老闆回報。
這樣的話,小員工得到充分授權才能自由揮灑,
老闆也不必緊迫盯人,
只需適時瞭解小員工的工作進展即可。
在資料庫的領域中,
這就有點像所謂 transaction 的概念。
太細的工作,不要老是去煩資料庫,
應該把工作打包完成到一定的程度,
再整批交給資料庫去處理。
說到這個 transaction,我多說兩句好了。
這個字經常被翻譯成「交易」,
但我一看到這個翻譯就難過,
究竟是交易什麼呢?
交易的感覺是雙向的,有點交換的感覺,
與其這樣翻譯,好像不如採用「交接」的譯法。
一邊交出去,另一邊接起來,
應該比較接近所要描述的情況。
另外也有人把它翻譯成「事務」,
應該是取其名詞之意,
但「事務」這兩個字又太籠統,
看到中文很難聯想到 transaction 這個原文。
如果非要翻譯成名詞,
「交接事務」或許是個不錯的譯法。
話說回來,改正這個問題之後,
我還要再認第二個錯。。。 ^_^
之前我在使用 localStorage 時,
看到後台裡有個 Web SQL 可用,
或許是太忙太累一時之間昏了頭,
就想說可以的話來研究一下,
做為鐵人賽其中一天的主題。
但後來在維基百科和 W3C 的網頁中發現,
原來這個技術已經在 2010 年停止發展了!!
啊啊啊啊啊——
那還要介紹嗎?
這樣吧,請先讓我整理一下昨天的程式碼,
然後再讓我想一想吧。。。 ^_^
我想先把資料儲存相關的程式碼獨立出來。
在 js 子目錄下,建立一個 storage.js,
然後再把儲存相關的工作集中在此處。
當然,我們也要在 manifest.json 裡,
宣告一下這個檔案。
"js": [
"js/storage.js",
...
"js/content.js"
]
接著就可以在 storage.js 裡,
編寫資料儲存相關的函式了。
我們可以先寫兩個函式,
把昨天的一些重複程式碼整合一下。
function getValueByPath(key, path, default_value = null) {
var item = JSON.parse(localStorage.getItem(key))
return (item && path in item) ? item[path]:default_value
}
function upsertValueByPath(key, path, value) {
var value_in_storage = JSON.parse(localStorage.getItem(key))
value_in_storage = (value_in_storage) ? value_in_storage : {};
value_in_storage[path] = value
localStorage.setItem(key, JSON.stringify(value_in_storage))
}
有了這兩個函式,
昨天的程式碼就會簡潔許多,
而且如果需要修改不同的做法,
也只要修改一處即可各處通用。
像這樣的整合,還有一個額外的好處,
那就是可以協助找出程式碼中考慮未盡周詳之處。
比如說,昨天我為了能讓編輯過程可以全鍵盤操作,
特別用 localStorage 記錄了最後修改翻譯的 sent id。
// 記錄之前最後一次修改翻譯內容的 sent id
localStorage.setItem('prev_sent_id', prev_sent_id)
...
// 取回之前最後一次修改翻譯內容的 sent id
prev_sent_id = localStorage.getItem('prev_sent_id')
所謂的全鍵盤操作,
也就是除了用【Ctrl+Alt+點擊】之外,
還能使用【Ctrl+Enter】、【Ctrl+上】、【Ctrl+下】,
直接進入編輯界面。
另外,今天也添加了一個新的快速鍵【Esc】,
在編輯翻譯內容時,可以取消修改,退出編輯界面。
這些與之前提到的 UX 使用者體驗,都是很有關係的。
但今天整合重複程式碼時就發現,
當初記錄這個 sent id 時,並未像其他資料一樣,
採用網址的 path 來做為索引。
如此一來,相同網頁就會使用到同一個 sent id,
這顯然不是我們想要的行為。
因此,今天在整合程式碼時,
這個問題也就順手一併處理掉了。
// 記錄之前最後一次修改翻譯內容的 sent id
upsertValueByPath('prev_sent_id_by_path', path, prev_sent_id)
...
// 取回之前最後一次修改翻譯內容的 sent id
prev_sent_id = getValueByPath('prev_sent_id_by_path', path, 'sent_0')
啊啊啊以上這些都是還不錯的程式碼改進做法。
至於那 Web SQL,
嗯。經過一整個晚上的測試,
我決定果斷放棄 WEB SQL了!!
也許是因為它長期不再發展,
目前使用起來感覺到處都是坑,
現在與其花時間去補這些坑,
不如去學習更好玩更有用的東西。
本來我想藉由它,介紹 SQL 的一些觀念和做法,
但現在姑且就此打住,
之後介紹服務端的時候,應該還有機會再介紹 SQL。
話說回來,今天在這坑上摔了一跤,
也算是一個經驗與警惕。
希望也可以做為各位的參考囉。
那麼,今天就先這樣囉。