每一个人内心都有一团火,路过的人只看到烟。javascript
——《至爱梵高·星空之谜》java
本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodashgit
gitbook也会同步仓库的更新,gitbook地址:pocket-lodashgithub
在《lodash源码分析之Hash缓存》和《lodash源码分析之List缓存》介绍了 lodash 的两种缓存方式,这两种缓存方式都实现了和 Map
一致的数据管理接口,其中 List
缓存只在不支持 Map
的环境中使用,那什么时候使用 Hash
缓存,什么时候使用 Map
或者 List
缓存呢?这就是 MapCache
类所须要作的事情。数组
从以前的分析能够看出,Hash
缓存彻底能够用 List
缓存或者 Map
来代替,为何 lodash 不干脆统一用一种缓存方式呢?缓存
缘由是在数据量较大时,对象的存取比 Map
或者数组的性能要好。微信
所以,ladash 在可以用 Hash
缓存时,都尽可能使用 Hash
缓存,而可否使用 Hash
缓存的关键是 key
的类型。函数
如下便为 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
所作的事情有点像函数重载,其调用方式和 Hash
、Map
及 ListCache
一致。
new MapCache([
['key', 'value'],
[{key: 'An Object Key'}, 1],
[Symbol(),2]
])
复制代码
所返回的结果以下:
{
size: 3,
__data__: {
string: {
...
},
hash: {
...
},
map: {
...
}
}
}
复制代码
能够看到,__data__
里根据 key
的类型分红了 string
、hash
和 map
三种类型来储存数据。其中 string
和 hash
都是 Hash
的实例,而 map
则是 map
或 ListCache
的实例。
MapCache
一样实现了跟 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)
}
复制代码
这个函数用来判断是否使用 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(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])
}
}
复制代码
构造器跟 Hash
和 ListCache
如出一辙,都是先调用 clear
方法,而后调用 set
方法,往缓存中加入初始数据。
clear() {
this.size = 0
this.__data__ = {
'hash': new Hash,
'map': new (Map || ListCache),
'string': new Hash
}
}
复制代码
clear
是为了清空缓存。
这里值得注意的是 __data__
属性,使用 hash
、string
和 map
来保存不一样类型的缓存数据,它们之间的区别上面已经论述清楚。
这里也能够清晰地看到,若是在支持 Map
的环境中,会优先使用 Map
,而不是 ListCache
。
has(key) {
return getMapData(this, key).has(key)
}
复制代码
has
用来判断是否已经有缓存数据,若是缓存数据已经存在,则返回 true
。
这里调用了 getMapData
方法,获取到对应的缓存实例(Hash
、Map
或者 ListCache
的实例),而后调用的是对应实例中的 has
方法。
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(key) {
return getMapData(this, key).get(key)
}
复制代码
get
方法是从缓存中取值。
一样是调用对应的缓存实例中的 get
方法。
delete(key) {
const result = getMapData(this, key)['delete'](key)
this.size -= result ? 1 : 0
return result
}
复制代码
delete
方法用来删除指定 key
的缓存。成功删除返回 true
, 不然返回 false
。 删除操做一样须要维护 size
属性。
一样是调用对应缓存实例中的 delete
方法,若是删除成功,则须要将自身的 size
的值减小 1
。
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
做者:对角另外一面