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

131 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。

这是源码共读的第xx期,链接: juejin.cn/post/708314…

git路径:git clone github.com/js-cookie/j…

Cookies.set('name', 'value')   //创建Cookie
Cookies.set('name', 'value', { expires: 7, path: '' })   //从现在起 7 天后过期的 Cookie
 
Cookies.get('name') // => 'value'    //读取cookies
Cookies.get('nothing') // => undefined
Cookies.get('foo', { domain: 'sub.example.com' }) // 域名
Cookies.remove('name')  //删除cookies

//删除对当前页面路径有效的 Cookie:
Cookies.set('name', 'value', { path: '' })
Cookies.remove('name') // fail!
Cookies.remove('name', { path: '' }) // removed!

//命名空间冲突
var Cookies2 = Cookies.noConflict()
Cookies2.set('name', 'value')

const api = Cookies.withAttributes({ path: '/', domain: '.example.com' })  //设置默认值

源代码

/* eslint-disable no-var */
import assign from './assign.mjs'
import defaultConverter from './converter.mjs'

function init (converter, defaultAttributes) {
  function set (name, value, attributes) {
    //判断document是否可用
    if (typeof document === 'undefined') {
      return
    }

    //合并配置
    attributes = assign({}, defaultAttributes, attributes)

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

    // 对cookie的key进行编码密
    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
      }

      //增加配置的key值
      stringifiedAttributes += '; ' + attributeName

      //存在设置配置为true的情况
      if (attributes[attributeName] === true) {
        continue
      }

      //增加配置的value值
      stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]  // 防止value有;的情况
    }

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

  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.
    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) {}
    }

    return name ? jar[name] : jar
  }

  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) }
    }
  )
}

export default init(defaultConverter, { path: '/' })
/* eslint-enable no-var */
/* 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 */
/* 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 */

解析

默认进入api.mjs,执行init方法,并返回一个对象,包含set、get等方法。

set

function set (name, value, attributes) {
    //判断document是否可用
    if (typeof document === 'undefined') {
      return
    }

    //合并配置
    attributes = assign({}, defaultAttributes, attributes)

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

    // 对cookie的key进行编码密
    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
      }

      //增加配置的key值
      stringifiedAttributes += '; ' + attributeName

      //存在设置配置为true的情况
      if (attributes[attributeName] === true) {
        continue
      }

      //增加配置的value值
      stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]  // 防止value有;的情况
    }

  	//set值并保存在document.cookie中
    return (document.cookie =
      name + '=' + converter.write(value, name) + stringifiedAttributes)
  }

converter.write

  write: function (value) {
  	//value加密并返回
    return encodeURIComponent(value).replace(
      /%(2[346BF]|3[AC-F
      ]|40|5[BDE]|60|7[BCD])/g,
      decodeURIComponent
    )
  }

get

  function get (name) {
  	//document存在或则和入参存在
    if (typeof document === 'undefined' || (arguments.length && !name)) {
      return
    }

    //获得所有cookie并根据; 拆分成数组
    var cookies = document.cookie ? document.cookie.split('; ') : []
    var jar = {}
    for (var i = 0; i < cookies.length; i++) {
      //根据=拆分key和value
      var parts = cookies[i].split('=')
      var value = parts.slice(1).join('=')

      try {
        //将name解密
        var found = decodeURIComponent(parts[0])
        //利用name与得到解密后的value,存入全局对象jar中
        jar[found] = converter.read(value, found)
      	//找到key对应的值,退出
        if (name === found) {
          break
        }
      } catch (e) {}
    }
  	//找到了返回值或者没找到返回空对象
    return name ? jar[name] : jar
  }

converter.read

read: function (value) {
    if (value[0] === '"') {
      value = value.slice(1, -1)
    }
  	//返回value
    return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
  },

return Object.create

 return Object.create(
    {
      set: set,
      get: get,
      remove: function (name, attributes) {
        set(
          name,
          '',
          assign({}, attributes, {
            expires: -1
          })   //清空cookie的值,即设置过期的时间
        )
      },	
    	// 设置全局默认项
      withAttributes: function (attributes) {
        return init(this.converter, assign({}, this.attributes, attributes))
      },
    	//设置更新默认转换器
      withConverter: function (converter) {
        return init(assign({}, this.converter, converter), this.attributes)
      }
    },
    {
    	//Object.freeze  不可修改
      attributes: { value: Object.freeze(defaultAttributes) },
      converter: { value: Object.freeze(converter) }
    }
  )

defaultConverter

/* 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 */

总结

set和get转换器处用的很妙