Javascrip—前端本地儲存講解(16)

it智雲程式設計發表於2018-09-28

三種本地儲存方式

cookie

前言

網路早期最大的問題之一是如何管理狀態。簡而言之,伺服器無法知道兩個請求是否來自同一個瀏覽器。當時最簡單的方法是在請求時,在頁面中插入一些引數,並在下一個請求中傳回引數。這需要使用包含引數的隱藏的表單,或者作為URL引數的一部分傳遞。這兩個解決方案都手動操作,容易出錯。cookie出現來解決這個問題。

作用

cookie是純文字,沒有可執行程式碼。儲存資料,當使用者訪問了某個網站(網頁)的時候,我們就可以通過cookie來向訪問者電腦上儲存資料,或者某些網站為了辨別使用者身份、進行session跟蹤而儲存在使用者本地終端上的資料(通常經過加密)

**如何工作 ** 當網頁要發http請求時,瀏覽器會先檢查是否有相應的cookie,有則自動新增在request header中的cookie欄位中。這些是瀏覽器自動幫我們做的,而且每一次http請求瀏覽器都會自動幫我們做。這個特點很重要,因為這關係到“什麼樣的資料適合儲存在cookie中”。

儲存在cookie中的資料,每次都會被瀏覽器自動放在http請求中,如果這些資料並不是每個請求都需要發給服務端的資料,瀏覽器這設定自動處理無疑增加了網路開銷;但如果這些資料是每個請求都需要發給服務端的資料(比如身份認證資訊),瀏覽器這設定自動處理就大大免去了重複新增操作。所以對於那種設定“每次請求都要攜帶的資訊(最典型的就是身份認證資訊)”就特別適合放在cookie中,其他型別的資料就不適合了。

特徵

不同的瀏覽器存放的cookie位置不一樣,也是不能通用的。 cookie的儲存是以域名形式進行區分的,不同的域下儲存的cookie是獨立的。 我們可以設定cookie生效的域(當前設定cookie所在域的子域),也就是說,我們能夠操作的cookie是當前域以及當前域下的所有子域 一個域名下存放的cookie的個數是有限制的,不同的瀏覽器存放的個數不一樣,一般為20個。 每個cookie存放的內容大小也是有限制的,不同的瀏覽器存放大小不一樣,一般為4KB。 cookie也可以設定過期的時間,預設是會話結束的時候,當時間到期自動銷燬 cookie值既可以設定,也可以讀取。

設定

客戶端設定

document.cookie = '名字=值';
document.cookie = 'username=cfangxu;domain=baike.baidu.com'    並且設定了生效域
複製程式碼

注意: 客戶端可以設定cookie 的下列選項:expires、domain、path、secure(有條件:只有在https協議的網頁中,客戶端設定secure型別的 cookie 才能成功),但無法設定HttpOnly選項。

伺服器端設定

不管你是請求一個資原始檔(如 html/js/css/圖片),還是傳送一個ajax請求,服務端都會返回response。而response header中有一項叫set-cookie,是服務端專門用來設定cookie的。

Set-Cookie 訊息頭是一個字串,其格式如下(中括號中的部分是可選的):
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]

複製程式碼

注意: 一個set-Cookie欄位只能設定一個cookie,當你要想設定多個 cookie,需要新增同樣多的set-Cookie欄位。 服務端可以設定cookie 的所有選項:expires、domain、path、secure、HttpOnly 通過 Set-Cookie 指定的這些可選項只會在瀏覽器端使用,而不會被髮送至伺服器端。

讀取

我們通過document.cookie來獲取當前網站下的cookie的時候,得到的字串形式的值,它包含了當前網站下所有的cookie(為避免跨域指令碼(xss)攻擊,這個方法只能獲取非 HttpOnly 型別的cookie)。它會把所有的cookie通過一個分號+空格的形式串聯起來,例如username=chenfangxu; job=coding

修改 cookie

要想修改一個cookie,只需要重新賦值就行,舊的值會被新的值覆蓋。但要注意一點,在設定新cookie時,path/domain這幾個選項一定要舊cookie 保持一樣。否則不會修改舊值,而是新增了一個新的 cookie。

