We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
vue.js入坑也有了小半年的时间了,圈子里一直流传着其源码优雅、简洁的传说。 最近的一次技术分享会,同事分享vue.js源码的缓存部分,鄙人将其整理出来,与大家一起学习
vue.js
首先我们来看一下链表的定义:
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)
其中的双向链表是我们今天的主角:
双向链表也叫双链表。双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针。这样可以从任何一个节点访问前一个节点,当然也可以访问后一个节点,以至整个链表。一般是在需要大批量的另外储存数据在链表中的位置的时候用。 维基百科-链表
想象一群人手拉手站成一排,除了队头跟队尾,可以根据每个人的左手以及右手找到排在其左边或者右边的人,这也可以看成一种双向链表
在JavaScript中,我们可以通过对象的属性来实现双向链表。
JavaScript
而在vue.js中,作者正是利用类似双向链表的方式实现缓存的利用
在缓存中,利用类似双向链表来管理缓存并不难的。难的是如何更加高效的管理缓存,如何在缓存达到其最大内存空间,删除程序中最不常用的变量,而不是随机删除,造成最常用的变量被误删的情况。
vue.js中采用LRU算法来实现缓存的高效管理。
LRU算法
LRU是Least Recently Used的简称,具体内容可以查看GitHub,其有以下优点:
LRU
Least Recently Used
entry
head
tail
newer entry
older entry
key
o(1)
可以通过下面的图来更好的理解LRU算法:
entry entry entry entry ______ ______ ______ ______ | head |.newer => | |.newer => | |.newer => | tail | | A | | B | | C | | D | |______| <= older.|______| <= older.|______| <= older.|______| removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
如果缓存达到最大,那么每次只需要将head删除就行了,保证了删除的项是最不常用的项
还是拿站成一排的人来举例。
有两个指示牌,上面分别写着tail以及head。head指向队伍的第一个人,tail指向队伍的最后一个人。
假设队伍有10个人,按照队伍的排列从队首到队尾依次编号a b c d ··· j,head指向a,tail指向j。
a b c d ··· j
a
j
下面分成五种情况来说明队伍的变化:
b
b c d ··· i
k
作者在caches对象的_keymap里面保存所需要缓存的变量,通过older以及newer这两个属性来实现双向链表。older指向其前一个对象,newer指向其后一个对象。通过这两个属性,将缓存中的变量连接起来。
caches
_keymap
older
newer
以上图举例: 缓存caches这个对象中保存了三个变量:key1、key2、key3。
key1
key2
key3
header
指向如下:
key1 key2 key3 ______ ______ ______ | head |.newer => | |.newer => | tail | | | | | | | |______| <= older.|______| <= older.|______|
下面我们来看作者对这些数据的处理所使用的方法
文件位置:src/cache.js
src/cache.js
首先export构造函数Cache
export
Cache
export default function Cache (limit) { // 标识当前缓存数组的大小 this.size = 0 // 标识缓存数组能达到的最大长度 this.limit = limit // head(最不常用的项),tail(最常用的项)全部初始化为undefined this.head = this.tail = undefined this._keymap = Object.create(null) }
接下来作者在Cache的原型链上面分别定义了:
put
key-value
undefined
shift
get
a) get:
Cache.prototype.get = function (key, returnEntry) { var entry = this._keymap[key] // 如果查找不到含有`key`这个属性的缓存对象 if (entry === undefined) return // 如果查找到的缓存对象已经是 tail (最近使用过的) if (entry === this.tail) { return returnEntry ? entry : entry.value } // HEAD--------------TAIL // <.older .newer> // <--- add direction -- // A B C <D> E if (entry.newer) { // 处理 newer 指向 if (entry === this.head) { // 如果查找到的缓存对象是 head (最近最少使用过的) // 则将 head 指向原 head 的 newer 所指向的缓存对象 this.head = entry.newer } // 将所查找的缓存对象的下一级的 older 指向所查找的缓存对象的older所指向的值 // 例如:A B C D E // 如果查找到的是D,那么将E指向C,不再指向D entry.newer.older = entry.older // C <-- E. } if (entry.older) { // 处理 older 指向 // 如果查找到的是D,那么C指向E,不再指向D entry.older.newer = entry.newer // C. --> E } // 处理所查找到的对象的 newer 以及 older 指向 entry.newer = undefined // D --x // older指向之前使用过的变量,即D指向E entry.older = this.tail // D. --> E if (this.tail) { // 将E的newer指向D this.tail.newer = entry // E. <-- D } // 改变 tail 为D this.tail = entry return returnEntry ? entry : entry.value }
b) put:
Cache.prototype.put = function (key, value) { var removed var entry = this.get(key, true) // 如果不存在 key 这样属性的缓存对象,才能调用 put 方法 if (!entry) { if (this.size === this.limit) { // 如果缓存数组达到上限,则先删除 head 指向的缓存对象 removed = this.shift() } // 初始化赋值 entry = { key: key } this._keymap[key] = entry if (this.tail) { // 如果存在tail(缓存数组的长度不为0),将tail指向新的 entry this.tail.newer = entry entry.older = this.tail } else { // 如果缓存数组的长度为0,将head指向新的entry this.head = entry } this.tail = entry this.size++ } entry.value = value return removed }
c) shift:
Cache.prototype.shift = function () { var entry = this.head if (entry) { // 删除 head ,并改变指向 this.head = this.head.newer this.head.older = undefined entry.newer = entry.older = undefined // 同步更新 _keymap 里面的属性值 this._keymap[entry.key] = undefined // 同步更新 缓存数组的长度 this.size-- } return entry }
从整个的代码来看,需要学习的不仅仅是LRU算法,作者的对于Object的处理方式也值的我们评味一番。
Object
没有选择去遍历entry,选择通过在Cache内增加一个_keymap属性,通过这个属性来管理entry,实现key与newer、older状态的分离,减少代码的复杂度
v1.0.26
The text was updated successfully, but these errors were encountered:
issues写blog可以的,感觉很方便啊
Sorry, something went wrong.
No branches or pull requests
一、从链表说起
首先我们来看一下链表的定义:
其中的双向链表是我们今天的主角:
想象一群人手拉手站成一排,除了队头跟队尾,可以根据每个人的左手以及右手找到排在其左边或者右边的人,这也可以看成一种双向链表
在
JavaScript
中,我们可以通过对象的属性来实现双向链表。而在
vue.js
中,作者正是利用类似双向链表的方式实现缓存的利用二、LRU算法
在缓存中,利用类似双向链表来管理缓存并不难的。难的是如何更加高效的管理缓存,如何在缓存达到其最大内存空间,删除程序中最不常用的变量,而不是随机删除,造成最常用的变量被误删的情况。
vue.js
中采用LRU算法
来实现缓存的高效管理。LRU
是Least Recently Used
的简称,具体内容可以查看GitHub,其有以下优点:entry
的排序,复杂度低head
(最近最少使用的项)和一个tail
(最近最多使用的项)head
和tail
都是entry
,一个entry
可能会有一个newer entry
以及一个older entry
(双向链接,older entry
更接近head
,newer entry
更接近tail
)key
就可以遍历这个缓存对象,也就意味着只有o(1)
的复杂度,内存消耗非常小可以通过下面的图来更好的理解
LRU算法
:如果缓存达到最大,那么每次只需要将
head
删除就行了,保证了删除的项是最不常用的项还是拿站成一排的人来举例。
有两个指示牌,上面分别写着
tail
以及head
。head
指向队伍的第一个人,tail
指向队伍的最后一个人。假设队伍有10个人,按照队伍的排列从队首到队尾依次编号
a b c d ··· j
,head
指向a
,tail
指向j
。下面分成五种情况来说明队伍的变化:
a
(使用了数组里面第一个变量),就将a
放到队尾,再手拉手重新组成一个新的队伍。并将原来指向j
的tail
现在指向a
。再让原来指向a
的head
指向现在队伍的第一个人b
b c d ··· i
之间任何一个人,则将其从队伍中抽出,放到队尾,重新排队,再改变tail
的指向为这个人j
,则保持队伍不变head
指向的编号a
,并改变head
指向编号b
,再在队尾增加一个人,假定编号为k
,最后则将tail
指向编号k
k
的人。再将tail
指向编号k
三、源码分析
作者在
caches
对象的_keymap
里面保存所需要缓存的变量,通过older
以及newer
这两个属性来实现双向链表。older
指向其前一个对象,newer
指向其后一个对象。通过这两个属性,将缓存中的变量连接起来。以上图举例:
缓存
caches
这个对象中保存了三个变量:key1
、key2
、key3
。header
指向key1
tail
指向key2
指向如下:
下面我们来看作者对这些数据的处理所使用的方法
文件位置:
src/cache.js
首先
export
构造函数Cache
接下来作者在
Cache
的原型链上面分别定义了:put
:在缓存中加入一个key-value
对象,如果缓存数组已经达到最大值,则返回被删除的entry
,即head
,否则返回undefined
shift
:在缓存数组中移除最少使用的entry
,即head
,返回被删除的entry
。如果缓存数组为空,则返回undefined
get
:将key
为传入参数的缓存对象标识为最常使用的entry
,即tail
,并调整双向链表,返回改变后的tail
。如果不存在key
为传入参数的缓存对象,则返回undefined
a)
get
:b)
put
:c)
shift
:四、后记
从整个的代码来看,需要学习的不仅仅是
LRU算法
,作者的对于Object
的处理方式也值的我们评味一番。没有选择去遍历
entry
,选择通过在Cache
内增加一个_keymap
属性,通过这个属性来管理entry
,实现key
与newer
、older
状态的分离,减少代码的复杂度五、附
v1.0.26
The text was updated successfully, but these errors were encountered: