【若川视野 x 源码共读】第17期 | js-cookie

148 阅读3分钟

cookie是什么

HTTP 是无状态的协议,这方便了它的扩展,但也会造成一些问题:因为它的每个请求都是完全独立的,,前一秒发送的请求,后一秒就完全不知道你是谁了。

既然服务器记不住,那就在外部想办法记住,HTTP 的 Cookie 就是为了解决这一问题的机制,你可以理解为是服务器给每个客户端都贴上一张小纸条,上面写了一些只有服务器才能理解的数据,需要的时候客户端把这些信息发给服务器,服务器看到 Cookie,就能够认出对方是谁了。

使用 js-cookie 解决了什么问题?

适用于所有浏览器

接受任何字符

经过严格测试

无依赖关系

支持ES模块

支持AMD/CommonJS

符合RFC 6265

有用的Wiki

启用自定义编码/解码

<800字节gzip!

同时相对于原生方法更加优雅

// 原生 ==========
// 修改
document.cookie = "name=qingfeng"; // 只会更新名称为 user 的 cookie
document.cookie = "name=qingfeng123"
// 删除
// 删除 cookie(让它立即过期)
document.cookie = "expires=Thu, 01 Jan 1970 00:00:00 GMT";
document.cookie = "=qingfeng; max-age=0";

// js-cookie ===============
// set
Cookies.set('name', 'value')
// get 
Cookies.get('name') // => 'value'
Cookies.get() // => { name: 'value' }
// remove
Cookies.remove('name')

阅读源码

"exports": {
  ".": {
    "import": "./dist/js.cookie.mjs",
    "require": "./dist/js.cookie.js"
  },
  "./package.json": "./package.json"
  },

打包查看入口文件,当然我们这里直接去src 目录下查看 api.mjs 文件

.mjs 后缀第一次遇见,那什么是.mjs呢?

.mjs

.mjs:表示当前文件用 ESM 的方式进行加载

.js :采用 CJS 的方式加载。

api.mjs文件有什么呢?

1、引用工具方法 assign 和 defaultConverter

2、核心逻辑 INIT 函数

init 接受两个参数,一个转换器,一个默认对象

init函数内部实现

init方法内部,定义了set,get方法,并返回了一个使用Object.create方法创建的对象

1、set ----> 作用是写入COOKIE

function set(name, value, attributes) {
    // document 不存在时直接返回
    if (typeof document === 'undefined') {
      return
    }

    // 合并 set attributes 和 init defaultAttributes
    attributes = assign({}, defaultAttributes, attributes)

    // 判断过期时间是否是数字类型
    if (typeof attributes.expires === 'number') {
      // 转毫秒
      attributes.expires = new Date(Date.now() + attributes.expires * 864e5)
    }
    if (attributes.expires) {
      // 转UTC,UTC和GMT,它们都是指的格林尼治标准时间,
      // 只不过UTC的称呼更为正式一点。两者的区别在于前者是一个天文上的概念而后者是基于一个原子钟。
      attributes.expires = attributes.expires.toUTCString()
    }

    // 使用decodeURIComponent对名称进行处理
    name = encodeURIComponent(name)
      .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
      .replace(/[()]/g, escape)

    // 定义属性
    var stringifiedAttributes = ''
    for (var attributeName in attributes) {
      // 不存在跳过
      if (!attributes[attributeName]) {
        continue
      }

      // 属性名拼接
      stringifiedAttributes += '; ' + attributeName

      if (attributes[attributeName] === true) {
        continue
      }

      // Considers RFC 6265 section 5.2:
      // ...
      // 3.  If the remaining unparsed-attributes contains a %x3B (";")
      //     character:
      // Consume the characters of the unparsed-attributes up to,
      // not including, the first %x3B (";") character.
      // ...
      // 拼接值
      stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]
    }

    // 使用document.cookie赋值COOKIE
    return (document.cookie =
      name + '=' + converter.write(value, name) + stringifiedAttributes)
  }

2、get ----> 作用是读取COOKIE

function get(name) {
    // document arguments,名称不存在时,直接返回
    if (typeof document === 'undefined' || (arguments.length && !name)) {
      return
    }

    // To prevent the for loop in the first place assign an empty array
    // in case there are no cookies at all.
    // cookies存在分号截取,否则返回空数组
    var cookies = document.cookie ? document.cookie.split('; ') : []
    var jar = {}
    for (var i = 0; i < cookies.length; i++) {
      // 键值对存储,,等号分割
      var parts = cookies[i].split('=')
      var value = parts.slice(1).join('=')

      try {
        var found = decodeURIComponent(parts[0])
        jar[found] = converter.read(value, found)

        if (name === found) {
          break
        }
      } catch (e) {}
    }

    // 有name 返回当前name对应value,没有返回所有
    return name ? jar[name] : jar
  }

相对而言,get逻辑要简单一些,主要是对数据读取并返回键值对

3、return 返回

return Object.create({
    set: set,
    get: get,
    remove: function (name, attributes) {
      set(
        name,
        '',
        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)
    }
  })

返回set,get,remove 方法,remove就是将当前name值过期时间设置为-1.withAttributes和withConverter则把自己的属性和传入的参数进行合并

4、assign方法

/* eslint-disable no-var */
export default function (target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i]
    for (var key in source) {
      target[key] = source[key]
    }
  }
  return target
}
/* eslint-enable no-var */

拷贝合并

5、defaultConverter

/* eslint-disable no-var */
export default {
  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
    )
  }
}
/* eslint-enable no-var */

导出read,write方法

这样一来整个js-cookie就完成了~

总结思考

为什么会出现cookie

cookie 和 session token 区别

cookie和localstorage和sessionstorage的区别

js-cookie 有什么优势,为什么要引入呢?