lodash中对大数组进行缓存以加快比较速度

lodash里的difference方法在比较的数组长度超过200的时候就会开启缓存机制,把用来排除比较的大数组变成key-value形式,以加快比较速度。

下面是baseDifference方法:

import SetCache from './SetCache.js'
//创建数组的cache
import arrayIncludes from './arrayIncludes.js'
//arrayIncludes判断数组是否包含给定值,参数一array,参数二value
import arrayIncludesWith from './arrayIncludesWith.js'
//arrayIncludesWith类似于arrayIncludes,区别是它的comparator需要作为参数传入
import map from '../map.js'
//类似于原生的map,对数组每一个元素执行迭代器后返回由返回值组成的新数组
import cacheHas from './cacheHas.js'
//判断cache中是否有给定key

/** Used as the size to enable large array optimizations. */
const LARGE_ARRAY_SIZE = 200//判断当数组参数太长时,是否开启大数组优化

/**
 * The base implementation of methods like `difference` without support
 * for excluding multiple arrays.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {Array} values The values to exclude.
 * @param {Function} [iteratee] The iteratee invoked per element.
 * @param {Function} [comparator] The comparator invoked per element.
 * @returns {Array} Returns the new array of filtered values.
 */
//difference,比较给定数组与其他数组,返回一个新数组,其中元素是给定数组独特于其他数组的元素
//difference方法的基础实现,不支持排除多个数组
//array,用于检查的数组
//values,用于排除元素的数组
//iteratee,迭代器,类型是function,循环会调用
//comparator,比较器,类型function,循环会调用
function baseDifference(array, values, iteratee, comparator) {
  let includes = arrayIncludes//arrayIncludes方法,判断数组是否包含给定值
  let isCommon = true
  const result = []//结果数组
  const valuesLength = values.length//用于排除的数组的长度

  if (!array.length) {//如果array无长度,返回空数组
    return result
  }
  if (iteratee) {//如果传递了迭代器参数,就循环values,对每个值运行迭代器,生成新的values
    values = map(values, (value) => iteratee(value))
  }
  if (comparator) {//如果传递了比较器参数,就是用arrayIncludesWith方法
    includes = arrayIncludesWith
    isCommon = false
  }
  else if (values.length >= LARGE_ARRAY_SIZE) {
    //如果values数组的长度超过200,indludes方法就换成cacheHas,启用cache以达到性能优化的效果
    includes = cacheHas
    isCommon = false
    values = new SetCache(values)
  }
  outer://label语句,将下面的for循环标记,continue会跳过本次循环,继续走下一次外层循环
  for (let value of array) {//循环array
    const computed = iteratee == null ? value : iteratee(value)
    //如果有iteratee参数,就把array数组的循环当前值value传入iteratee,返回值存为computed

    value = (comparator || value !== 0) ? value : 0
    if (isCommon && computed === computed) {
      //排除array当前循环值是否是NaN的情况,isCommon用来标记是否要使用特殊的比较方式
      let valuesIndex = valuesLength//values长度
      while (valuesIndex--) {
        //循环values,如果发现values里有值和当前array循环元素相等,直接跳出values循环,循环下一次array
        if (values[valuesIndex] === computed) {
          continue outer
        }
      }
      result.push(value)//values循环结束没有发现有相等的值,就push入结果数组
    }
    else if (!includes(values, computed, comparator)) {
      //如果当前array循环值是NaN或者需要使用特殊比较方法
      //调用includes判断values中有没有和当前array循环值相等的
      result.push(value)
    }
  }
  return result
}

export default baseDifference

下面是设置缓存存取的第一层,SetCache:

import MapCache from './MapCache.js'

/** Used to stand-in for `undefined` hash values. */
const HASH_UNDEFINED = '__lodash_hash_undefined__'
//用来替代undefined hash值
class SetCache {

  /**
   * Creates an array cache object to store unique values.
   *
   * @private
   * @constructor
   * @param {Array} [values] The values to cache.
   */
  //构造函数,创建一个数组缓存对象来储存唯一值
  constructor(values) {//values参数为一个数组,将为它设置缓存
    let index = -1//循环变量
    const length = values == null ? 0 : values.length//values数组长度

    this.__data__ = new MapCache//为SetCache新实例添加__data__属性,__data__里存放一个MapCache实例
    while (++index < length) {
      this.add(values[index])//根据每个值调用this.prototype.add方法
    }
  }

