本篇主要介紹Cookie技術的讀書總結,可是我認爲邏輯上最好會和Web Storage技術放在一塊兒進行對比,所以後續會再總結一篇關於WEB存儲的姊妹總結,敬請期待。html
首先先來一段總結:Cookie用於本地數據存儲,出如今服務器和瀏覽器交互的響應Set-Cookie頭部和請求Cookie頭部中,受到單域名下Cookie的數量、單個Cookie大小、性能、安全限制。子Cookie技術的出現緩解了單域名下Cookie的數量限制,關於子Cookie有一整套工具函數可使用。apache
用戶的信息最好存儲在客戶端上,這就對客戶端數據存儲提出了要求。最先的解決方式就是Cookie。HTTP Cookie,一般直接叫作 cookie,最初是在客戶端用於存儲會話信息的。該標準要求服務器對任意 HTTP 請求發送 Set-Cookie HTTP 頭做爲響應的一部分,其中包含會話信息。數組
一個典型的響應頭部:瀏覽器
HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value Other-header: other-header-value
這個 HTTP 響應設置以 name 爲名稱、以 value 爲值的一個 cookie,名稱和值在傳送時都必須是URL 編碼的。瀏覽器會存儲這樣的會話信息,並在這以後,經過爲每一個請求添加 Cookie 頭將信息發送回服務器:安全
GET /index.html HTTP/1.1 Cookie: name=value Other-header: other-header-value
cookie 在性質上是綁定在特定的域名下的。當設定了一個 cookie 後,再給建立它的域名發送請求時,都會包含這個 cookie。這個限制確保了儲存在 cookie 中的信息只能讓批准的接受者訪問,而沒法被其餘域訪問。服務器
因爲 cookie 是存在客戶端計算機上的,還加入了一些限制確保 cookie 不會被惡意使用,同時不會佔據太多磁盤空間。每一個域的 cookie 總數是有限的,不過瀏覽器之間各有不一樣:
1)IE6 以及更低版本限制每一個域名最多 20 個 cookie。
2)IE7 和以後版本每一個域名最多 50 個。 IE7 最初是支持每一個域名最大 20 個 cookie,以後被微軟的一個補丁所更新。
3)Firefox 限制每一個域最多 50 個 cookie。
4)Opera 限制每一個域最多 30 個 cookie。
5)Safari 和 Chrome 對於每一個域的 cookie 數量限制沒有硬性規定。cookie
當超過單個域名限制以後還要再設置 cookie,瀏覽器就會清除之前設置的 cookie。 IE 和 Opera 會刪除最近最少使用過的(LRU, Least Recently Used) cookie,騰出空間給新設置的 cookie。 Firefox 看上去好像是隨機決定要清除哪一個 cookie,因此考慮 cookie 限制很是重要,以避免出現不可預期的後果。dom
瀏覽器中對於 cookie 的尺寸也有限制。大多數瀏覽器都有大約 4096B(加減 1)的長度限制。爲了最佳的瀏覽器兼容性,最好將整個 cookie 長度限制在 4095B(含 4095)之內。尺寸限制影響到一個域下全部的 cookie,而並不是每一個 cookie 單獨限制。若是你嘗試建立超過最大尺寸限制的 cookie,那麼該 cookie 會被悄無聲息地丟掉。函數
1)名稱:一個惟一肯定 cookie 的名稱。cookie 名稱是不區分大小寫的。cookie 的名稱必須是通過 URL 編碼的。
2)值:儲存在 cookie 中的字符串值。值必須被 URL 編碼。
3)域: cookie 對於哪一個域是有效的。全部向該域發送的請求中都會包含這個 cookie 信息。
4)路徑:對於指定域中的那個路徑,應該向服務器發送 cookie。
5)失效時間:表示 cookie 什麼時候應該被刪除的時間戳(也就是,什麼時候應該中止向服務器發送這個cookie)。默認狀況下,瀏覽器會話結束時即將全部 cookie 刪除;不過也能夠本身設置刪除時間。這個值是個 GMT 格式的日期(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用於指定應該刪除cookie 的準確時間。所以, cookie 可在瀏覽器關閉後依然保存在用戶的機器上。若是你設置的失效日期是個之前的時間,則 cookie 會被馬上刪除。
6)安全標誌:指定後, cookie 只有在使用 SSL 鏈接的時候才發送到服務器。例如, cookie 信息只能發送給 https://www.wrox.com,而 http://www.wrox.com 的請求則不能發送 cookie。工具
每一段信息都做爲 Set-Cookie 頭的一部分,使用分號加空格分隔每一段。secure 標誌是 cookie 中惟一一個非名值對兒的部分,直接包含一個 secure 單詞。尤爲要注意,域、路徑、失效時間和 secure 標誌都是服務器給瀏覽器的指示(是從服務器發回的響應),以指定什麼時候應該發送 cookie。這些參數並不會做爲發送到服務器的 cookie 信息的一部分,只有名值對兒纔會被髮送到服務器。
設置 cookie 的格式以下,和 Set-Cookie 頭中使用的格式同樣,以下:
name=value; expires=expiration_time; path=domain_path;
domain=domain_name; secure
因爲 JavaScript 中讀寫 cookie 不是很是直觀,經常須要寫一些函數來簡化 cookie 的功能。基本的cookie 操做有三種:讀取、寫入和刪除。建立cookie的工具函數:
var CookieUtil = { get: function (name) { var cookieName = encodeURIComponent(name) + '=', cookieStart = document.cookie.indexOf(cookieName), cookieValue = null; if (cookieStart > - 1) { var cookieEnd = document.cookie.indexOf(';', cookieStart); if (cookieEnd == - 1) { cookieEnd = document.cookie.length; } cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd)); } return cookieValue; }, set: function (name, value, expires, path, domain, secure) { var cookieText = encodeURIComponent(name) + '=' + encodeURIComponent(value); if (expires instanceof Date) { cookieText += '; expires=' + expires.toGMTString(); } if (path) { cookieText += '; path=' + path; } if (domain) { cookieText += '; domain=' + domain; } if (secure) { //secure在這裏是布爾值 cookieText += '; secure'; } document.cookie = cookieText; }, unset: function (name, path, domain, secure) { this.set(name, '', new Date(0), path, domain, secure); } };
CookieUtil.get()方法根據 cookie 的名字獲取相應的值。它會在 document.cookie 字符串中查找 cookie 名加上等於號的位置。若是找到了,那麼使用 indexOf()查找該位置以後的第一個分號(表示了該 cookie 的結束位置)。若是沒有找到分號,則表示該 cookie 是字符串中的最後一個,則餘下的字符串都是 cookie 的值。該值使用 decodeURIComponent()進行解碼並最後返回。若是沒有發現 cookie,則返回 null。
CookieUtil.set()方法在頁面上設置一個 cookie,接收以下幾個參數: cookie 的名稱, cookie 的值,可選的用於指定 cookie 什麼時候應被刪除的 Date 對象, cookie 的可選的 URL 路徑,可選的域,以及可選的表示是否要添加 secure 標誌的布爾值。參數是按照它們的使用頻率排列的,只有頭兩個是必需的。在這個方法中,名稱和值都使用encodeURIComponent()進行了URL編碼,並檢查其餘選項。若是expires參數是 Date 對象,那麼會使用 Date 對象的 toGMTString()方法正確格式化 Date 對象,並添加到expires 選項上。方法的其餘部分就是構造 cookie 字符串並將其設置到 document.cookie 中。
沒有刪除已有 cookie 的直接方法。因此,須要使用相同的路徑、域和安全選項再次設置 cookie,並將失效時間設置爲過去的時間。 CookieUtil.unset()方法能夠處理這種事情。它接收 4 個參數:要刪除的 cookie 的名稱、可選的路徑參數、可選的域參數和可選的安全參數。這些參數加上空字符串並設置失效時間爲 1970 年 1 月 1 日(初始化爲 0ms 的 Date 對象的值),傳給 CookieUtil.set()。這樣就能確保刪除 cookie。
FireBug對應哪一個頁面,設置的cookie就存儲在那個頁面對應的域。打開本地apache服務器的/localhost/alien/頁面,在其中打開firebug。
測試實例1:
CookieUtil.set("name", "Nicholas"); CookieUtil.set("book", "Professional JavaScript"); //讀取 cookie 的值 console.log(CookieUtil.get("name")); //"Nicholas" console.log(CookieUtil.get("book")); //"Professional JavaScript"
測試實例2 刪除cookie:
CookieUtil.unset("name");
CookieUtil.unset("book");
此時FireBug中不顯示任何Cookie。
測試實例3 打開本地服務器localhost主頁,設置安全的cookie。
CookieUtil.set("name","Nicholas", null, null, null, true);
console.log(CookieUtil.get("name"));
設置secure爲true時,前面缺乏的參數都定義爲null。這是由於JavaScript會按照順序對應參數。
測試結果:安全項顯示「安全」。
子Cookie的目的是爲了突破單域名下的Cookie數量限制,也就是在一個Cookie中存儲多個名值對,常見格式以下:
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
關於子Cookie的設置、獲取和刪除有如下工具函數:
var SubCookieUtil = { get: function (name, subName) { var subCookies = this.getAll(name); if (subCookies) { return subCookies[subName]; } else { return null; } }, getAll: function (name) { var cookieName = encodeURIComponent(name) + '=', cookieStart = document.cookie.indexOf(cookieName), cookieValue = null, cookieEnd, subCookies, i, parts, result = { }; if (cookieStart > - 1) { cookieEnd = document.cookie.indexOf(';', cookieStart); if (cookieEnd == - 1) { cookieEnd = document.cookie.length; } cookieValue = document.cookie.substring(cookieStart + cookieName.length, cookieEnd); if (cookieValue.length > 0) { subCookies = cookieValue.split('&'); for (i = 0, len = subCookies.length; i < len; i++) { parts = subCookies[i].split('='); result[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); } return result; } } return null; }, set: function (name, subName, value, expires, path, domain, secure) { var subcookies = this.getAll(name) || { }; subcookies[subName] = value; this.setAll(name, subcookies, expires, path, domain, secure); }, setAll: function (name, subcookies, expires, path, domain, secure) { var cookieText = encodeURIComponent(name) + '=', subcookieParts = new Array(), subName; for (subName in subcookies) { //因爲採用push方法,新的子Cookie被延續到原來的Cookie中 if (subName.length > 0 && subcookies.hasOwnProperty(subName)) { subcookieParts.push(encodeURIComponent(subName) + '=' + encodeURIComponent(subcookies[subName])); } } if (subcookieParts.length > 0) { cookieText += subcookieParts.join('&'); if (expires instanceof Date) { cookieText += '; expires=' + expires.toGMTString(); } if (path) { cookieText += '; path=' + path; } if (domain) { cookieText += '; domain=' + domain; } if (secure) { cookieText += '; secure'; } } else { cookieText += '; expires=' + (new Date(0)).toGMTString(); } document.cookie = cookieText; }, unset: function (name, subName, path, domain, secure) { var subcookies = this.getAll(name); if (subcookies) { delete subcookies[subName]; this.setAll(name, subcookies, null, path, domain, secure); } }, unsetAll: function (name, path, domain, secure) { this.setAll(name, null, new Date(0), path, domain, secure); } };
如下是對上述方法的解析:
獲取子 cookie 的方法有兩個: get()和 getAll()。其中 get()獲取單個子 cookie 的值, getAll()獲取全部子 cookie 並將它們放入一個對象中返回,對象的屬性爲子 cookie 的名稱,對應值爲子 cookie對應的值。 get()方法接收兩個參數: cookie 的名字和子 cookie 的名字。它其實就是調用 getAll()獲取全部的子 cookie,而後只返回所需的那一個(若是 cookie 不存在則返回 null)。
SubCookieUtil.getAll()方法和 CookieUtil.get()在解析 cookie 值的方式上很是類似。區別在於 cookie 的值並不是當即解碼,而是先根據&字符將子 cookie 分割出來放在一個數組中,每個子 cookie再根據等於號分割,這樣在 parts 數組中的前一部分即是子 cookie 名,後一部分則是子 cookie 的值。這兩個項目都要使用 decodeURIComponent()來解碼,而後放入 result 對象中,最後做爲方法的返回值。若是 cookie 不存在,則返回 null。
set()方法接收 7 個參數: cookie 名稱、子 cookie 名稱、子 cookie 值、可選的 cookie 失效日期或時間的 Date 對象、可選的 cookie 路徑、可選的 cookie 域和可選的布爾 secure 標誌。全部的可選參數都是做用於 cookie自己而非子 cookie。爲了在同一個 cookie中存儲多個子 cookie,路徑、域和 secure標誌必須一致;針對整個 cookie 的失效日期則能夠在任何一個單獨的子 cookie 寫入的時候同時設置。在這個方法中,第一步是獲取指定 cookie 名稱對應的全部子 cookie。邏輯或操做符「 ||」用於當 getAll()返回 null 時將 subcookies 設置爲一個新對象。而後,在 subcookies 對象上設置好子 cookie 值並傳給setAll()。
setAll()方法接收 6 個參數: cookie 名稱、包含全部子 cookie 的對象以及和 set()中同樣的 4個可選參數。這個方法使用 for-in 循環遍歷第二個參數中的屬性。爲了確保確實是要保存的數據,使用了 hasOwnProperty()方法,來確保只有實例屬性被序列化到子 cookie 中。因爲可能會存在屬性名爲空字符串的狀況,因此在把屬性名加入結果對象以前還要檢查一下屬性名的長度。將每一個子 cookie的名值對兒都存入 subcookieParts 數組中,以便稍後可使用 join()方法以&號組合起來。
普通 cookie 能夠經過將失效時間設置爲過去的時間的方法來刪除,可是子 cookie 不能這樣作。爲了刪除一個子 cookie,首先必須獲取包含在某個 cookie中的全部子 cookie,而後僅刪除須要刪除的那個子 cookie,而後再將餘下的子 cookie 的值保存爲 cookie的值。unset()方法用於刪除某個 cookie 中的單個子 cookie而不影響其餘的;而 unsetAll()方法則等同於 CookieUtil.unset(),用於刪除整個 cookie。和 set()及 setAll()同樣,路徑、域和 secure 標誌必須和以前建立的 cookie 包含的內容一致。
//設置兩個 cookie
SubCookieUtil.set("data", "name", "Nicholas");
SubCookieUtil.set("data", "book", "Professional JavaScript");
//設置所有子 cookie 和失效日期
SubCookieUtil.setAll("data", { name: "Nicholas", book: "Professional JavaScript" },new Date("January 1, 2018"));
//修更名字的值,並修改 cookie 的失效日期
SubCookieUtil.set("data", "name", "Michael", new Date("February 1, 2010"));
//刪除全部子Cookie
SubCookieUtil.unsetAll('data');
1)單域名下數目限制和大小限制:子Cookie只是突破了單個域名下Cookie數目限制,可是Cookie的大小依舊受限,所以要注意子Cookie的大小不能使單個Cookie超出大小限制。2)性能限制:因爲全部的 cookie 都會由瀏覽器做爲請求頭髮送,因此在 cookie 中存儲大量信息會影響到特定域的請求性能。 cookie 信息越大,完成對服務器請求的時間也就越長。儘管瀏覽器對 cookie 進行了大小限制,不過最好仍是儘量在 cookie 中少存儲信息,以免影響性能。3)安全限制:cookie 數據並不是存儲在一個安全環境中,其中包含的任何數據均可以被他人訪問。因此不要在 cookie 中存儲諸如信用卡號或者我的地址之類的數據。cookie 的性質和它的侷限使得其並不能做爲存儲大量信息的理想手段,因此又出現了其餘方法。