lodash源码剖析之缓存体式格局的挑选

每个人内心都有一团火,途经的人只看到烟。

——《至爱梵高·星空之谜》

本文为读 lodash 源码的第八篇,后续文章会更新到这个堆栈中,迎接 star:pocket-lodash

gitbook也会同步堆栈的更新,gitbook地点:pocket-lodash

媒介

在《lodash源码剖析之Hash缓存》和《lodash源码剖析之List缓存》引见了 lodash 的两种缓存体式格局,这两种缓存体式格局都完成了和 Map 一致的数据管理接口,个中 List 缓存只在不支撑 Map 的环境中运用,那什么时刻运用 Hash 缓存,什么时刻运用 Map 或许 List 缓存呢?这就是 MapCache 类所须要做的事变。

缓存体式格局的挑选

从之前的剖析能够看出,Hash 缓存完整能够用 List 缓存或许 Map 来替代,为何 lodash 不痛快统一用一种缓存体式格局呢?

原因是在数据量较大时,对象的存取比 Map 或许数组的机能要好。

因而,ladash 在能够用 Hash 缓存时,都只管运用 Hash 缓存,而可否运用 Hash 缓存的关键是 key 的范例。

以下便为 lodash 决议运用缓存体式格局的流程:

《lodash源码剖析之缓存体式格局的挑选》

起首,推断 key 的范例,以是不是为 string/number/symbol/boolean 范例为成两拨,假如是以上的范例,再推断 key 是不是即是 __proto__ ,假如不是 __proto__ ,则运用 Hash 缓存。不能为 __proto__ 的原因是,大部分 JS 引擎都以这个属性来保留对象的原型。

假如不是以上的范例,则推断 key 是不是为 null,假如为 null ,则依旧运用 Hash 缓存,其他的则运用 Map 或许 List 缓存。

从上面的流程图还能够看到,在能够用 Hash 来缓存的 key 中,还以是不是为 string 范例分成了两个 Hash 对象来缓存数据,为何要如许呢?

我们都晓得,对象的 key 假如不是字符串或许 Symbol 范例时,会转换成字符串的情势,因而假如缓存的数据中同时存在像数字 1 和字符串 '1' 时,数据都邑贮存在字符串 '1' 上。这两个差别的键值,末了猎取的都是统一份数据,这显著是不可的,因而须要将要字符串的 key 和其他须要转换范例的 key 离开两个 Hash 对象贮存。

作用与用法

MapCache 所做的事变有点像函数重载,其挪用体式格局和 HashMapListCache 一致。

new MapCache([
  ['key', 'value'],
  [{key: 'An Object Key'}, 1],
  [Symbol(),2]
])

所返回的效果以下:

{
  size: 3,
  __data__: {
    string: {
      ... 
    },
    hash: {
      ...
    },
    map: {
      ...  
    }
  }
}

能够看到,__data__ 里依据 key 的范例分成了 stringhashmap 三种范例来贮存数据。个中 stringhash 都是 Hash 的实例,而 map 则是 mapListCache 的实例。

接口设想

MapCache 一样完成了跟 Map 一致的数据管理接口,以下:

《lodash源码剖析之缓存体式格局的挑选》

依靠

import Hash from './Hash.js'
import ListCache from './ListCache.js'

lodash源码剖析之Hash缓存

lodash源码剖析之List缓存

源码剖析

function getMapData({ __data__ }, key) {
  const data = __data__
  return isKeyable(key)
    ? data[typeof key == 'string' ? 'string' : 'hash']
    : data.map
}

function isKeyable(value) {
  const type = typeof value
  return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
    ? (value !== '__proto__')
    : (value === null)
}

class MapCache {

  constructor(entries) {
    let index = -1
    const length = entries == null ? 0 : entries.length

    this.clear()
    while (++index < length) {
      const entry = entries[index]
      this.set(entry[0], entry[1])
    }
  }

  clear() {
    this.size = 0
    this.__data__ = {
      'hash': new Hash,
      'map': new (Map || ListCache),
      'string': new Hash
    }
  }

