资讯专栏INFORMATION COLUMN

js-cookie源码学习

JellyBool / 3382人阅读

这篇文章最初发表在我自己搭建的站点js-cookie库源码学习

背景

最近在做项目的时候,前端登录功能要做一个记住密码的功能。但开发用的框架中没有实现这个功能,所以我就想自己实现这个功能。实现起来其实很简单,就是每次用户在登录页面点击登录时,把用户输入的用户名和密码保存到cookie中就可以了,当用户再登录时,再从cookie中获取用户名和密码填充到表单中就可以了。当然,也可以选择保存在localStorage中,不过本文主要是想分析下用到的js-cookie这个轻量级的js库。

Document.cookie

Document.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-cookie

js-cookie库使用起来很简单,支持向浏览器中写入、读取、删除cookie三种操作,API简单也符合我们的使用习惯。那么它是如何实现这三个操作的呢?这就要分析它的源码了。
注意:这里分析的是它的releases-v2.2.0版本。github上的master分支源码已经修改,主要是把set和get方法重构了,拆分成了两个多带带的方法,releases-v2.2.0版本set和get方法是写在一起的。下面简单分析下它的源码实现:set和get。remove是set的一种特殊情况,增加expires属性即可,这里就不细说了。

set的主要逻辑 -- releases-v2.2.0版本
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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105829.html

相关文章

  • 记一次开源学习--D2Admin 人人企业版

    摘要:前言上个月月底开源组开源了使用适配人人企业版专业版的前端工程具体详情见人人企业版适配发布。当然,也督促自己产出一篇相关的文章,来记录这次有趣的学习之旅。 Created by huqi at 2019-5-5 13:01:14 Updated by huqi at 2019-5-20 15:57:37 前言 上个月月底@D2开源组 开源了使用 D2Admin 适配 人人企业版(专业版) 的...

    notebin 评论0 收藏0
  • 解析ahooks整体架构及React工具库源码

     这是讲 ahooks 源码的第一篇文章,简要就是以下几点:  加深对 React hooks 的理解。  学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。  培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。  注:本系列对 ahooks 的源码解析是基于v3.3.13。自己 folk 了一份源码,主要是对源码做了一些解读,可见详情。  第一篇主要介绍 a...

    3403771864 评论0 收藏0
  • 关于ahooks封装cookie localStorage sessionStorage方法

      之所以讲这篇文章主要是为了加深对 React hooks 的理解。  因此,先要学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。  且培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。  现在看下ahooks 是怎么封装 cookie/localStorage/sessionStorage 的。  cookie  ahooks 封装了 useCookie...

    3403771864 评论0 收藏0
  • 用vue2.6实现一个抖音很火的【时间轮盘】屏保小DEMO

    摘要:代码如下转动然后通过具体的秒分小时上下午星期日期月值转动,来控制角度,而且当前值进行文字高亮。 写在前面:前段时间看抖音,有人用时间轮盘作为动态的桌面壁纸,一时间成为全网最火的电脑屏保,后来小米等运用市场也出现了【时间轮盘】,有点像五行八卦,感觉很好玩,于是突发奇想,自己写一个网页版小DEMO玩玩,先看看效果:在线体验showImg(https://user-gold-cdn.xitu.io...

    wind5o 评论0 收藏0
  • watchdog-framework基于SpringBoot+Shiro+Mybatis等开发的企业

    摘要:介绍基于等开发的企业级管理系统快速开发脚手架,拥有角色用户资源管理同时数据更新时关联的用户相应的权限也会实时更新,并且此项目会进行持续更新升级,欢迎使用,若对你有帮助请点击上方的。 介绍 watchdog-framework基于SpringBoot+Shiro+Mybatis+Mybatis-Plus+HikariCP+Redis+Vue+iView等开发的企业级管理系统快速开发脚手架...

    caiyongji 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<