lodash源码分析之Stack
本文为读 lodash 源码的第一百八十一篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash
gitbook也会同步仓库的更新,gitbook地址:pocket-lodash
依赖
import ListCache from './ListCache.js'
import MapCache from './MapCache.js'
源码分析
lodash
中设计了 Hash
、Map
和 List
等数据结构来做缓存。
其中 Hash
本质上是使用对象来做缓存,这会使得 Hash
会在一些场景上会有使用限制,只能使用字符串或者 Symbol
来做 key
。但是 Map
和 List
则没有这些限制。
Stack
所做的,其实是动态切换 Map
和 List
缓存,因为 List
本质上是使用数组对来进行缓存,当缓存比较大时,会有性能问题,因此在缓存长度超过一定数量时,会切换到 Map
缓存。
Stack
所提供的接口和 Map
及 List
一致。
源码如下:
const LARGE_ARRAY_SIZE = 200
class Stack {
constructor(entries) {
const data = this.__data__ = new ListCache(entries)
this.size = data.size
}
clear() {
this.__data__ = new ListCache
this.size = 0
}
delete(key) {
const data = this.__data__
const result = data['delete'](key)
this.size = data.size
return result
}
get(key) {
return this.__data__.get(key)
}
has(key) {
return this.__data__.has(key)
}
set(key, value) {
let data = this.__data__
if (data instanceof ListCache) {
const pairs = data.__data__
if (pairs.length < LARGE_ARRAY_SIZE - 1) {
pairs.push([key, value])
this.size = ++data.size
return this
}
data = this.__data__ = new MapCache(pairs)
}
data.set(key, value)
this.size = data.size
return this
}
}
constructor
constructor(entries) {
const data = this.__data__ = new ListCache(entries)
this.size = data.size
}
传入的是[[key, value], ...]
这样形式的数组,初始化一个 ListCache
实例,用私有属性 __data__
保存 ListCache
实例,后面可能会切换成 MapCache
实例。
用 size
来保存缓存的长度。
clear
clear() {
this.__data__ = new ListCache
this.size = 0
}
clear
方法来清空缓存。
其实就是新创建一个 ListCache
实例,但是没有传入任何参数给 constructor
。
size
也重置为 0
。
delete
delete(key) {
const data = this.__data__
const result = data['delete'](key)
this.size = data.size
return result
}
其实就是调用 ListCache
或者 MapCache
上的 delete
方法,删除对应的 key
。
在删除的同时,也更新 size
。
get
get(key) {
return this.__data__.get(key)
}
获取缓存上的值,也是简单的方法委托。
has
has(key) {
return this.__data__.has(key)
}
判断某个 key
是否已有缓存,也是简单的方法委托。
set
Stack
最主要的是 set
的方法,就是在这个方法里实现了 List
和 Set
数据结构的切换。
源码如下:
set(key, value) {
let data = this.__data__
if (data instanceof ListCache) {
const pairs = data.__data__
if (pairs.length < LARGE_ARRAY_SIZE - 1) {
pairs.push([key, value])
this.size = ++data.size
return this
}
data = this.__data__ = new MapCache(pairs)
}
data.set(key, value)
this.size = data.size
return this
}
首先判断 data
是否为 ListCache
的实例,如果是 ListCache
的实例,则从实例的私有属性 __data__
里取出原始数据 pairs
,pairs
的形式如:[[key, value],...]
实现数据结构的切换关键在于 pairs
的长度,lodash
这里设定的长度为 LARGE_ARRAY_SIZE - 1
,即在小于 199
时,会使得 ListCache
。
如果使用的是 ListCache
,则将 [key, value]
直接 push
进 pairs
里即可,同时更新 size
和 ListCache
实例的 size
。即 this.size = ++data.size
。
这里不直接使用 ListCache
的实例的 set
方法,我猜应该是出于性能的考虑,因为 ListCache
实例的 set
方法会做重复性检测,这会损耗一部分性能。而 Stack
是内部方法,在使用的时候会避免重复,因此在 set
的时候不需要这部分检测。
否则就切换成 MapCache
结构,构建实例时将 pairs
传给 MapCache
的构造方法。
接下来就是用 MapCache
实例的 set
方法来设置 key
和 value
,然后再更新 size
。
题外话
Stack
手动来管理 size
,而且在多个地方都要更新 size
,其实可以利用 getter
来避免手动管理。
代码如下:
class Stack {
get size () {
return this.__data__.size
}
}
License
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
作者:对角另一面