源码共读 js-cookie

673 阅读3分钟

我正在参与掘金会员专属活动-源码共读第一期,点击参与

前言

从易到难开始学习源码。学习源码不是为了面试,只是想揭开别人造的轮子背后的神秘面纱。同时也希望在此过程中提升自己的能力和开拓视野。

使用 js-cookie

安装

npm install js-cookie
或
pnpm install js-cookie

使用

创建一个 cookie.js 文件,写一个简单的例子

创建一个 cookie,名字为 foo, 值为 ‘bar’

import Cookies from 'js-cookie'

Cookies.set('foo', 'bar')

获取 cookie 的值

Cookies.get('foo'); // bar

删除 cookie 的值

Cookies.remove('foo')

以上的 js-cookie 在使用上的基础语法,在创建或者删除的时候,js-cookie 中的 set 还提供了第三个参数 options。语法:Cookies.set(name: string, value, options)

options 参数配置

  1. expires / max-age 设置 cookie 的过期时间

  2. Size 设置cookie 的大小

  3. path 设置cookie 的路径,默认是 “/”,本域名下所有页面都可以访问

  4. domain 设置 cookie 的域(例子: .test.com )

  5. secure 设置只能通过https来传递Cookie(默认为 false,如果设置成 true,那么只能通过http或者其他安全协议才能访问)

  6. sameSite 防止 CSRF 攻击和用户追踪(可以设置三个值:Strict、Lax 和 None)

    1. Strict 最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。
    2. Lax 规则稍稍放宽,大多数情况下是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
    3. None 关闭 sameSite 属性,提示必须设置 Secure 属性,否则无效

备注:以上是 cookie 的一部分参数,不是全部。

源码

克隆一份代码 js-cookies地址 到本地

阅读文件 src/api.mjs,大致的代码如下,然后逐个分析 set、get以及返回的对象

function init (converter, defaultAttributes) {
	function set (name, value, attributes) {
		...
	}
	function get (name) {
		...
	}
	return Object.create(
		...
	)
}
export default init(defaultConverter, { path: '/' })

set 函数

function set (name, value, attributes) {
    if (typeof document === 'undefined') {
      return
    }

    attributes = assign({}, defaultAttributes, attributes)

    // cookies过期时间
    if (typeof attributes.expires === 'number') {
      attributes.expires = new Date(Date.now() + attributes.expires * 864e5)
    }
    if (attributes.expires) {
      attributes.expires = attributes.expires.toUTCString()
    }

    // 将name encodeURIComponent 编码
    name = encodeURIComponent(name)
      .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
      .replace(/[()]/g, escape)

    // 循环attributes生成stringifiedAttributes
    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]
    }
    // converter.write(value, name):使用 encodeURIComponent 解码
    return (document.cookie =
      name + '=' + converter.write(value, name) + stringifiedAttributes)
  }
  1. 获取属性 attributes 对象
  2. 获取属性 expires (cookie 过期时间)转成 UTC String
  3. 将name encodeURIComponent 函数编码,防止 cookie 不受一些特殊字符的干扰
  4. 将 attributes(对象类型)转成 stringifiedAttributes(字符串类型,例如:“;expires=1;path=/”)
  5. 最后将name和value以及属性拼接成字符串并返回

get 函数

function get (name) {
    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.
		// 获取所有的cookie信息,并转成数组类型
    var cookies = document.cookie ? document.cookie.split('; ') : []
    var jar = {}
		// 循环 cookie,并将 cookie 转成 key:value
    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) {}
    }

    return name ? jar[name] : jar
  }
  1. 获取所有的cookie信息,并转成数组类型
  2. 循环 cookie,将 cookie 转成 key:value,存入到 jar 对象中
  3. 在 jar 找到 name,返回 name 的值

init 返回的函数

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) }
    }
  )
  1. remove 将 name 设置成空,设置成已过期
  2. 通过withAttributes这个函数,可以修改attributes
  3. 通过withConverter这个函数,可以修改converter
  4. set、get、remove、withAttributes、withConverter

最后

以上是我在学习 js-cookie 的知识点,我认为源码当中最巧妙的地方是在 init 函数返回时通过 object.create(),并且将修改 attributes和converter 设置两个函数。