每日一篇——lodash—cache

紧接上一篇,讲到baseDifference方法中,遇到数组长度较大(大于200)时,用setCache方法来存储数据,今天我们就来看一看setCache到底做了什么,这对于今后我们遇到大量数据存取的时候有一定借鉴意义。

import MapCache from './MapCache.js'
 
 
/** Used to stand-in for `undefined` hash values. */
const HASH_UNDEFINED = '__lodash_hash_undefined__'
 
class SetCache {
 
  /**
   * Creates an array cache object to store unique values.
   *
   * @private
   * @constructor
   * @param {Array} [values] The values to cache.
   */
  constructor(values) {
    let index = -1
    const length = values == null ? 0 : values.length
 
    this.__data__ = new MapCache
    while (++index < length) {
      this.add(values[index])
    }
  }
 
  /**
   * Adds `value` to the array cache.
   *
   * @memberOf SetCache
   * @alias push
   * @param {*} value The value to cache.
   * @returns {Object} Returns the cache instance.
   */
  add(value) {
    this.__data__.set(value, HASH_UNDEFINED)
    return this
  }
 
  /**
   * Checks if `value` is in the array cache.
   *
   * @memberOf SetCache
   * @param {*} value The value to search for.
   * @returns {number} Returns `true` if `value` is found, else `false`.
   */
  has(value) {
    return this.__data__.has(value)
  }
}
 
SetCache.prototype.push = SetCache.prototype.add
 
export default SetCache
复制代码

setCache这个class主要是对MapCache的二次封装,在constructor中new MapCache,然后基于这个实例的add和has方法其实都是来自于MapCahe,这里还加了一个alias(push即为add)。
这个class中一个需要注意的地方是第19行的new MapCache,一般来说,我们都会写new MapCahe(),那加不加括号有什么区别呢?其实是优先级的区别,new 的运算优先级要小于 . 的运算优先级,现在这种情况下两种写法是一样的,但是new MapCache.add()和new MapCache().add()就会有区别,前者会报错,因为他会先执行MapCache.add,然后再执行new。
接着来看MapCache吧。

import Hash from './Hash.js'
 
 
/**
 * Gets the data for `map`.
 *
 * @private
 * @param {Object} map The map to query.
 * @param {string} key The reference key.
 * @returns {*} Returns the map data.
 */
function getMapData({ __data__ }, key) {
  const data = __data__
  return isKeyable(key)
    ? data[typeof key == 'string' ? 'string' : 'hash']
    : data.map
}
 
/**
 * Checks if `value` is suitable for use as unique object key.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
 */
function isKeyable(value) {
  const type = typeof value
  return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
    ? (value !== '__proto__')
    : (value === null)
}
 
class MapCache {
 
  /**
   * Creates a map cache object to store key-value pairs.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  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])
    }
  }
 
  /**
   * Removes all key-value entries from the map.
   *
   * @memberOf MapCache
   */
  clear() {
    this.size = 0
    this.__data__ = {
      'hash': new Hash,
      'map': new Map,
      'string': new Hash
    }
  }
 
  /**
   * Removes `key` and its value from the map.
   *
   * @memberOf MapCache
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  delete(key) {
    const result = getMapData(this, key)['delete'](key)
    this.size -= result ? 1 : 0
    return result
  }
 
  /**
   * Gets the map value for `key`.
   *
   * @memberOf MapCache
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  get(key) {
    return getMapData(this, key).get(key)
  }
 
  /**
   * Checks if a map value for `key` exists.
   *
   * @memberOf MapCache
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  has(key) {
    return getMapData(this, key).has(key)
  }
 
  /**
   * Sets the map `key` to `value`.
   *
   * @memberOf MapCache
   * @param {string} key The key of the value to set.
   * @param {*} value The value to set.
   * @returns {Object} Returns the map cache instance.
   */
  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
  }
}
 
export default MapCache
复制代码

结合clear、set和getMapData方法可以看出,根据不同的key值,有三种存储的数据类型。如果key是string,number,boolean,symbol中的一种,并且不是__proto__(要么直接是null也行),那么就用hash|string类型,也就是自己封装的Hash来存储,否则就用js自身的Map类型来存储,为什么要分这三种情况呢,我们来看一下object和map的区别,区别主要有以下五点:

  • 一个Object的键只能是string或者symbol,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。
  • Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。
  • 你可以通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。
  • Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。虽然 ES5 开始可以用 map = Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。 Map 在涉及频繁增删键值对的场景下会有些性能优势。

这里主要是考虑了第一条,注意Object的键只能是string或者symbol不是说当你设置这个object的key为string或者symbol时会报错,而是当你用function或者对象作为key值时,他会自动先转化为string。

为什么要用Map类型我们应该已经知道了,那么Hash类型是啥,又是如何实现的,我们等下来看,这里其实主要是对这三种类型进行了判断,然后每个方法都是调用对应class(Map或Hash)的实例方法,包括get,set,has,delete等方法。

我们接着看lodash自己定义的Hash。

/** Used to stand-in for `undefined` hash values. */
const HASH_UNDEFINED = '__lodash_hash_undefined__'
 
 
class Hash {
 
  /**
   * Creates a hash object.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  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])
    }
  }
 
  /**
   * Removes all key-value entries from the hash.
   *
   * @memberOf Hash
   */
  clear() {
    this.__data__ = Object.create(null)
    this.size = 0
  }
 
  /**
   * Removes `key` and its value from the hash.
   *
   * @memberOf Hash
   * @param {Object} hash The hash to modify.
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  delete(key) {
    const result = this.has(key) && delete this.__data__[key]
    this.size -= result ? 1 : 0
    return result
  }
 
  /**
   * Gets the hash value for `key`.
   *
   * @memberOf Hash
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  get(key) {
    const data = this.__data__
    const result = data[key]
    return result === HASH_UNDEFINED ? undefined : result
  }
 
  /**
   * Checks if a hash value for `key` exists.
   *
   * @memberOf Hash
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  has(key) {
    const data = this.__data__
    return data[key] !== undefined
  }
 
  /**
   * Sets the hash `key` to `value`.
   *
   * @memberOf Hash
   * @param {string} key The key of the value to set.
   * @param {*} value The value to set.
   * @returns {Object} Returns the hash instance.
   */
  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
复制代码

可以看到,其实Hash就是创建了一个空对象,然后基于这个对象来维护他的get,set,has等方法。
这里值得一提的是Object.create(null),我们应该知道Object.create方法本身是创建一个新对象,使用现有的对象来提供新创建的对象的__proto__,Object.create(null)创建的对象的__proto__就是null,和{}最大的区别在于,{}(也就是new Object())他其实是Object的一个实例,所以他是有对象的实例方法的,而Object.create(null)创建的对象是没有实例方法的,如obj.hasOwnProperty之类的方法都不能调用。

总结:目前为止,基本上我们已经可以对lodash的cache有一个完整的认识了,当需要维护的数组很大时,就采用map或者hash(自定义的对象)来进行代替,其实也就是我们常说的空间换时间,虽然占用内存较大,但每次的存取都会比较快。

转载于:https://juejin.im/post/5c8cb622e51d454e0b5e0c76

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值