刪除

把要刪除的cookie的過期時間設定成已過去的時間,path/domain/這幾個選項一定要舊cookie 保持一樣。

注意

如果只設定一個值,那麼算cookie中的value; 設定的兩個cookie,key值如果設定的相同,下面的也會把上面的覆蓋。

cookie的屬性(可選項)

過期時間

如果我們想長時間存放一個cookie。需要在設定這個cookie的時候同時給他設定一個過期的時間。如果不設定,cookie預設是臨時儲存的,當瀏覽器關閉程式的時候自動銷燬

注意:document.cookie = '名稱=值;expires=' + GMT(格林威治時間)格式的日期型字串;
複製程式碼

一般設定天數:new Date().setDate( oDate.getDate() + 5 ); 比當前時間多5天

一個設定cookie時效性的例子

function setCookie(c_name, value, expiredays){
    var exdate=new Date();
    exdate.setDate(exdate.getDate() + expiredays);
    document.cookie=c_name+ "=" + escape(value) + ((expiredays==null) ? "" : ";expires="+exdate.toGMTString())
}
使用方法:setCookie('username','cfangxu',30)
複製程式碼

expires 是 http/1.0協議中的選項,在新的http/1.1協議中expires已經由 max-age 選項代替,兩者的作用都是限制cookie 的有效時間。expires的值是一個時間點(cookie失效時刻= expires),而max-age 的值是一個以秒為單位時間段(cookie失效時刻= 建立時刻+ max-age)。
另外,max-age 的預設值是 -1(即有效期為 session );max-age有三種可能值:負數、0、正數。
負數:有效期session;
0:刪除cookie;
正數:有效期為建立時刻+ max-age

cookie的域概念(domain選項)

domain指定了 cookie 將要被髮送至哪個或哪些域中。預設情況下,domain 會被設定為建立該 cookie 的頁面所在的域名,所以當給相同域名傳送請求時該 cookie 會被髮送至伺服器。

瀏覽器會把 domain 的值與請求的域名做一個尾部比較(即從字串的尾部開始比較),並將匹配的 cookie 傳送至伺服器。

客戶端設定

document.cookie = "username=cfangxu;path=/;domain=qq.com" 如上:“www.qq.com" 與 "sports.qq.com" 公用一個關聯的域名"qq.com",我們如果想讓 "sports.qq.com" 下的cookie被 "www.qq.com" 訪問,我們就需要用到 cookie 的domain屬性,並且需要把path屬性設定為 "/"。

服務端設定 Set-Cookie: username=cfangxu;path=/;domain=qq.com 注:一定的是同域之間的訪問,不能把domain的值設定成非主域的域名。

cookie的路徑概念(path選項) cookie 一般都是由於使用者訪問頁面而被建立的,可是並不是只有在建立 cookie 的頁面才可以訪問這個 cookie。 因為安全方面的考慮,預設情況下,只有與建立 cookie 的頁面在同一個目錄或子目錄下的網頁才可以訪問。 即path屬性可以為伺服器特定文件指定cookie,這個屬性設定的url且帶有這個字首的url路徑都是有效的。

客戶端設定

最常用的例子就是讓 cookie 在根目錄下,這樣不管是哪個子頁面建立的 cookie,所有的頁面都可以訪問到了。

document.cookie = "username=cfangxu; path=/"

服務端設定

Set-Cookie:name=cfangxu; path=/blog

如上設定:path 選項值會與 /blog,/blogrool 等等相匹配;任何以 /blog 開頭的選項都是合法的。需要注意的是,只有在 domain 選項核實完畢之後才會對 path 屬性進行比較。path 屬性的預設值是傳送 Set-Cookie 訊息頭所對應的 URL 中的 path 部分。

domain和path總結:

domain是域名,path是路徑,兩者加起來就構成了 URL,domain和path一起來限制 cookie 能被哪些 URL 訪問。 所以domain和path2個選項共同決定了cookie何時被瀏覽器自動新增到請求頭部中傳送出去。如果沒有設定這兩個選項,則會使用預設值。domain的預設值為設定該cookie的網頁所在的域名,path預設值為設定該cookie的網頁所在的目錄。

