mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-01-31 15:27:50 +00:00
Combine MemCache and Stack into MapCache.
This commit is contained in:
284
lodash.js
284
lodash.js
@@ -1358,6 +1358,7 @@
|
||||
var ArrayBuffer = context.ArrayBuffer,
|
||||
Map = getNative(context, 'Map'),
|
||||
Reflect = context.Reflect,
|
||||
Set = getNative(context, 'Set'),
|
||||
Symbol = context.Symbol,
|
||||
Uint8Array = context.Uint8Array,
|
||||
WeakMap = getNative(context, 'WeakMap'),
|
||||
@@ -1381,19 +1382,18 @@
|
||||
nativeMax = Math.max,
|
||||
nativeMin = Math.min,
|
||||
nativeParseInt = context.parseInt,
|
||||
nativeRandom = Math.random,
|
||||
nativeSet = getNative(context, 'Set');
|
||||
nativeRandom = Math.random;
|
||||
|
||||
/** Used to store function metadata. */
|
||||
var metaMap = WeakMap && new WeakMap;
|
||||
|
||||
/** Used to detect maps and sets. */
|
||||
var mapCtorString = Map ? fnToString.call(Map) : '',
|
||||
setCtorString = nativeSet ? fnToString.call(nativeSet) : '';
|
||||
setCtorString = Set ? fnToString.call(Set) : '';
|
||||
|
||||
/** Detect lack of support for map and set `toStringTag` values (IE 11). */
|
||||
var noMapSetTag = Map && nativeSet &&
|
||||
!(objToString.call(new Map) == mapTag && objToString.call(new nativeSet) == setTag);
|
||||
var noMapSetTag = Map && Set &&
|
||||
!(objToString.call(new Map) == mapTag && objToString.call(new Set) == setTag);
|
||||
|
||||
/** Used to lookup unminified function names. */
|
||||
var realNames = {};
|
||||
@@ -1718,45 +1718,77 @@
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Creates a memoize cache object to store key-value pairs.
|
||||
* Creates a map cache object to store key-value pairs.
|
||||
*
|
||||
* @private
|
||||
* @static
|
||||
* @name Cache
|
||||
* @memberOf _.memoize
|
||||
*/
|
||||
function MemCache() {
|
||||
this.__data__ = {};
|
||||
function MapCache() {
|
||||
this.__data__ = {
|
||||
'hash': createCache(),
|
||||
'map': (Map ? new Map : []),
|
||||
'string': createCache()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes `key` and its value from the memoize cache.
|
||||
* Removes `key` and its value from the map.
|
||||
*
|
||||
* @private
|
||||
* @name delete
|
||||
* @memberOf _.memoize.Cache
|
||||
* @memberOf MapCache
|
||||
* @param {string} key The key of the value to remove.
|
||||
* @returns {boolean} Returns `true` if the entry was removed successfully, else `false`.
|
||||
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
||||
*/
|
||||
function memDelete(key) {
|
||||
return this.has(key) && delete this.__data__[key];
|
||||
function mapDelete(key) {
|
||||
var data = this.__data__,
|
||||
map = data.map;
|
||||
|
||||
if (isKeyable(key)) {
|
||||
var hash = typeof key == 'string' ? data.string : data.hash;
|
||||
return hasOwnProperty.call(hash, key) && delete hash[key];
|
||||
}
|
||||
if (Map) {
|
||||
return map['delete'](key);
|
||||
}
|
||||
var index = assocIndexOf(map, key);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
var lastIndex = map.length - 1;
|
||||
if (index == lastIndex) {
|
||||
map.pop();
|
||||
} else {
|
||||
splice.call(map, index, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cached value for `key`.
|
||||
* Gets the map value for `key`.
|
||||
*
|
||||
* @private
|
||||
* @name get
|
||||
* @memberOf _.memoize.Cache
|
||||
* @memberOf MapCache
|
||||
* @param {string} key The key of the value to get.
|
||||
* @returns {*} Returns the cached value.
|
||||
*/
|
||||
function memGet(key) {
|
||||
return key == '__proto__' ? undefined : this.__data__[key];
|
||||
function mapGet(key) {
|
||||
var data = this.__data__,
|
||||
map = data.map;
|
||||
|
||||
if (isKeyable(key)) {
|
||||
var hash = typeof key == 'string' ? data.string : data.hash;
|
||||
return (nativeCreate || hasOwnProperty.call(hash, key)) ? hash[key] : undefined;
|
||||
}
|
||||
if (Map) {
|
||||
return map.get(key);
|
||||
}
|
||||
var index = assocIndexOf(map, key);
|
||||
return index < 0 ? undefined : map[index][1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a cached value for `key` exists.
|
||||
* Checks if a map value for `key` exists.
|
||||
*
|
||||
* @private
|
||||
* @name has
|
||||
@@ -1764,83 +1796,51 @@
|
||||
* @param {string} key The key of the entry to check.
|
||||
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
||||
*/
|
||||
function memHas(key) {
|
||||
return key != '__proto__' && hasOwnProperty.call(this.__data__, key);
|
||||
function mapHas(key) {
|
||||
var data = this.__data__,
|
||||
map = data.map;
|
||||
|
||||
if (isKeyable(key)) {
|
||||
var hash = typeof key == 'string' ? data.string : data.hash;
|
||||
return hasOwnProperty.call(hash, key);
|
||||
}
|
||||
return Map ? map.has(key) : (assocIndexOf(map, key) > -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `value` to `key` of the cache.
|
||||
* Sets `value` to `key` of the map.
|
||||
*
|
||||
* @private
|
||||
* @name set
|
||||
* @memberOf _.memoize.Cache
|
||||
* @memberOf MapCache
|
||||
* @param {string} key The key of the value to set.
|
||||
* @param {*} value The value to set.
|
||||
* @returns {Object} Returns the memoize cache object.
|
||||
* @returns {Object} Returns the map cache object.
|
||||
*/
|
||||
function memSet(key, value) {
|
||||
if (key != '__proto__') {
|
||||
this.__data__[key] = value;
|
||||
function mapSet(key, value) {
|
||||
var data = this.__data__,
|
||||
map = data.map;
|
||||
|
||||
if (isKeyable(key)) {
|
||||
var hash = typeof key == 'string' ? data.string : data.hash;
|
||||
hash[key] = value;
|
||||
}
|
||||
else if (Map) {
|
||||
map.set(key, value);
|
||||
}
|
||||
else {
|
||||
var index = assocIndexOf(map, key);
|
||||
if (index < 0) {
|
||||
map.push([key, value]);
|
||||
} else {
|
||||
map[index][1] = value;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Creates a stack object to store key-value pairs.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function Stack() {
|
||||
return Map ? new Map : (this.__data__ = [], this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes `key` and its value from the stack.
|
||||
*
|
||||
* @private
|
||||
* @name delete
|
||||
* @memberOf Stack
|
||||
* @param {string} key The key of the value to remove.
|
||||
* @returns {boolean} Returns `true` if the entry was removed successfully, else `false`.
|
||||
*/
|
||||
function stackDelete() {
|
||||
var data = this.__data__;
|
||||
return !!data.length && (data.pop(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stack value for `key`.
|
||||
*
|
||||
* @private
|
||||
* @name get
|
||||
* @memberOf Stack
|
||||
* @param {string} key The key of the value to get.
|
||||
* @returns {*} Returns the cached value.
|
||||
*/
|
||||
function stackGet(key) {
|
||||
var data = this.__data__,
|
||||
index = assocIndexOf(data, key);
|
||||
|
||||
return index < 0 ? undefined : data[index][1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `value` to `key` of the stack.
|
||||
*
|
||||
* @private
|
||||
* @name set
|
||||
* @memberOf Stack
|
||||
* @param {string} key The key of the value to set.
|
||||
* @param {*} value The value to set.
|
||||
*/
|
||||
function stackSet(key, value) {
|
||||
this.__data__.push([key, value]);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a set cache object to store unique values.
|
||||
@@ -1849,17 +1849,17 @@
|
||||
* @param {Array} [values] The values to cache.
|
||||
*/
|
||||
function SetCache(values) {
|
||||
var length = values ? values.length : 0;
|
||||
var length = values ? values.length : 0,
|
||||
data = this.__data__ = new MapCache;
|
||||
|
||||
this.data = { 'hash': nativeCreate(null), 'set': new nativeSet };
|
||||
while (length--) {
|
||||
this.push(values[length]);
|
||||
data.set(values[length], true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is in `cache` mimicking the return signature of
|
||||
* `_.indexOf` by returning `0` if the value is found, else `-1`.
|
||||
* `_.indexOf` by returning `0` if `value` is found, else `-1`.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} cache The set cache to search.
|
||||
@@ -1867,10 +1867,7 @@
|
||||
* @returns {number} Returns `0` if `value` is found, else `-1`.
|
||||
*/
|
||||
function cacheIndexOf(cache, value) {
|
||||
var data = cache.data,
|
||||
result = (typeof value == 'string' || isObject(value)) ? data.set.has(value) : data.hash[value];
|
||||
|
||||
return result ? 0 : -1;
|
||||
return cache.__data__.has(value) ? 0 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1882,12 +1879,7 @@
|
||||
* @param {*} value The value to cache.
|
||||
*/
|
||||
function cachePush(value) {
|
||||
var data = this.data;
|
||||
if (typeof value == 'string' || isObject(value)) {
|
||||
data.set.add(value);
|
||||
} else {
|
||||
data.hash[value] = true;
|
||||
}
|
||||
this.__data__.set(value, true);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
@@ -1977,7 +1969,7 @@
|
||||
}
|
||||
}
|
||||
// Check for circular references and return its corresponding clone.
|
||||
stack || (stack = new Stack);
|
||||
stack || (stack = new MapCache);
|
||||
var stacked = stack.get(value);
|
||||
if (stacked) {
|
||||
return stacked;
|
||||
@@ -2047,7 +2039,7 @@
|
||||
var index = -1,
|
||||
indexOf = getIndexOf(),
|
||||
isCommon = indexOf === baseIndexOf,
|
||||
cache = (isCommon && values.length >= LARGE_ARRAY_SIZE) ? createCache(values) : null,
|
||||
cache = (isCommon && values.length >= LARGE_ARRAY_SIZE) ? new SetCache(values) : null,
|
||||
valuesLength = values.length;
|
||||
|
||||
if (cache) {
|
||||
@@ -2400,7 +2392,7 @@
|
||||
}
|
||||
// Assume cyclic values are equal.
|
||||
// For more information on detecting circular references see https://es5.github.io/#JO.
|
||||
stack || (stack = new Stack);
|
||||
stack || (stack = new MapCache);
|
||||
var stacked = stack.get(object);
|
||||
if (stacked) {
|
||||
return stacked == other;
|
||||
@@ -2452,7 +2444,7 @@
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
var stack = new Stack,
|
||||
var stack = new MapCache,
|
||||
result = customizer ? customizer(objValue, srcValue, key, object, source, stack) : undefined;
|
||||
|
||||
if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack) : result)) {
|
||||
@@ -2605,7 +2597,7 @@
|
||||
srcValue = source[key];
|
||||
}
|
||||
if (isObject(srcValue)) {
|
||||
stack || (stack = new Stack);
|
||||
stack || (stack = new MapCache);
|
||||
baseMergeDeep(object, source, key, baseMerge, customizer, stack);
|
||||
}
|
||||
else {
|
||||
@@ -2994,7 +2986,7 @@
|
||||
length = array.length,
|
||||
isCommon = indexOf === baseIndexOf,
|
||||
isLarge = isCommon && length >= LARGE_ARRAY_SIZE,
|
||||
seen = isLarge ? createCache() : null,
|
||||
seen = isLarge ? new SetCache : null,
|
||||
result = [];
|
||||
|
||||
if (seen) {
|
||||
@@ -3432,14 +3424,13 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `Set` cache object to optimize linear searches of large arrays.
|
||||
* Creates an empty cache object.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [values] The values to cache.
|
||||
* @returns {null|Object} Returns the new cache object if `Set` is supported, else `null`.
|
||||
* @returns {Object} Returns the new cache object.
|
||||
*/
|
||||
function createCache(values) {
|
||||
return (nativeCreate && nativeSet) ? new SetCache(values) : null;
|
||||
function createCache() {
|
||||
return nativeCreate ? nativeCreate(null) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4312,6 +4303,17 @@
|
||||
(object != null && value in Object(object)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return !(value === '__proto__' || isObject(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `func` has a lazy counterpart.
|
||||
*
|
||||
@@ -5069,7 +5071,7 @@
|
||||
|
||||
while (othIndex--) {
|
||||
var value = arrays[othIndex] = isArrayLike(value = arrays[othIndex]) ? value : [];
|
||||
caches[othIndex] = (isCommon && value.length >= 120) ? createCache(othIndex && value) : null;
|
||||
caches[othIndex] = (isCommon && value.length >= 120) ? new SetCache(othIndex && value) : null;
|
||||
}
|
||||
var array = arrays[0],
|
||||
index = -1,
|
||||
@@ -7759,9 +7761,8 @@
|
||||
* Creates a function that memoizes the result of `func`. If `resolver` is
|
||||
* provided it determines the cache key for storing the result based on the
|
||||
* arguments provided to the memoized function. By default, the first argument
|
||||
* provided to the memoized function is coerced to a string and used as the
|
||||
* cache key. The `func` is invoked with the `this` binding of the memoized
|
||||
* function.
|
||||
* provided to the memoized function is used as the map cache key. The `func`
|
||||
* is invoked with the `this` binding of the memoized function.
|
||||
*
|
||||
* **Note:** The cache is exposed as the `cache` property on the memoized
|
||||
* function. Its creation may be customized by replacing the `_.memoize.Cache`
|
||||
@@ -7776,35 +7777,27 @@
|
||||
* @returns {Function} Returns the new memoizing function.
|
||||
* @example
|
||||
*
|
||||
* var upperCase = _.memoize(function(string) {
|
||||
* return string.toUpperCase();
|
||||
* });
|
||||
* var object = { 'a': 1, 'b': 2 };
|
||||
* var other = { 'c': 3, 'd': 4 };
|
||||
*
|
||||
* upperCase('fred');
|
||||
* // => 'FRED'
|
||||
* var values = _.memoize(_.values);
|
||||
* values(object);
|
||||
* // => [1, 2]
|
||||
*
|
||||
* values(other);
|
||||
* // => [3, 4]
|
||||
*
|
||||
* object.a = 2;
|
||||
* values(object);
|
||||
* // => [1, 2]
|
||||
*
|
||||
* // modifying the result cache
|
||||
* upperCase.cache.set('fred', 'BARNEY');
|
||||
* upperCase('fred');
|
||||
* // => 'BARNEY'
|
||||
* values.cache.set(object, ['a', 'b']);
|
||||
* values(object);
|
||||
* // => ['a', 'b']
|
||||
*
|
||||
* // replacing `_.memoize.Cache`
|
||||
* var object = { 'user': 'fred' };
|
||||
* var other = { 'user': 'barney' };
|
||||
* var identity = _.memoize(_.identity);
|
||||
*
|
||||
* identity(object);
|
||||
* // => { 'user': 'fred' }
|
||||
* identity(other);
|
||||
* // => { 'user': 'fred' }
|
||||
*
|
||||
* _.memoize.Cache = WeakMap;
|
||||
* var identity = _.memoize(_.identity);
|
||||
*
|
||||
* identity(object);
|
||||
* // => { 'user': 'fred' }
|
||||
* identity(other);
|
||||
* // => { 'user': 'barney' }
|
||||
*/
|
||||
function memoize(func, resolver) {
|
||||
if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
|
||||
@@ -9740,8 +9733,8 @@
|
||||
* @returns {boolean} Returns `true` if `path` exists, else `false`.
|
||||
* @example
|
||||
*
|
||||
* var object = { 'a': { 'b': { 'c': 3 } } },
|
||||
* other = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });
|
||||
* var object = { 'a': { 'b': { 'c': 3 } } };
|
||||
* var other = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });
|
||||
*
|
||||
* _.has(object, 'a');
|
||||
* // => true
|
||||
@@ -12158,22 +12151,17 @@
|
||||
LazyWrapper.prototype = baseCreate(baseLodash.prototype);
|
||||
LazyWrapper.prototype.constructor = LazyWrapper;
|
||||
|
||||
// Add functions to the `MemCache` cache.
|
||||
MemCache.prototype['delete'] = memDelete;
|
||||
MemCache.prototype.get = memGet;
|
||||
MemCache.prototype.has = memHas;
|
||||
MemCache.prototype.set = memSet;
|
||||
// Add functions to the `MapCache` cache.
|
||||
MapCache.prototype['delete'] = mapDelete;
|
||||
MapCache.prototype.get = mapGet;
|
||||
MapCache.prototype.has = mapHas;
|
||||
MapCache.prototype.set = mapSet;
|
||||
|
||||
// Add functions to the `Set` cache.
|
||||
SetCache.prototype.push = cachePush;
|
||||
|
||||
// Add functions to the `Stack` cache.
|
||||
Stack.prototype['delete'] = stackDelete;
|
||||
Stack.prototype.get = stackGet;
|
||||
Stack.prototype.set = stackSet;
|
||||
|
||||
// Assign cache to `_.memoize`.
|
||||
memoize.Cache = MemCache;
|
||||
memoize.Cache = MapCache;
|
||||
|
||||
// Add functions that return wrapped values when chaining.
|
||||
lodash.after = after;
|
||||
|
||||
19
test/test.js
19
test/test.js
@@ -12165,17 +12165,19 @@
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('should skip the `__proto__` key', function(assert) {
|
||||
QUnit.test('should cache the `__proto__` key', function(assert) {
|
||||
assert.expect(8);
|
||||
|
||||
var array = [],
|
||||
key = '__proto__';
|
||||
|
||||
_.times(2, function(index) {
|
||||
var count = 0,
|
||||
key = '__proto__',
|
||||
resolver = index && _.identity;
|
||||
|
||||
var memoized = _.memoize(function() {
|
||||
count++;
|
||||
return [];
|
||||
return array;
|
||||
}, resolver);
|
||||
|
||||
var cache = memoized.cache;
|
||||
@@ -12183,9 +12185,9 @@
|
||||
memoized(key);
|
||||
memoized(key);
|
||||
|
||||
assert.strictEqual(count, 2);
|
||||
assert.strictEqual(cache.get(key), undefined);
|
||||
assert.strictEqual(cache['delete'](key), false);
|
||||
assert.strictEqual(count, 1);
|
||||
assert.strictEqual(cache.get(key), array);
|
||||
assert.strictEqual(cache['delete'](key), true);
|
||||
assert.notOk(cache.__data__ instanceof Array);
|
||||
});
|
||||
});
|
||||
@@ -12214,9 +12216,10 @@
|
||||
return true;
|
||||
},
|
||||
'get': function(key) {
|
||||
return _.find(this.__data__, function(entry) {
|
||||
var entry = _.find(this.__data__, function(entry) {
|
||||
return key === entry.key;
|
||||
}).value;
|
||||
});
|
||||
return entry && entry.value;
|
||||
},
|
||||
'has': function(key) {
|
||||
return _.some(this.__data__, function(entry) {
|
||||
|
||||
Reference in New Issue
Block a user