原文:THE PAST, PRESENT & FUTURE OF LOCAL STORAGE FOR WEB APPLICATIONS
說明:因個人水平有限,如有誤譯,歡迎指正。
原生的本地應(yīng)用相比于 Web 應(yīng)用在持久化本地存儲上具有一定的優(yōu)勢。對于本地應(yīng)用,操作系統(tǒng)通常會為存儲提供一個抽象層并用于獲取與應(yīng)用相關(guān)的數(shù)據(jù),比如,用戶偏好或者運行時狀態(tài)。這些值可能存儲在注冊表,INI 文件,XML 文件,或其他取決于平臺約定的地方。如果您的原生客戶端應(yīng)用需要除鍵值對外的本地存儲,那么您可以嵌入一個自己的數(shù)據(jù)庫,設(shè)計自己的文件格式,等多種解決方案。
因為歷史原因,Web 應(yīng)用沒有這些看起來奢侈的功能。早期發(fā)明了 Cookie,而且它們確實被用于少量數(shù)據(jù)的持久化存儲。但是它們存在以下三種潛在的缺陷:
- Cookie 會包含在所有的 HTTP 請求中,因此在不停傳輸這些數(shù)據(jù)的情況下降低了 Web 應(yīng)用的響應(yīng)速度。
- 因為 Cookie 包含在了每一個 HTTP 請求中,因此在因特網(wǎng)上傳輸?shù)臄?shù)據(jù)都是未加密的(除非您的整個應(yīng)用使用了 SSL)。
- Cookie 中存儲的數(shù)據(jù)量不得超過 4 KB——這已經(jīng)足夠降低您的應(yīng)用速度了(參見上面),但是離好用還有很多差距。
我們真正想要的卻是:
- 更大的存儲空間
- 存在于客戶端
- 持久化與頁面刷新無關(guān)
- 不會向服務(wù)器端傳輸
在 HTML5 之前,想要實現(xiàn)此目的的所有嘗試都不盡如人意。
HTML5 前 hack 本地存儲的簡短歷史
在一開始,我們只能夠使用 Internet Explorer 瀏覽器?;蛘哒f,這就是微軟所想的。后來,在 第一次瀏覽器大戰(zhàn) 中,Microsoft 發(fā)明了很多東西并將它們裝進(jìn)了他們的瀏覽器,因此,Internet Explorer 贏得了這次戰(zhàn)爭。這當(dāng)中,有一個東西叫做 DHTML 行為,其中有一個行為就是 userData。
userData 允許每個域名下面的頁面可以存儲 64 KB 的數(shù)據(jù),使用的是基于 XML 具有層級的結(jié)構(gòu)。(信任的域名,比如 intrant 站點,更能存儲 10 倍這樣的數(shù)據(jù)。640 KB 對于任何人來說可以說是足夠了。)IE 沒有展示任何權(quán)限對話框,也不能增加額外的存儲空間。
2002 年,Adobe 在 Flash 6 中引入了一個特性,但不幸的是,取了一個具有誤導(dǎo)性的名稱“Flash cookies”。在 Flash 的環(huán)境中,這個特性被稱為本地共享對象。簡言之,它允許 Flash 對象在每個域名下可以存儲 100 KB 的數(shù)據(jù)。Brad Neuberg 開發(fā)了一個名為 AMSSS (AJAX Massive Storage System) 的 Flash-JavaScript 早期原型,但是它卻受限于 Flash 的某些設(shè)計怪異行為。到 2006 年,隨著 Flash 8 ExternalInterface 的發(fā)明,可以使用 JavaScript 訪問 LSO(本地共享對象)了,不僅更加容易,而且數(shù)度也提升了一個量級。Brad 重寫了 AMASS 并將其集成進(jìn)了流行的 Dojo Toolkit 的 moiker dojox.storage 中。Flash 為每個域名提供了“免費的” 100 KB 存儲空間,如果超出,則允許按數(shù)量級增加(1 MB,10 MB,等等)。
2007 年,Google 正式推出 Gears,一個開源的瀏覽器插件,致力于在瀏覽器中提供額外的兼容性。Gears 提供了一個基于 SQLite 的嵌入式 SQL 數(shù)據(jù)庫接口。到 2010 年時,Google 將其重心轉(zhuǎn)移到將所有 Gears 的兼容性帶入到類似 HTML5 的 Web 標(biāo)準(zhǔn)中,也正因此,Google Gears 最終終止開發(fā)。
與此同時,Brad Neuberg 和其他人持續(xù) hack dojox.storage,以對所有這些不同的插件和 API 提供一個統(tǒng)一的接口。到 2009 年時,dojox.storage 能夠自動檢測(并在此之上提供一個統(tǒng)一的接口)Adobe Flash,Gears,Adobe AIR,以及一個早期的 HTML5 存儲原型,它只在老的 Firefox 中實現(xiàn)。
當(dāng)您縱覽這些解決方案的時候,就會發(fā)現(xiàn)一種模式:它們不是特定于某個瀏覽器,就是依賴于一個第三方插件。盡管也有史詩級的努力來掩蓋這些不同(dojox.storage),但是它們暴露了相當(dāng)不同的接口,擁有不一樣的存儲限制以及展示了不同的用戶體驗。所以,這就是 HTML5 著手要去解決的問題:提供一個標(biāo)準(zhǔn)化的 API,原生實現(xiàn)并在多種瀏覽器上表現(xiàn)一致,且不依賴于第三方插件。
HTML5 存儲介紹
我所指的“HTML5 存儲(HTML5 Storage)”就是名為 Web Storage 的規(guī)范,它曾是 HTML5 規(guī)范的一部分,但是因為某些無聊的政治因素而被獨立了出去。某些瀏覽器提供商也將其稱之為“Local Storage”或“DOM Storage”。由于某些相關(guān)的,相似的命名,新興的標(biāo)準(zhǔn),命名的情況變得更加復(fù)雜,我在后面將會對其討論。
那么,什么是 HTML5 存儲?簡單來說,它是一種 Web 頁面在本地存儲命名的鍵值對方式,并存在于客戶端 Web 瀏覽器中。和 Cookie 一樣,這個數(shù)據(jù)在您導(dǎo)航到其它 Web 站點,關(guān)閉瀏覽器選項卡,退出瀏覽器等情況下依舊被持久化。不同于 Cookie 的是,這個數(shù)據(jù)從不會發(fā)送到遠(yuǎn)程 Web 服務(wù)器(除非您手動發(fā)送它)。不像之前所有的提供持久化本地存儲的嘗試,它是在 Web 瀏覽器中原生實現(xiàn)的,所以即使在沒有第三方插件的情況下,它依舊可用。
哪些瀏覽器支持?每個瀏覽器的最新版本都支持 HTML5 存儲...即使是 Internet Explorer!Can I use。
在您的 JavaScript 代碼中,您可以通過 window 全局對象上的 localStorage 對象訪問 HTML5 存儲,在使用前應(yīng)該先 偵測瀏覽器是否支持它。
function supports_html5_storage() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
除了自己編寫檢測函數(shù)外,您還可以使用 Modernizr 來檢測 HTML5 存儲的支持情況。
if (Modernizr.localstorage) {
// window.localStorage is available!
} else {
// no native support for HTML5 storage :(
// maybe try dojox.storage or a third-party solution
}
使用 HTML5 存儲
HTML5 存儲是基于命名的鍵值對。您存儲的數(shù)據(jù)是要有一個命名的鍵,然后您就可以使用這個鍵來獲取數(shù)據(jù)。這個命名的鍵是一個字符串。而數(shù)據(jù)可以是任何 JavaScript 支持的數(shù)據(jù)類型,包括字符串,布爾,整型,或浮點型數(shù)據(jù)。然而,這個數(shù)據(jù)卻是以字符串的形式存儲的。如果您想要存儲和獲取除字符串外的其它數(shù)據(jù)類型,您將要使用類似 parseInt() 或 parseFloat() 這樣的函數(shù)來對獲取的數(shù)據(jù)執(zhí)行強(qiáng)制轉(zhuǎn)化,以得到您期望的數(shù)據(jù)類型。
interface Storage {
getter any getItem(in DOMString key);
setter creator void setItem(in DOMString key, in any data);
};
調(diào)用 setItem() 時如果使用了一個已經(jīng)存在的命名鍵,那么將會在沒有通知的情況下覆蓋之前的值。調(diào)用 getItem() 時如果使用的是一個不存在的鍵將會返回 null 而不會拋出異常。
像其它的 JavaScript 對象一樣,您可以將 localStorage 對象看作為一個關(guān)聯(lián)型數(shù)據(jù),除了使用 getItem() 和 setItem() 方法,您還可以簡單地使用方括號。舉個例子:
var foo = localStorage.getItem("bar");
// ...
localStorage.setItem("bar", foo);
…也可以使用方括號句法重寫:
var foo = localStorage["bar"];
// ...
localStorage["bar"] = foo;
也有根據(jù)一個給定的命名鍵刪除值和清除整個存儲區(qū)域的方法(也就是一次性刪除整個鍵和值)。
interface Storage {
deleter void removeItem(in DOMString key);
void clear();
};
調(diào)用 removeItem() 時如果使用的是一個不存在的鍵將不會做任何事。
最后,有一個用于獲取整個存儲區(qū)域值數(shù)量的屬性,可以通過索引迭代整個鍵(獲取每一個鍵的名稱)。
interface Storage {
readonly attribute unsigned long length;
getter DOMString key(in unsigned long index);
};
如果在訪問 key() 的時候使用了不在 0 - (length-1) 之間的索引號,那么這個函數(shù)將返回 null。