lodash原始碼分析之Hash快取

對角發表於2018-01-02

在那小小的夢的暖閣,我為你收藏起整個季節的煙雨。

——洛夫《靈河》

本文為讀 lodash 原始碼的第四篇,後續文章會更新到這個倉庫中,歡迎 star:pocket-lodash

gitbook也會同步倉庫的更新,gitbook地址:pocket-lodash

作用與用法

Hash 顧名思義,就是要有一個離散的序列,根據 key 來儲取資料。而在 javascript 中,最適合的無疑是物件了。

Hash 在 lodash 的 .internal 資料夾中,作為內部檔案來使用。lodash 會根據不同的資料型別選擇不同的快取方式,Hash 便是其中的一種方式,這種方式只能快取 key 的型別符合物件鍵要求的資料。

Hash 只接收一個二維陣列作為引數,呼叫方式如下:

new Hash([['tes1', 1],['test2',2],['test3',3]])
複製程式碼

其中子項中的第一項會作為 key ,第二項是需要快取的值。

Hash 例項化的結果如下:

{
  size: 3,
  __data__: {
    test1: 1,
    test2: 2,
    test3: 3
  }
}
複製程式碼

快取的數量儲存在 __data__ 的物件中。

Hash與Map

後面將會講到,除了使用 Hash 方式快取資料外,還會用到 Map,lodash 在設計 Hash 的資料管理介面時,也與 Map 的介面一致,但是不會包含 Map 的遍歷方法。

先來看看這些介面都有那些:

lodash原始碼分析之Hash快取

原始碼

const HASH_UNDEFINED = '__lodash_hash_undefined__'

class Hash {
  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__ = Object.create(null)
    this.size = 0
  }
  delete(key) {
    const result = this.has(key) && delete this.__data__[key]
    this.size -= result ? 1 : 0
    return result
  }
  get(key) {
    const data = this.__data__
    const result = data[key]
    return result === HASH_UNDEFINED ? undefined : result
  }
  has(key) {
    const data = this.__data__
    return data[key] !== undefined
  }
  set(key, value) {
    const data = this.__data__
    this.size += this.has(key) ? 0 : 1
    data[key] = value === undefined ? HASH_UNDEFINED : value
    return this
  }
}

export default Hash
複製程式碼

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])
    }
  }
複製程式碼

constructor 中並沒有看到初始化 __data__ 屬性和 size 屬性,這個其實在 clear 方法中初始化了,後面會解釋。

接著遍歷傳入的二維陣列,呼叫 set 方法,初始化快取的值。將子項的第一項作為 key ,第二項為快取的值。

clear

clear() {
  this.__data__ = Object.create(null)
  this.size = 0
}
複製程式碼

clear 的作用是清空快取,因此需要將 size 重置為 0

將快取的資料 __data__ 設定為空物件。

這裡並沒有用 this.__data__ = {} 置空,而是呼叫了 Object.create 方法,並且將 null 作為引數。我們都知道, Object.create 的第一個引數為建立物件的原型物件,傳入 null 的時候,返回的就是一個真空物件,即沒有原型的物件,因此不會有原型屬性的干擾,用來做快取物件十分適合。

has

has(key) {
  const data = this.__data__
  return data[key] !== undefined
}
複製程式碼

has 用來判斷是否已經有快取資料,如果快取資料已經存在,則返回 true

判斷也十分簡單,只需要判斷取出來的值是否為 undefined 即可。

這個判斷有一個坑,後面會講到。

set

set(key, value) {
  const data = this.__data__
  this.size += this.has(key) ? 0 : 1
  data[key] = value === undefined ? HASH_UNDEFINED : value
  return this
}
複製程式碼

set 用來增加或者更新需要快取的值。set 的時候需要同時維護 size 和在快取的值。

首先呼叫 has 方法,判斷對應的 key 是否已經被快取過,如果已經快取過,則 size 保持不變,否則 size1

快取值其實就是設定快取物件 this.__data__ 對應 key 屬性的值。

has 中說到用 data[key] !== undefined 有一個坑,因為要快取的值也可以是 undefined ,如果不做處理,肯定會導致判斷錯誤。

lodash 的處理方式是將 undefined 的值轉換成 HASH_UNDEFINED ,也即一開始便定義的 __lodash_hash_undefined__ 字串來儲存。

所以在快取中,是用字串 __lodash_hash_undefined__ 來替代 undefined 的。

set 在最後還將例項 this 返回,以支援鏈式操作。

get

get(key) {
  const data = this.__data__
  const result = data[key]
  return result === HASH_UNDEFINED ? undefined : result
}
複製程式碼

get 方法是從快取中取值。

取值其實就是返回快取物件中對應 key 的值即可。因為 undefined 在快取中是以 __lodash_hash_undefined__ 來表示的,因此遇到值為 __lodash_hash_undefined__ 時,返回 undefined

其實這樣還是有小小的問題的,如果需要快取的值剛好是 __lodash_hash_undefined__,那取出來的值跟預設的就不一致了。但是這樣情況應該很少出現吧。

delete

delete(key) {
  const result = this.has(key) && delete this.__data__[key]
  this.size -= result ? 1 : 0
  return result
}
複製程式碼

delete 方法用來刪除指定 key 的快取。成功刪除返回 true, 否則返回 false。 刪除操作同樣需要維護 size 屬性和快取值。

首先呼叫 has 方法來判斷快取是否存在,如果存在,用 delete 操作符將 __data__ 中對應的屬性刪除。

delete 操作符在成功刪除屬性時會返回 true,如果成功刪除,則需要將 size 減少 1

參考

  1. Set 和 Map 資料結構
  2. Object.create()

License

署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)

最後,所有文章都會同步傳送到微信公眾號上,歡迎關注,歡迎提意見:

lodash原始碼分析之Hash快取

作者:對角另一面

相關文章