js-cookie源码阅读有感

520 阅读4分钟

js-cookie源码阅读记录

js-cookie是一个javascript 设置cookie的库

1. 基本功能与使用

  • 设置cookie
Cookies.set('test', '123', { expires: 1 })
  • 获取cookie
Cookies.get('test')
  • 删除cookie
Cookies.remove('test')

2. 方法实现原理

  • set方法 原理: document.cookie = test=123; expires=1,结构为 以;空格分隔的键值对
    function set (key, value, attributes) {
      // 接收三个参数 
      // key: cookie的键 
      // value: cookie的值
      // attributes: cookie的属性值
      // 如果document是undefined 说明不是浏览器 则代码不会往下执行
      if (typeof document === 'undefined') {
        return
      }
      // 合并传入的属性值与默认值
      attributes = assign({}, defaultAttributes, attributes);
      
      // expires 传入的数字表示的是天数
      // 如果传入的有效期属性是数字 则有效期=当前时间戳+传入的天数的毫秒数
      // 比如传入1 则表示cookie 1天后过期
      if (typeof attributes.expires === 'number') {
        attributes.expires = new Date(Date.now() + attributes.expires * 864e5);
      }
      // 转换为UTC格式的字符串
      if (attributes.expires) {
        attributes.expires = attributes.expires.toUTCString();
      }
       
      // key先编码 再解码
      key = encodeURIComponent(key)
        .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
        .replace(/[()]/g, escape);
       
      // 遍历属性
      var stringifiedAttributes = '';
      for (var attributeName in attributes) {
        if (!attributes[attributeName]) {
          continue
        }
        // 得到类似这样的结构 '; expires'
        stringifiedAttributes += '; ' + attributeName;

        if (attributes[attributeName] === true) {
          continue
        }
        // 得到类似这样的结构 '; expires=1'
        stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];
      }
       // 最终得到 'test=124; expries=1'
      return (document.cookie =
        key + '=' + converter.write(value, key) + stringifiedAttributes)
    }
  • get方法 原理: 解析document.cookie的返回值

比如: 'test2=%E4%BD%A0%E5%A5%BD; esmodule=EsModule; es=[(123)]'

    function get (key) {
      // 判断是不是浏览器 有没有传key
      if (typeof document === 'undefined' || (arguments.length && !key)) {
        return
      }

      // 有cookie则以;空格分割 否则为空数组
      var cookies = document.cookie ? document.cookie.split('; ') : [];
      var jar = {};
      for (var i = 0; i < cookies.length; i++) {
        // 得到每一项以=分割的数组 例如 [test, 123]
        var parts = cookies[i].split('=');
        // 得到value值 join处理 cookie值中有=的情况 比如 test=123=cos 的情况
        var value = parts.slice(1).join('=');

        try {
          // 解码key值
          var foundKey = decodeURIComponent(parts[0]);
          // 解码value值
          // 传入foundKey是为了自定义解码时使用
          jar[foundKey] = converter.read(value, foundKey);
          // 如果当前变量的key等于获取的key 则跳出循环
          if (key === foundKey) {
            break
          }
        } catch (e) {}
      }
      // 返回cookie的值 或者jar对象
      return key ? jar[key] : jar
    }
  • remove方法 原理: 设置属性{ expires: -1 }让cookie直接过期
    remove: function (key, attributes) {
      set(
        key,
        '',
        assign({}, attributes, {
          expires: -1
        })
      );
    },

3. 解释下上文的Cookies

上文的Cookies.set中的Cookies其实是js-cookie导出的全局属性,指向的是源码中的init方法

init方法返回一个对象,对象包含setgetremove等方法

 function init (converter, defaultAttributes) {
    function set (key, value, attributes) {
      console.log(key, value, attributes)
      if (typeof document === 'undefined') {
        return
      }

      attributes = assign({}, defaultAttributes, attributes);
     
     ... // 此处为省略代码

      return (document.cookie =
        key + '=' + converter.write(value, key) + stringifiedAttributes)
    }

    function get (key) {
      if (typeof document === 'undefined' || (arguments.length && !key)) {
        return
      }
    ... // 此处为省略代码
      return key ? jar[key] : jar
    }

    return Object.create(
      {
        set: set,
        get: get,
        remove: function (key, attributes) {
          set(
            key,
            '',
            assign({}, attributes, {
              expires: -1
            })
          );
        },
        withAttributes: function (attributes) {
          return init(this.converter, assign({}, this.attributes, attributes))
        },
        withConverter: function (converter) {
          return init(assign({}, this.converter, converter), this.attributes)
        }
      },
      {
        attributes: { value: Object.freeze(defaultAttributes) },
        converter: { value: Object.freeze(converter) }
      }
    )
  }
    var api = init(defaultConverter, { path: '/' });
    
    return api;

返回对象使用了Object.create()方法

我们看到上面还返回了withAttributeswithConverter方法

withAttributes传入自定义的属性,返回新的实例api,也就是init函数,只不过设置过后,当再次设置cookie时,默认会带上传入的自定义属性

const api = Cookies.withAttributes({ path: '/', domain: '.example.com' })

withConverter 是创建覆盖默认解码实现的 api 的新实例,如果要单独为cookie解码与编码,则可以使用此方法

官网例子

// 自定义解码
document.cookie = 'escaped=%u5317'
document.cookie = 'default=%E5%8C%97'
var cookies = Cookies.withConverter({
  read: function (value, name) {
    if (name === 'escaped') {
      return unescape(value)
    }
    // Fall back to default for all other cookies
    return Cookies.converter.read(value, name)
  }
})
cookies.get('escaped') // 北
cookies.get('default') // 北
cookies.get() // { escaped: '北', default: '北' }

// 自定义编码
Cookies.withConverter({
  write: function (value, name) {
    return value.toUpperCase()
  }
})


Object.freeze方法用来冻结默认属性与方法,防止被修改

4. 实现一个兼容UMD,CMD,AMD规范,浏览器全局属性的库的写法

; (function (context, factory) {
  if (typeof exports === 'object' && typeof module !== undefined) {
    module.exports = factory()
  } else if (typeof define === 'function' && define.amd) {
    define(factory)
  } else {
    context['ZKK'] = factory()
  }
}(
  this, (function () {
    return 'hello'
  })
))

js-cookie源码中的写法

;
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, (function () {
    var current = global.Cookies;
    var exports = global.Cookies = factory();
    exports.noConflict = function () { global.Cookies = current; return exports; };
  }()));
}(this, (function () { 'use strict';
......

5. init函数默认的解码,编码对象

  var defaultConverter = {
    read: function (value) {
      if (value[0] === '"') {
        value = value.slice(1, -1);
      }
      return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
    },
    write: function (value) {
      return encodeURIComponent(value).replace(
        /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,
        decodeURIComponent
      )
    }
  };

6. init函数

init函数返回了一个对象,对象的属性上有对应的方法,这些方法又在init的函数作用域里。

参考资料

js-cookie