每个人心里都有一团火,路过的人只看到烟。

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

本文为读 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 决定使用缓存方式的流程:

首先,判断 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 一致的数据管理接口,如下:

依赖

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源码分析之缓存方式的选择的更多相关文章

  1. lodash源码分析之缓存使用方式的进一步封装

    在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...

  2. lodash源码分析之数组的差集

    外部世界那些破旧与贫困的样子,可以使我内心世界得到平衡. --卡尔维诺<烟云> 本文为读 lodash 源码的第十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodas ...

  3. lodash源码分析之List缓存

    昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方 顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免/因为风的缘故 --洛夫<因为风的缘故& ...

  4. jQuery1.9.1源码分析--数据缓存Data模块

    jQuery1.9.1源码分析--数据缓存Data模块 阅读目录 jQuery API中Data的基本使用方法介绍 jQuery.acceptData(elem)源码分析 jQuery.data(el ...

  5. lodash源码分析之自减的两种形式

    这个世界需要一个特定的恶人,可以供人们指名道姓,千夫所指:"全都怪你". --村上春树<当我谈跑步时我谈些什么> 本文为读 lodash 源码的第六篇,后续文章会更新到 ...

  6. lodash源码分析之baseFindIndex中的运算符优先级

    我悟出权力本来就是不讲理的--蟑螂就是海米:也悟出要造反,内心必须强大到足以承受任何后果才行. --北岛<城门开> 本文为读 lodash 源码的第十篇,后续文章会更新到这个仓库中,欢迎 ...

  7. 【转】MaBatis学习---源码分析MyBatis缓存原理

    [原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...

  8. 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的 | 百篇博客分析OpenHarmony源码 | v20.04

    百篇博客系列篇.本篇为: v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的 | 51.c.h .o 精读内核源码就绕不过汇编语言,鸿蒙内核有6个汇编文件,读不懂它们就真的很难理解 ...

  9. lodash源码分析之Hash缓存

    在那小小的梦的暖阁,我为你收藏起整个季节的烟雨. --洛夫<灵河> 本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbo ...

随机推荐

  1. JavaScript 中 闭包 的详解

    闭包是什么 在 JavaScript 中,闭包是一个让人很难弄懂的概念.ECMAScript 中给闭包的定义是:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量. ...

  2. iOS 页面之间的专长动画控制器间的转换

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 24.0px; font: 14.0px "Heiti SC Light" ...

  3. http中的get和post(二)

    博客园精华区有篇文章< GET 和 POST 有什么区别?及为什么网上的多数答案都是错的 >,文中和回复多是对以下两个问题进行了深究: 长度限制 Url 是否隐藏数据 在我看来这两者都不是 ...

  4. iOS 如何优化 App 的启动时间

    App 运行理论 main() 执行前发生的事 Mach-O 格式 虚拟内存基础 Mach-O 二进制的加载 理论速成 Mach-O 术语 Mach-O 是针对不同运行时可执行文件的文件类型. 文件类 ...

  5. 转:IT巨头纷纷“卡位” 智能语音成人机交互入口必争之地

    http://www.cs.com.cn/xwzx/hwxx/201707/t20170712_5368595.html 随着物联网的迅速发展,作为重要接口的智能语音技术已成为国内外IT巨头的必争之地 ...

  6. 历年NOIP选题题解汇总

    联赛前上vijos板刷往年联赛题,使用在线编辑编写代码,祝我rp++. 废话不多说,挑比较有意思的记一下. 题目是按照年份排序的,最早只到了03年. 有些题目因为 我还没写/很早之前写的忘了 所以就没 ...

  7. ActiveMQ (二) 常用配置简介

    ActiveMQ的主要配置文件 ActiveMQ的一些常用的属性很多可以在对应的配置文件中进行配置的.比如访问web console的管理端的端口,用户名密码,连接MQ时的用户名和密码,持久化设置,是 ...

  8. touch监听判断手指的上滑,下滑,左滑,右滑,事件监听

    判断滑动的方向和距离,来实现一定的效果,比如返回上一页等等 <body> <script> $(function(){ //给body强制定义高度 var windowHeig ...

  9. 我知道你不知道的负Margin

    现如今,负margin技术的应用可谓越来越广,任一个大型站点惊鸿一瞥之下都会有其身影所在.个人认为负margin技术是学习css路上必不可缺少的课题之一,许多高级应用及疑难杂症修复都可以使用负marg ...

  10. Jupyter Notebook使用小技巧

    在 C:\Windows\Fonts目录下找到Mircosoft YaHei UI字体,然后复制到[你的Python安装路径]/Lib/site-packages/matplotlib/mpl-dat ...