  /**
   * Adds `value` to the array cache.
   *
   * @memberOf SetCache
   * @alias push
   * @param {*} value The value to cache.
   * @returns {Object} Returns the cache instance.
   */
  //this.prototype.add,调用当前SetCache实例上的MapCache实例的set方法,将值添加入缓存里
  //MapCache上设置的值类型是键值对,这里调用,value是key,HASH_UNDEFINED是value
  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`.
   */
  //this.prototype.has,调用当前SetCache实例上的MapCache实例的has方法,判断当前缓存里有没有这个值
  has(value) {
    return this.__data__.has(value)
  }
}

SetCache.prototype.push = SetCache.prototype.add

export default SetCache

下面是真正存放数据的类MapCache:

import Hash from './Hash.js'
import ListCache from './ListCache.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.
 */
//从map实例里找到key对应的值
function getMapData({ __data__ }, key) {
  const data = __data__
  return isKeyable(key)
    ? data[typeof key == 'string' ? 'string' : 'hash']
    : data.map
    //根据key的类型返回对应的数据,要么是Map对象,要么是ListCache对象,要么是Hash对象
}

/**
 * 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`.
 */
//判断一个值是否合适作为一个唯一的对象key
//如果这个值是String或者Number或者Symbol或者Boolean值,且不是__proto__,就可以作为一个唯一的键
//如果这个值不是以上四种类型,但是它等于null,那也可以作为一个唯一的键
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.
   */
  //MapCache构造函数,创建一个map缓存对象来储存键值对
  //entries参数是键值对数组
  constructor(entries) {
    let index = -1//循环参数
    const length = entries == null ? 0 : entries.length//entries长度

    this.clear()//初始化实例上的size属性和__data__属性
    while (++index < length) {//循环键值对数组,并调用this.prototype.set方法
      const entry = entries[index]
      this.set(entry[0], entry[1])
    }
  }

  /**
   * Removes all key-value entries from the map.
   *
   * @memberOf MapCache
   */
  //清除当前map实例上所有键值对
  clear() {
    this.size = 0
    this.__data__ = {
      'hash': new Hash,
      'map': new (Map || ListCache),
      '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)
    //根据key的数据类型获取map里对应的数据集合,要么是Map对象,要么是ListCache对象,要么是Hash对象
    const size = data.size//获取对应的size属性

    data.set(key, value)//调用数据集合对象上的set方法,设置键值对到上面
    this.size += data.size == size ? 0 : 1//更新MapCache实例上的size属性和数据集合的size属性同步
    return this
  }
}

export default MapCache

MapCache类型会根据数组元素的值是否能作为对象key值来分别使用Map类型或者是原生的对象来存储,并且自己实现了一个兼容的Map类型ListCache。

最终这个数组会变成下面这样:

  {
    __data__: {
      size: xxx,
      __data__: {
        hash: new Hash,
        map: new Map,
        string: new Hash
      }
    }
  }

  {
    __data__: {
      size: xxx,
      __data__: {
        hash: {
          size: xxx,
          __data__: {
            values4: '__lodash_hash_undefined__',
            values6: '__lodash_hash_undefined__',
            ...
          }
        },
        map: new Map,
        string: {
          size: xxx,
          __data__: {
            values3: '__lodash_hash_undefined__',
            values8: '__lodash_hash_undefined__',
            ...
          }
        }
      }
    }
  }

下面是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.
   */
  //Hash类构造函数,接收参数是一个包含键值对数组的数组:
  //[['key1', 'values1'], ['key2', 'values2']......]
  constructor(entries) {
    let index = -1
    const length = entries == null ? 0 : entries.length

    this.clear()//初始化
    while (++index < length) {//循环并设置数据到__data__属性里
      const entry = entries[index]
      this.set(entry[0], entry[1])
    }
  }