cookie的安全性(secure選項) 通常 cookie 資訊都是使用HTTP連線傳遞資料,這種傳遞方式很容易被檢視,所以 cookie 儲存的資訊容易被竊取。假如 cookie 中所傳遞的內容比較重要,那麼就要求使用加密的資料傳輸。

secure選項用來設定cookie只在確保安全的請求中才會傳送。當請求是HTTPS或者其他安全協議時,包含 secure 選項的 cookie 才能被髮送至伺服器。

document.cookie = "username=cfangxu; secure"

把cookie設定為secure,只保證 cookie 與伺服器之間的資料傳輸過程加密,而儲存在本地的 cookie檔案並不加密。就算設定了secure 屬性也並不代表他人不能看到你機器本地儲存的 cookie 資訊。機密且敏感的資訊絕不應該在 cookie 中儲存或傳輸,因為 cookie 的整個機制原本都是不安全的

注意:如果想在客戶端即網頁中通過 js 去設定secure型別的 cookie,必須保證網頁是https協議的。在http協議的網頁中是無法設定secure型別cookie的。

httpOnly 這個選項用來設定cookie是否能通過 js 去訪問。預設情況下,cookie不會帶httpOnly選項(即為空),所以預設情況下,客戶端是可以通過js程式碼去訪問(包括讀取、修改、刪除等)這個cookie的。當cookie帶httpOnly選項時,客戶端則無法通過js程式碼去訪問(包括讀取、修改、刪除等)這個cookie。

在客戶端是不能通過js程式碼去設定一個httpOnly型別的cookie的,這種型別的cookie只能通過服務端來設定。

cookie的編碼 cookie其實是個字串,但這個字串中等號、分號、空格被當做了特殊符號。所以當cookie的 key 和 value 中含有這3個特殊字元時,需要對其進行額外編碼,一般會用escape進行編碼,讀取時用unescape進行解碼;當然也可以用encodeURIComponent/decodeURIComponent或者encodeURI/decodeURI,檢視關於編碼的介紹

第三方cookie 通常cookie的域和瀏覽器地址的域匹配,這被稱為第一方cookie。那麼第三方cookie就是cookie的域和位址列中的域不匹配,這種cookie通常被用在第三方廣告網站。為了跟蹤使用者的瀏覽記錄,並且根據收集的使用者的瀏覽習慣,給使用者推送相關的廣告。 關於第三方cookie和cookie的安全問題可以檢視https://mp.weixin.qq.com/s/oOGIuJCplPVW3BuIx9tNQg

localStorage(本地儲存) HTML5新方法,不過IE8及以上瀏覽器都相容。

特點

生命週期:持久化的本地儲存,除非主動刪除資料,否則資料是永遠不會過期的。 儲存的資訊在同一域中是共享的。 當本頁操作(新增、修改、刪除)了localStorage的時候,本頁面不會觸發storage事件,但是別的頁面會觸發storage事件。 大小:據說是5M(跟瀏覽器廠商有關係) 在非IE下的瀏覽中可以本地開啟。IE瀏覽器要在伺服器中開啟。 localStorage本質上是對字串的讀取,如果儲存內容多的話會消耗記憶體空間,會導致頁面變卡 localStorage受同源策略的限制 設定 localStorage.setItem('username','cfangxu');

獲取 localStorage.getItem('username') 也可以獲取鍵名 localStorage.key(0) #獲取第一個鍵名

刪除 localStorage.removeItem('username') 也可以一次性清除所有儲存 localStorage.clear()

storage事件 當storage發生改變的時候觸發。

注意: 當前頁面對storage的操作會觸發其他頁面的storage事件 事件的回撥函式中有一個引數event,是一個StorageEvent物件,提供了一些實用的屬性,如下表:

image
sessionStorage 其實跟localStorage差不多,也是本地儲存,會話本地儲存

