這篇文章最初發表在我自己搭建的站點js-cookie庫源碼學習
背景最近在做項目的時候,前端登錄功能要做一個記住密碼的功能。但開發用的框架中沒有實現這個功能,所以我就想自己實現這個功能。實現起來其實很簡單,就是每次用戶在登錄頁面點擊登錄時,把用戶輸入的用戶名和密碼保存到cookie中就可以了,當用戶再登錄時,再從cookie中獲取用戶名和密碼填充到表單中就可以了。當然,也可以選擇保存在localStorage中,不過本文主要是想分析下用到的js-cookie這個輕量級的js庫。
Document.cookieDocument.cookie這個API比較"簡陋"。這里先簡單的介紹下Document.cookie這個API,主要是寫入、讀取、刪除三個操作。
// 1. 寫入cookie, 會追加到當前的cookie中 document.cookie = "username=abc" // 2. 讀取cookie,可以用正則匹配或者字符串解析查找的方法來讀取 var usename = /username=(S+);/.exec(document.cookie)[1] // 這里只是簡單的匹配了下,實際開發中要考慮很多情況 // 3. 刪除cookie,設置某個cookie過期即可 document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT" // 4. 判斷是否有某個cookie,其實和讀取差不多 /username=(S+);/.test(document.cookie) // true有,false沒有
可以看到原生的Document.cookie寫入基本靠拼字符串,讀取和判斷是否有要靠正則或者字符串解析,刪除則也是要拼字符串。這個操作不太方便,而且和我們平常用的API不太一樣,比如我們常用的Map:
var map = new Map(); map.set(key, value); map.get(key); map.has(key); map.delete(key);
它的api就是對"鍵值對"這種數據結構進行操作。Document.cookie其實也是key=value這種鍵值對結構,但是它沒有提供像map這樣的API接口,這顯然不符合我們的“直覺”和使用習慣。
js-cookiejs-cookie庫使用起來很簡單,支持向瀏覽器中寫入、讀取、刪除cookie三種操作,API簡單也符合我們的使用習慣。那么它是如何實現這三個操作的呢?這就要分析它的源碼了。
注意:這里分析的是它的releases-v2.2.0版本。github上的master分支源碼已經修改,主要是把set和get方法重構了,拆分成了兩個多帶帶的方法,releases-v2.2.0版本set和get方法是寫在一起的。下面簡單分析下它的源碼實現:set和get。remove是set的一種特殊情況,增加expires屬性即可,這里就不細說了。
function api (key, value, attributes) { // set 主要邏輯 if (arguments.length > 1) { attributes = extend({ path: "/" }, api.defaults, attributes); if (typeof attributes.expires === "number") { var expires = new Date(); expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); attributes.expires = expires; } // We"re using "expires" because "max-age" is not supported by IE attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ""; try { // 這里需要注意,用戶有可能要保存json格式的cookie值,所以這里需要轉化下 result = JSON.stringify(value); if (/^[{[]/.test(result)) { // 如果轉化成了合法的json,則將value重新賦值為json字符串,如果不含有{或[,則不是json字符串,也就不會走這個分支 value = result; } } catch (e) {} // 這里為什么要把value先編碼再解碼呢?,下面的key也是,不過key要解碼的unicode值少些 if (!converter.write) { // 內置的編碼轉換 // ["#", "$", "&", "+", ":", "<", ">", "=", "/", "?", "@", "[", "]", "^", "`", "{", "}", "|"] value = encodeURIComponent(String(value)) .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); } else { value = converter.write(value, key); } // 先編碼 key = encodeURIComponent(String(key)); // ["#", "$", "&", "+", "^", "`", "|"] // 再通過字符串替換的方式解碼 key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); // ECMA-262 standard規范已經不推薦用這個escape函數了 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/escape // 而且 ()也不會被編碼,所以感覺下面的這句是沒有必要的 key = key.replace(/[()]/g, escape); // 拼接其它的cookies屬性值 var stringifiedAttributes = ""; for (var attributeName in attributes) { if (!attributes[attributeName]) { continue; } stringifiedAttributes += "; " + attributeName; if (attributes[attributeName] === true) { continue; } stringifiedAttributes += "=" + attributes[attributeName]; } return (document.cookie = key + "=" + value + stringifiedAttributes); } }
set方法在寫入cookie時會先對cookie值encodeURIComponent然后再decodeURIComponent,這樣可以保證存儲再cookie中的值始終是不編碼的可讀性性好的字符串。
get的主要邏輯 -- releases-v2.2.0版本function api (key, value, attributes) { // Read if (!key) { result = {}; } // To prevent the for loop in the first place assign an empty array // in case there are no cookies at all. Also prevents odd result when // calling "get()" var cookies = document.cookie ? document.cookie.split("; ") : []; var rdecode = /(%[0-9A-Z]{2})+/g; var i = 0; for (; i < cookies.length; i++) { var parts = cookies[i].split("="); var cookie = parts.slice(1).join("="); if (!this.json && cookie.charAt(0) === """) { // 沒看懂為什么需要這個if分支 cookie = cookie.slice(1, -1); // 去掉了 " " } try { var name = parts[0].replace(rdecode, decodeURIComponent); cookie = converter.read ? converter.read(cookie, name) : converter(cookie, name) || cookie.replace(rdecode, decodeURIComponent); if (this.json) { try { cookie = JSON.parse(cookie); } catch (e) {} } if (key === name) { result = cookie; break; } if (!key) { result[name] = cookie; } } catch (e) {} } return result; }整個庫暴露接口的方式 -- releases-v2.2.0版本
通過上面的get、set以及下面的代碼可以看到整個庫其實返回的就是 function api() {}這個函數,
set、get、remove的邏輯都寫在api這個函數里了,所以api這個函數看起來很長,而且由于其中涉及到許多細節處理導致邏輯也比較復雜,那么庫的作者應該是意識到了這一點,所以在github的master分支上的代碼已經把get和set拆分開了,感興趣的可以去看看js-cookie@master。
function init(converter) { function api (key, value, attributes) { api.set = api; // Cookie.set(key, value, attributes) api.get = function (key) { // Cookie.get(key) return api.call(api, key); }; api.getJSON = function () { // Cookie.getJSON(key) return api.apply({ json: true }, [].slice.call(arguments)); }; api.defaults = {}; api.remove = function (key, attributes) { // Cookie.remove(key, attributes) api(key, "", extend(attributes, { expires: -1 })); }; api.withConverter = init; // 支持自定義的編碼方式 // 返回的是 function api() {} return api; } // 返回的是 function api() {} return init(function () {}); }js-cookie庫AMD規范和CommonJS規范的實現
js-cookie聲稱支持AMD規范和CommonJS規范,那么它是如何支持這兩種規范的呢?
;(function (factory) { var registeredInModuleLoader = false; if (typeof define === "function" && define.amd) { // AMD define(factory); registeredInModuleLoader = true; } if (typeof exports === "object") { // CommonJS module.exports = factory(); registeredInModuleLoader = true; } if (!registeredInModuleLoader) { // 瀏覽器 var OldCookies = window.Cookies; var api = window.Cookies = factory(); api.noConflict = function () { window.Cookies = OldCookies; return api; }; } }(function () { // factory 邏輯... // 返回的是 function api() {} return init(function () {}); });
其實也很簡單,現在很多js庫基本都是這么實現的。
總結js-cookie庫的源碼很簡單,但是它的v2.2.0-release版本將set和get邏輯都寫在一個方法的方式其實是不提倡這么做的。這么做需要用if分支來做很多過濾,會導致代碼邏輯較復雜。上面也看到,暴露接口時,它用到了function.call()和function.apply(),以及代碼內部用到的已經不推薦使用的arguments,這都使代碼的可讀性降低了。不過它里面對于cookie的處理過程仍然值得參考。其實MDN上也提供了一個輕量的處理cookie的庫一個完整支持unicode的cookie讀取/寫入器,有時間也可以看看。
參考MDN: Document.cookie
GitHub: js-cookie
AMD 異步模塊定義
CommonJS
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/105829.html
摘要:前言上個月月底開源組開源了使用適配人人企業版專業版的前端工程具體詳情見人人企業版適配發布。當然,也督促自己產出一篇相關的文章,來記錄這次有趣的學習之旅。 Created by huqi at 2019-5-5 13:01:14 Updated by huqi at 2019-5-20 15:57:37 前言 上個月月底@D2開源組 開源了使用 D2Admin 適配 人人企業版(專業版) 的...
這是講 ahooks 源碼的第一篇文章,簡要就是以下幾點: 加深對 React hooks 的理解。 學習如何抽象自定義 hooks。構建屬于自己的 React hooks 工具庫。 培養閱讀學習源碼的習慣,工具庫是一個對源碼閱讀不錯的選擇?! ∽ⅲ罕鞠盗袑?ahooks 的源碼解析是基于v3.3.13。自己 folk 了一份源碼,主要是對源碼做了一些解讀,可見詳情?! 〉谝黄饕榻B a...
之所以講這篇文章主要是為了加深對 React hooks 的理解。 因此,先要學習如何抽象自定義 hooks。構建屬于自己的 React hooks 工具庫。 且培養閱讀學習源碼的習慣,工具庫是一個對源碼閱讀不錯的選擇。 現在看下ahooks 是怎么封裝 cookie/localStorage/sessionStorage 的?! ookie ahooks 封裝了 useCookie...
摘要:代碼如下轉動然后通過具體的秒分小時上下午星期日期月值轉動,來控制角度,而且當前值進行文字高亮。 寫在前面:前段時間看抖音,有人用時間輪盤作為動態的桌面壁紙,一時間成為全網最火的電腦屏保,后來小米等運用市場也出現了【時間輪盤】,有點像五行八卦,感覺很好玩,于是突發奇想,自己寫一個網頁版小DEMO玩玩,先看看效果:在線體驗showImg(https://user-gold-cdn.xitu.io...
摘要:介紹基于等開發的企業級管理系統快速開發腳手架,擁有角色用戶資源管理同時數據更新時關聯的用戶相應的權限也會實時更新,并且此項目會進行持續更新升級,歡迎使用,若對你有幫助請點擊上方的。 介紹 watchdog-framework基于SpringBoot+Shiro+Mybatis+Mybatis-Plus+HikariCP+Redis+Vue+iView等開發的企業級管理系統快速開發腳手架...
閱讀 1218·2021-09-30 09:47
閱讀 3770·2021-09-06 15:02
閱讀 1783·2021-09-01 10:46
閱讀 2367·2019-08-30 15:52
閱讀 601·2019-08-29 15:28
閱讀 1875·2019-08-29 15:08
閱讀 1158·2019-08-29 13:28
閱讀 2582·2019-08-29 12:19