  /**
   * Removes all key-value entries from the hash.
   *
   * @memberOf Hash
   */
  clear() {//初始化__data__属性和size属性
    //__data__存放数据,size为数据长度
    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) {//从__data__里删除当前值
    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) {//从__data__里获取当前值
    //如果值设置为__lodash_hash_undefined__,则不允许get,只能进行set和delete还有has操作
    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) {//判断__data__里是否有当前值
    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) {//设置新值到__data__
    const data = this.__data__
    this.size += this.has(key) ? 0 : 1//如果已经有这个值了,size就不变,否则加1
    data[key] = value === undefined ? HASH_UNDEFINED : value//值存入__data__
    return this
  }
}

export default Hash

下面是ListCache:

import assocIndexOf from './assocIndexOf.js'

class ListCache {

  /**
   * Creates an list cache object.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  //ListCache构造函数,ListCache其实就是一个自己实现的Map数据类型
  //参数entries是键值对数组,结构如下
  /*
    [
      [key1, value1],
      [key2, value2],
      ...
    ]
  */
  constructor(entries) {
    let index = -1//循环索引
    const length = entries == null ? 0 : entries.length//entries长度

    this.clear()//初始化实例上的__data__属性
    while (++index < length) {//循环参数,给__data__上存储键值对
      const entry = entries[index]
      this.set(entry[0], entry[1])
    }
  }

  /**
   * Removes all key-value entries from the list cache.
   *
   * @memberOf ListCache
   */
  //初始化__data__和size
  clear() {
    this.__data__ = []
    this.size = 0
  }

  /**
   * Removes `key` and its value from the list cache.
   *
   * @memberOf ListCache
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  //从__data__里删除键值对
  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
  }

  /**
   * Gets the list cache value for `key`.
   *
   * @memberOf ListCache
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  //根据key获取键值对的值
  get(key) {
    const data = this.__data__
    const index = assocIndexOf(data, key)
    return index < 0 ? undefined : data[index][1]
  }

  /**
   * Checks if a list cache value for `key` exists.
   *
   * @memberOf ListCache
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  //根据key判断__data__里是否含有对应key
  has(key) {
    return assocIndexOf(this.__data__, key) > -1
  }

  /**
   * Sets the list cache `key` to `value`.
   *
   * @memberOf ListCache
   * @param {string} key The key of the value to set.
   * @param {*} value The value to set.
   * @returns {Object} Returns the list cache instance.
   */
  //给__data__上设置键值对
  set(key, value) {
    const data = this.__data__
    const index = assocIndexOf(data, key)//在__data__上寻找有没有重复的key,如果没有就返回-1,如果有就返回索引

    if (index < 0) {//如果没有重复的key,长度this.size加一,__data__存入键值对
      ++this.size
      data.push([key, value])
    } else {//如果有重复的key,就改变对应的value
      data[index][1] = value
    }
    return this
  }
}

export default ListCache

下面是循环查找__data__里键值对的方法associndexOf:

import eq from '../eq.js'

/**
 * Gets the index at which the `key` is found in `array` of key-value pairs.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} key The key to search for.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
//寻找键值对数组中key对应的数据索引
function assocIndexOf(array, key) {
  let { length } = array//数组长度
  while (length--) {//循环寻找键值对对应的索引,找到就返回,没找到就返回-1
    if (eq(array[length][0], key)) {
      return length
    }
  }
  return -1
}

export default assocIndexOf

下面是用于判断两个值是否相等的eq方法:

/**
 * Performs a
 * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
 * comparison between two values to determine if they are equivalent.
 *
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to compare.
 * @param {*} other The other value to compare.
 * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
 * @example
 *
 * const object = { 'a': 1 }
 * const other = { 'a': 1 }
 *
 * eq(object, object)
 * // => true
 *
 * eq(object, other)
 * // => false
 *
 * eq('a', 'a')
 * // => true
 *
 * eq('a', Object('a'))
 * // => false
 *
 * eq(NaN, NaN)
 * // => true
 */
//判断两个值是否相等
function eq(value, other) {
  return value === other || (value !== value && other !== other)
}

export default eq

 

posted @ 2018-09-01 19:04  hahazexia  阅读(1232)  评论(1)    收藏  举报