特點: 用於本地儲存一個會話(session)中的資料,這些資料只有在同一個會話中的頁面才能訪問並且當會話結束後資料也隨之銷燬。因此sessionStorage不是一種持久化的本地儲存,僅僅是會話級別的儲存。也就是說只要這個瀏覽器視窗沒有關閉,即使重新整理頁面或進入同源另一頁面,資料仍然存在。關閉視窗後,sessionStorage即被銷燬,或者在新視窗開啟同源的另一個頁面,sessionStorage也是沒有的。 cookie、localStorage、sessionStorage區別 相同:在本地(瀏覽器端)儲存資料 不同: localStorage、sessionStorage

localStorage只要在相同的協議、相同的主機名、相同的埠下,就能讀取/修改到同一份localStorage資料。

sessionStorage比localStorage更嚴苛一點,除了協議、主機名、埠外,還要求在同一視窗(也就是瀏覽器的標籤頁)下。

localStorage是永久儲存,除非手動刪除。

sessionStorage當會話結束(當前頁面關閉的時候,自動銷燬)

cookie的資料會在每一次傳送http請求的時候,同時傳送給伺服器而localStorage、sessionStorage不會。

擴充套件其他的前端儲存方式(不常用) web SQL database 先說個會被取代的,為什麼會被取代,主要有以下幾個原因:

W3C捨棄 Web SQL database草案,而且是在2010年年底,規範不支援了,瀏覽器廠商已經支援的就支援了,沒有支援的也不打算支援了,比如IE和Firefox。

為什麼要捨棄?因為 Web SQL database 本質上是一個關係型資料庫,後端可能熟悉,但是前端就有很多不熟悉了,雖然SQL的簡單操作不難,但是也得需要學習。

SQL熟悉後,真實操作中還得把你要儲存的東西,比如物件,轉成SQL語句,也挺麻煩的。

indexedDB

來自MDN的解釋: indexedDB 是一種低階API,用於客戶端儲存大量結構化資料(包括, 檔案/ blobs)。該API使用索引來實現對該資料的高效能搜尋。雖然 Web Storage 對於儲存較少量的資料很有用,但對於儲存更大量的結構化資料來說,這種方法不太有用。IndexedDB提供了一個解決方案。 所以,IndexedDB API是強大的,但對於簡單的情況可能看起來太複雜了,所以要看你的業務場景來選擇到底是用還是不用。

indexedDB 是一個基於JavaScript的物件導向的資料庫。 IndexedDB允許你儲存和檢索用鍵索引的物件;

IndexedDB 鼓勵使用的基本模式如下所示:

開啟資料庫並且開始一個事務。 建立一個 object store。 構建一個請求來執行一些資料庫操作,像增加或提取資料等。 通過監聽正確型別的 DOM 事件以等待操作完成。

在操作結果上進行一些操作(可以在 request 物件中找到)

1、首先開啟indexedDB資料庫 語法:

var db;
// 開啟資料庫,open還有第二個引數版本號
var request = window.indexedDB.open('myTestDatabase');
// 資料庫開啟成功後
request.onsuccess = function (event) {
    // 儲存資料結果,後面所有的資料庫操作都離不開它。
    db = request.result;
}
request.onerror = function (event) {
    alert("Why didn't you allow my web app to use IndexedDB?!");
}

// 資料庫首次建立版本,或者window.indexedDB.open傳遞的新版本(版本數值要比現在的高)
request.onupgradeneeded = function (event) {

}
複製程式碼

onupgradeneeded事件: 更新資料庫的 schema,也就是建立或者刪除物件儲存空間,這個事件將會作為一個允許你處理物件儲存空間的 versionchange 事務的一部分被呼叫。在資料庫第一次被開啟時或者當指定的版本號高於當前被持久化的資料庫的版本號時,這個 versionchange 事務將被建立。onupgradeneeded 是我們唯一可以修改資料庫結構的地方。在這裡面,我們可以建立和刪除物件儲存空間以及構建和刪除索引。

2、構建資料庫

IndexedDB 使用物件儲存空間而不是表,並且一個單獨的資料庫可以包含任意數量的物件儲存空間。每當一個值被儲存進一個物件儲存空間時,它會被和一個鍵相關聯。

