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

浙公网安备 33010602011771号