昨日我沿著河岸/漫步到/蘆葦彎腰喝水的地方
順便請煙囪/在天空為我寫一封長長的信
潦是潦草了些/而我的心意/則明亮亦如你窗前的燭光/稍有曖昧之處/勢所難免/因為風的緣故
——洛夫《因為風的緣故》
本文為讀 lodash 原始碼的第七篇,後續文章會更新到這個倉庫中,歡迎 star:pocket-lodash
gitbook也會同步倉庫的更新,gitbook地址:pocket-lodash
作用與用法
在之前的《lodash原始碼分析之Hash快取》介紹過用 Hash
做快取的情況,在這篇文章中介紹過,lodash 是想要實現和 Map
一樣的介面。
Hash
其實是用物件來做快取,但是物件有一個侷限,它的 key
只能是字串或者 Symbol
型別,但是 Map
是支援各種型別的值來作為 key
,因此 Hash
快取無法完全模擬 Map
的行為,當遇到 key
為陣列、物件等型別時,Hash
就無能為力了。
因此,在不支援 Map
的環境下,lodash
實現了 ListCache
來模擬,ListCache
本質上是使用一個二維陣列來儲存資料。
ListCache
的呼叫方式和 Hash
一致:
new ListCache([
[{key: 'An Object Key'}, 1],
[['An Array Key'],2],
[function(){console.log('A Function Key')},3]
])
複製程式碼
返回的結果如下:
{
size: 3,
__data__: [
[{key: 'An Object Key'}, 1],
[['An Array Key'],2],
[function(){console.log('A Function Key')},3]
]
}
複製程式碼
結構和 Hash
類似,但是 __data__
變成了陣列。
介面設計
ListCache
的介面與 Hash
一樣,同樣實現了 Map
的資料管理介面。
依賴
import assocIndexOf from './assocIndexOf.js'
複製程式碼
原始碼分析
class 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])
}
}
clear() {
this.__data__ = []
this.size = 0
}
delete(key) {
const data = this.__data__
const index = assocIndexOf(data, key)
if (index < 0) {
return false
}
const lastIndex = data.length - 1
if (index == lastIndex) {
data.pop()
} else {
data.splice(index, 1)
}
--this.size
return true
}
get(key) {
const data = this.__data__
const index = assocIndexOf(data, key)
return index < 0 ? undefined : data[index][1]
}
has(key) {
return assocIndexOf(this.__data__, key) > -1
}
set(key, value) {
const data = this.__data__
const index = assocIndexOf(data, key)
if (index < 0) {
++this.size
data.push([key, value])
} else {
data[index][1] = value
}
return this
}
}
複製程式碼
constructor
構造器跟 Hash
一模一樣,都是先呼叫 clear
方法,然後呼叫 set
方法,往快取中加入初始資料。
這裡呼叫 clear
方法並不是說為了清除資料,還沒開始使用這個類,肯定是沒有資料的,而是為了初始化 __data__
和 size
這兩個屬性。
clear
clear() {
this.__data__ = []
this.size = 0
}
複製程式碼
clear
是為了清空快取。
其實就是將容器 __data__
設定成空陣列,在 Hash
中是設定為空物件,將快取數量 size
設定為 0
。
has
has(key) {
return assocIndexOf(this.__data__, key) > -1
}
複製程式碼
has
用來判斷是否已經有快取資料,如果快取資料已經存在,則返回 true
。
在之前的文章中已經介紹過,assocIndexOf
檢測的是對應 key
的 [key,value]
陣列在二維陣列中的索引,其行為跟 indexOf
一致,不存在於二維陣列中時,返回 -1
,否則返回索引值。因此可以用是否大於 -1
來判斷指定 key
的資料是否已經被快取。
set
set(key, value) {
const data = this.__data__
const index = assocIndexOf(data, key)
if (index < 0) {
++this.size
data.push([key, value])
} else {
data[index][1] = value
}
return this
}
複製程式碼
set
用來增加或者更新需要快取的值。set
的時候需要同時維護 size
和快取的值。
跟 has
一樣,呼叫 assocIndexOf
找到指定 key
的索引值,如果小於 0
,則表明指定的 key
尚未快取,需要將快取數量 size
加 1
,然後將快取資料加入到 this.__data__
的末尾。
否則更新 value
即可。
強迫症看到 has
用大於 -1
來判斷,而這裡用小於 0
來判斷可能會相當難受。
get
get(key) {
const data = this.__data__
const index = assocIndexOf(data, key)
return index < 0 ? undefined : data[index][1]
}
複製程式碼
get
方法是從快取中取值。
如果快取中存在值,則返回快取中的值,否則返回 undefined
。
delete
delete(key) {
const data = this.__data__
const index = assocIndexOf(data, key)
if (index < 0) {
return false
}
const lastIndex = data.length - 1
if (index == lastIndex) {
data.pop()
} else {
data.splice(index, 1)
}
--this.size
return true
}
複製程式碼
delete
方法用來刪除指定 key
的快取。成功刪除返回 true
, 否則返回 false
。 刪除操作同樣需要維護 size
屬性。
首先呼叫 assocIndexOf
來找到快取的索引。
如果索引小於 0
,表明沒有快取,刪除不成功,直接返回 false
。
如果要刪除的快取是快取中的最後一項,則直接呼叫 pop
方法,將快取刪除,否則將呼叫 splice
方法將對應位置的快取刪除。
為什麼不直接都用 splice
來刪除資料呢?因為 pop
的效能比 splice
好,我簡單測了一下,大概快 17%
左右。
有興趣的可以看下 pop
和 splice
的規範,splice
要比 pop
做的事情要多。
從這裡又看出了 lodash
對效能的極致追求。
最後將快取數量 size
減少 1
。
參考
License
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,所有文章都會同步傳送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面