// 資料庫首次建立版本,或者window.indexedDB.open傳遞的新版本(版本數值要比現在的高)
  request.onupgradeneeded = function (event) {

      //之前我們們不是在success中得到了db了麼,為什麼還要在這獲取,
      //因為在當前事件函式執行後才會去執行success事件
      var db = event.target.result;

      // 建立一個物件儲存空間,keyPath是id,keyGenerator是自增的
      var objectStore = db.createObjectStore('testItem',{keyPath: 'id',autoIncrement: true});
      // 建立一個索引來通過id搜尋,id是自增的,不會有重複,所以可以用唯一索引
      objectStore.createIndex('id','id',{unique: true})

      objectStore.createIndex('name','name');
      objectStore.createIndex('age','age');

      //新增一條資訊道資料庫中
      objectStore.add({name: 'cfangxu', age: '27'});

  }
複製程式碼
bjectStore.createIndex(indexName, keyPath, objectParameters)

indexName:建立的索引名稱,可以使用空名稱作為索引。
keyPath:索引使用的關鍵路徑,可以使用空的keyPath, 或者keyPath傳為陣列keyPath也是可以的。
objectParameters:可選引數。常用引數之一是unique,表示該欄位值是否唯一,不能重複。例如,本demo中id是不能重複的,於是有設定:
複製程式碼

3、新增資料 上面的程式碼建好了欄位,並且新增了一條資料,但是我們如果想在onupgradeneeded事件外面操作,接下來的步驟了。 由於資料庫的操作都是基於事務(transaction)來進行,於是,無論是新增編輯還是刪除資料庫,我們都要先建立一個事務(transaction),然後才能繼續下面的操作。 語法: var transaction = db.transaction(dbName, "readwrite"); 第一個引數是事務希望跨越的物件儲存空間的列表,可以是陣列或者字串。如果你希望事務能夠跨越所有的物件儲存空間你可以傳入一個空陣列。如果你沒有為第二個引數指定任何內容,你得到的是隻讀事務。因為這裡我們是想要寫入所以我們需要傳入 "readwrite" 標識。

var timer = setInterval(function () {
    if(db) {
        clearInterval(timer);
        // 新建一個事務
        var transaction = db.transaction(['testItem'], 'readwrite');
        // 開啟一個儲存物件
        var objectStore = transaction.objectStore('testItem');
        // 新增資料到物件中
        objectStore.add({ name: 'xiaoming', age: '12' });
        objectStore.add({ name: 'xiaolong', age: '20' });
    }
},100)
複製程式碼

為什麼要用一個間隔定時器? 因為這是一個demo,正常的是要有操作才能進行資料庫的寫入,在我們的demo中,js執行到transaction會比indexedDB的onsuccess事件回撥快,導致會拿到db為undefined,所以寫了個間隔定時器等它一會。

4、獲取資料

var transaction = db.transaction(['testItem'], 'readwrite');

var objectStore = transaction.objectStore('testItem');

var getRquest = objectStore.get(1);
getRquest.onsuccess = function (event) {
    console.log(getRquest.result);
}
//輸出:{name: "cfangxu", age: "27", id: 1}
複製程式碼

5、修改資料

var transaction = db.transaction(['testItem'], 'readwrite');

var objectStore = transaction.objectStore('testItem');

var getRquest = objectStore.put({ name: 'chenfangxu', age: '27', id:1 });
// 修改了id為1的那條資料
複製程式碼

6、刪除資料

var transaction = db.transaction(['testItem'], 'readwrite');

var objectStore = transaction.objectStore('testItem');

var getRquest = objectStore.delete(1);
// 刪除了id為1的那條資料
複製程式碼

上面的例子執行完後,一定一定要右鍵重新整理indexedDB,它自己是不會變的。

這裡推薦一下我的前端學習交流群:784783012,裡面都是學習前端的,如果你想製作酷炫的網頁,想學習程式設計。自己整理了一份2018最全面前端學習資料,從最基礎的HTML+CSS+JS【炫酷特效,遊戲,外掛封裝,設計模式】到移動端HTML5的專案實戰的學習資料都有整理,送給每一位前端小夥伴,有想學習web前端的,或是轉行,或是大學生,還有工作中想提升自己能力的,正在學習的小夥伴歡迎加入學習。

相關文章