每一個人內心都有一團火,路過的人只看到煙。——《至愛梵高·星空之謎》javascript
本文爲讀 lodash 源碼的第八篇,後續文章會更新到這個倉庫中,歡迎 star:pocket-lodashjava
gitbook也會同步倉庫的更新,gitbook地址:pocket-lodashgit
在《lodash源碼分析之Hash緩存》和《lodash源碼分析之List緩存》介紹了 lodash 的兩種緩存方式,這兩種緩存方式都實現了和 Map
一致的數據管理接口,其中 List
緩存只在不支持 Map
的環境中使用,那什麼時候使用 Hash
緩存,什麼時候使用 Map
或者 List
緩存呢?這就是 MapCache
類所須要作的事情。github
從以前的分析能夠看出,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)
最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:
做者:對角另外一面