每个人心里都有一团火,路过的人只看到烟。
——《至爱梵高·星空之谜》
本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash
gitbook也会同步仓库的更新,gitbook地址:pocket-lodash
在《lodash源码分析之Hash缓存》和《lodash源码分析之List缓存》介绍了 lodash 的两种缓存方式,这两种缓存方式都实现了和 一致
的数据管理接口,其中 缓存只
在不支持 的环
境中使用,那何时使用 缓存,
何时使用 或者
缓存呢
?这就是 类所需要做的事
情。
从之前的分析可以看出, 缓存完
全可以用 缓存或
者 来代
替,为什么 lodash 不干脆统一用一种缓存方式呢?
原因是在数据量较大时,对象的存取比 或者
数组的性能要好。
因此,ladash 在能够用 缓存时
,都尽量使用 缓存,
而能否使用 缓存的
关键是 的类
型。
以下便为 lodash 决定使用缓存方式的流程:
首先,判断 的类
型,以是否为 类型为成两拨,如果是以上的类型,再判断
是否
等于
,如果不是 ,则使用 缓存。
不能为 的原因是,大部分 JS
引擎都以这个属性来保存对象的原型。
如果不是以上的类型,则判断 是否
为 ,如果为
,则依
然使用 缓存,
其余的则使用 或者
缓存。
从上面的流程图还可以看到,在可以用 来缓存
的 中,
还以是否为 类型分成了
两个 对象来
缓存数据,为什么要这样呢?
我们都知道,对象的 如果
不是字符串或者 类型时,会
转换成字符串的形式,因此如果缓存的数据中同时存在像数字
和字符串 时,
数据都会储存在字符串 上。
这两个不同的键值,最后获取的都是同一份数据,这明显是不行的,因此需要将要字符串的 和其
他需要转换类型的 分开
两个 对象储
存。
所做的事情有点
像函数重载,其调用方式和 、
及
一致。
new MapCache([
['key', 'value'],
[{key: 'An Object Key'}, 1],
[Symbol(),2]
])
所返回的结果如下:
{
size: 3,
\_\_data\_\_: {
string: {
...
},
hash: {
...
},
map: {
...
}
}
}
可以看到, 里根据
的类
型分成了 、
类型来储存数据。其中 和
三种
和
,而 都是
的实例 则是
或 的实例。
同样实现了跟 一致
的数据管理接口,如下:
import Hash from './Hash.js'
import ListCache from './ListCache.js'
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
}
}
function isKeyable(value) {
const type = typeof value
return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
? (value !== '\_\_proto\_\_')
: (value === null)
}
这个函数用来判断是否使用 缓存。
返回 表示使
用 缓存,
返回 则使用 或者
缓存。
这个在流程图上已经解释过,不再作详细的解释。
function getMapData({ \_\_data\_\_ }, key) {
const data = \_\_data\_\_
return isKeyable(key)
? data[typeof key == 'string' ? 'string' : 'hash']
: data.map
}
这个函数根据 来获
取储存了该 的缓
存实例。
即为
实例中的
性的值。 属
如果使用的是 缓存,
则类型为字符串时,返回 中的
返回 属性的值,
否则 属性的
值。这两者都为 实例。
否则返回 属性
的值,这个可能是 实例
或者 实例。
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
}
}
是为了清
空缓存。
这里值得注意的是 属性,使用
存不同类型的缓存数据,它们之间的区别上面已经论述清楚。 、
和
来保
这里也可以清晰地看到,如果在支持 的环
境中,会优先使用 ,而
不是 。
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
}
用来
增加或者更新需要缓存的值。 的时
候需要同时维护 和缓存
的值。
这里除了调用对应的缓存实例的 方法
来维护缓存的值外,还需要维护自身的 属性,
如果增加值,则加
。
get(key) {
return getMapData(this, key).get(key)
}
方法
是从缓存中取值。
同样是调用对应的缓存实例中的 方法
。
delete(key) {
const result = getMapData(this, key)['delete'](key)
this.size -= result ? 1 : 0
return result
}
方法用来删
除指定 的缓
存。成功删除返回 , 否则
返回 。 删除操
作同样需要维护 属性。
同样是调用对应缓存实例中的 方法,如果
删除成功,则需要将自身的 的值减
少
。
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
作者:对角另一面
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。