  delete(key) {
    const result = getMapData(this, key)['delete'](key)
    this.size -= result ? 1 : 0
    return result
  }

  get(key) {
    return getMapData(this, key).get(key)
  }

  has(key) {
    return getMapData(this, key).has(key)
  }

  set(key, value) {
    const data = getMapData(this, key)
    const size = data.size

    data.set(key, value)
    this.size += data.size == size ? 0 : 1
    return this
  }
}

是不是运用Hash

function isKeyable(value) {
  const type = typeof value
  return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
    ? (value !== '__proto__')
  : (value === null)
}

这个函数用来推断是不是运用 Hash 缓存。返回 true 示意运用 Hash 缓存,返回 false 则运用 Map 或许 ListCache 缓存。

这个在流程图上已诠释过,不再作细致的诠释。

猎取对应缓存体式格局的实例

function getMapData({ __data__ }, key) {
  const data = __data__
  return isKeyable(key)
    ? data[typeof key == 'string' ? 'string' : 'hash']
    : data.map
}

这个函数依据 key 来猎取贮存了该 key 的缓存实例。

__data__ 即为 MapCache 实例中的 __data__ 属性的值。

假如运用的是 Hash 缓存,则范例为字符串时,返回 __data__ 中的 string 属性的值,不然返回 hash 属性的值。这两者都为 Hash 实例。

不然返回 map 属性的值,这个多是 Map 实例或许 ListCache 实例。

constructor

constructor(entries) {
  let index = -1
  const length = entries == null ? 0 : entries.length

  this.clear()
  while (++index < length) {
    const entry = entries[index]
    this.set(entry[0], entry[1])
  }
}

组织器跟 HashListCache 如出一辙,都是先挪用 clear 要领,然后挪用 set 要领,往缓存中到场初始数据。

clear

clear() {
  this.size = 0
  this.__data__ = {
    'hash': new Hash,
    'map': new (Map || ListCache),
    'string': new Hash
  }
}

clear 是为了清空缓存。

这里值得注意的是 __data__ 属性,运用 hashstringmap 来保留差别范例的缓存数据,它们之间的区分上面已叙述清楚。

这里也能够清楚地看到,假如在支撑 Map 的环境中,会优先运用 Map ,而不是 ListCache

has

has(key) {
  return getMapData(this, key).has(key)
}

has 用来推断是不是已有缓存数据,假如缓存数据已存在,则返回 true

这里挪用了 getMapData 要领,猎取到对应的缓存实例(HashMap 或许 ListCache 的实例),然后挪用的是对应实例中的 has 要领。

set

set(key, value) {
  const data = getMapData(this, key)
  const size = data.size

  data.set(key, value)
  this.size += data.size == size ? 0 : 1
  return this
}

set 用来增添或许更新须要缓存的值。set 的时刻须要同时保护 size 和缓存的值。

这里除了挪用对应的缓存实例的 set 要领来保护缓存的值外,还须要保护本身的 size 属性,假如增添值,则加 1

get

get(key) {
  return getMapData(this, key).get(key)
}

get 要领是从缓存中取值。

一样是挪用对应的缓存实例中的 get 要领。

delete

delete(key) {
  const result = getMapData(this, key)['delete'](key)
  this.size -= result ? 1 : 0
  return result
}

delete 要领用来删除指定 key 的缓存。胜利删除返回 true, 不然返回 false。 删除操纵一样须要保护 size 属性。

一样是挪用对应缓存实例中的 delete 要领,假如删除胜利,则须要将本身的 size 的值削减 1

参考

License

签名-非商业性运用-制止归纳 4.0 国际 (CC BY-NC-ND 4.0)

末了,一切文章都邑同步发送到微信民众号上,迎接关注,迎接提意见: 《lodash源码剖析之缓存体式格局的挑选》

作者:对角另一面

    原文作者:对角另一面
    原文地址: https://segmentfault.com/a/1190000012928395
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