本来是按顺序分析difference
方法的,但是一连串下来的引用文件实在是太多了。所以本篇先分析difference
方法的中的一个内置的私有方法baseDifference
,顺带用到了 lodash 的map
方法。
上篇文章先讨论了宽松相等和真值判断 。同样,本篇先讨论严格相等比较,再分析一个问题,什么情况下 value !== value
为真?
严格相等 在 ECMA262 规范中,严格相等比较的原文如下:
Strict Equality Comparison
The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:
If Type (x) is different from Type (y), return false.
If Type (x) is Number or BigInt, then
Return ! Type (x)::equal(x, y).
Return ! SameValueNonNumeric (x, y).
NOTE: This algorithm differs from the SameValue Algorithm in its treatment of signed zeroes and NaNs.
2.1 步中用的 T::equal ( x, y )
语法:
Number::equal ( x, y )
If x is NaN, return false.
If y is NaN, return false.
If x is the same Number value as y, return true.
If x is +0 and y is -0, return true.
If x is -0 and y is +0, return true.
Return false.
BigInt::equal ( x, y )
The abstract operation BigInt::equal with two arguments x and y of type BigInt returns true if x and y have the same [mathematical integer](http://www.ecma-international.org/ecma-262/11.0/index.html#mathematical integer) value and false otherwise.
3 步中用的 SameValueNonNumeric ( x, y )
语法:
SameValueNonNumeric ( x, y )
The internal comparison abstract operation SameValueNonNumeric(x, y), where neither x nor y are numeric type values, produces true or false. Such a comparison is performed as follows:
Assert : Type (x) is not Number or BigInt.
Assert : Type (x) is the same as Type (y).
If Type (x) is Undefined, return true.
If Type (x) is Null, return true.
If Type (x) is String, then
If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return true; otherwise, return false.
If Type (x) is Boolean, then
If x and y are both true or both false, return true; otherwise, return false.
If Type (x) is Symbol, then
If x and y are both the same Symbol value, return true; otherwise, return false.
If x and y are the same Object value, return true. Otherwise, return false.
整个规范是用一种独特的约定写的,所以把原文翻译下便于理解。
严格相等比较:
Strict Equality Comparison
比较 x === y(其中 x 和 y 是值),产生 true 或 false。该比较的执行步骤如下:
如果 x 的类型与 y 的类型不同,返回 false。
如果 x 的类型是 Number 或者 BigInt,
Return ! Type (x)::equal(x, y).返回
返回 ! SameValueNonNumeric (x, y)的结果。
注意: 该算法与 SameValue 算法的区别在于对有符号零值和 NaN 的处理。
2.1 步中用的 T::equal ( x, y )
语法:
Number::equal ( x, y )
如果 x 是 NaN,返回 false
如果 y 是 NaN,返回 fasle
如果 x 与 y 是相同的数字类型值,返回 true。
如果 x 为 +0 且 y 为-0,返回 true。
如果 x 为-0 且 y 为+0.返回 true
返回 false
BigInt::equal ( x, y )
如果 x 和 y 具有相同的数学整数 值,则带有两个 BigInt 类型参数 x 和 y 的抽象操作 BigInt::equal 将返回 true,否则返回 false。
3 步中用的 SameValueNonNumeric ( x, y )
语法:
SameValueNonNumeric (x, y)
内部比较抽象操作 SameValueNonNumeric (x, y)(其中 x 和 y 都不是数字类型值),产生 true 或 false。 该比较的执行步骤如下:
断言:x 的类型不是 Number 或 BigInt。
断言: x 的类型与 y 的类型相同。
如果 x 的类型为 Undefined,返回 true。
如果 x 的类型为 Null,返回 true。
如果 x 的类型为 String,
如果 x 和 y 是完全相同的代码单元序列(具有相同的长度并且在相同索引处有相同的代码单元),则返回 true;否则,返回 false。
如果 x 的类型为 Boolean,
如果 x 和 y 都为 true 或都为 false,返回 true;否则,返回 false。
如果 x 的类型为 Symbol,
如果 x 和 y 是相同的 Symbol 类型值,返回 true;否则,返回 false。
如果 x 和 y 为相同的 Object 类型值,返回 true;否则,返回 false。
回归到最初的问题,什么情况下 value !== value
为真?换言之,什么情况下value === value
为假?
value
和value
本身肯定是相同的类型,跳过。
如果value
为Number
类型,
value
为NaN
,返回false
,所以NaN
满足条件。
Number
类型的其他相同值都返回true
,跳过。
如果value
为BigInt
类型,相同值都返回ture
,跳过。
进入SameValueNonNumeric ( x, y )
语法,Undefined
、Null
、String
、Boolean
、Symbol
、Object
类型相同值的严格相等比较都返回true
,跳过。
分析完毕,所以只有 NaN !== NaN
这一种情况满足value !== value
为真!
引用私有方法 Hash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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;
MapCache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import Hash from './Hash.js' ;function getMapData ({ __data__ }, key ) { const data = __data__; return isKeyable(key) ? data[typeof key === 'string' ? 'string' : 'hash' ] : data.map; } function isKeyable (value ) { const type = typeof value; return type === 'string' || type === 'number' || type === 'symbol' || type === 'boolean' ? value !== '__proto__' : value === null ; } class MapCache { 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 .size = 0 ; this .__data__ = { hash: new Hash(), map: new Map (), string: new Hash(), }; } delete (key) { const result = getMapData(this , key)['delete' ](key); this .size -= result ? 1 : 0 ; return result; } get (key) { return getMapData(this , key).get(key); } has(key) { return getMapData(this , key).has(key); } 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;
SetCache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import MapCache from './MapCache.js' ;const HASH_UNDEFINED = '__lodash_hash_undefined__' ;class SetCache { constructor (values) { let index = -1 ; const length = values == null ? 0 : values.length; this .__data__ = new MapCache(); while (++index < length) { this .add(values[index]); } } add(value) { this .__data__.set(value, HASH_UNDEFINED); return this ; } has(value) { return this .__data__.has(value); } } SetCache.prototype.push = SetCache.prototype.add; export default SetCache;
baseIndexOf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function baseFindIndex (array, predicate, fromIndex, fromRight ) { const { length } = array; let index = fromIndex + (fromRight ? 1 : -1 ); while (fromRight ? index-- : ++index < length) { if (predicate(array[index], index, array)) { return index; } } return -1 ; } export default baseFindIndex;
baseIsNaN 正是在这里用到了前文中只有NaN !== NaN
为真的讨论。
1 2 3 4 5 6 7 8 9 10 11 12 13 function baseIsNaN (value ) { return value !== value; } export default baseIsNaN;
strictIndexOf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function strictIndexOf (array, value, fromIndex ) { let index = fromIndex - 1 ; const { length } = array; while (++index < length) { if (array[index] === value) { return index; } } return -1 ; } export default strictIndexOf;
baseIndexOf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import baseFindIndex from './baseFindIndex.js' ;import baseIsNaN from './baseIsNaN.js' ;import strictIndexOf from './strictIndexOf.js' ;function baseIndexOf (array, value, fromIndex ) { return value === value ? strictIndexOf(array, value, fromIndex) : baseFindIndex(array, baseIsNaN, fromIndex); } export default baseIndexOf;
arrayIncludes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import baseIndexOf from './baseIndexOf.js' ;function arrayIncludes (array, value ) { const length = array == null ? 0 : array.length; return !!length && baseIndexOf(array, value, 0 ) > -1 ; } export default arrayIncludes;
arrayIncludesWith 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function arrayIncludesWith (array, target, comparator ) { if (array == null ) { return false ; } for (const value of array) { if (comparator(target, value)) { return true ; } } return false ; } export default arrayIncludesWith;
cacheHas 1 2 3 4 5 6 7 8 9 10 11 12 13 function cacheHas (cache, key ) { return cache.has(key); } export default cacheHas;
map、baseDifference map 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function map (array, iteratee ) { let index = -1 ; const length = array == null ? 0 : array.length; const result = new Array (length); while (++index < length) { result[index] = iteratee(array[index], index, array); } return result; } export default map;
baseDifference 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 import SetCache from './SetCache.js' ;import arrayIncludes from './arrayIncludes.js' ;import arrayIncludesWith from './arrayIncludesWith.js' ;import map from '../map.js' ;import cacheHas from './cacheHas.js' ;const LARGE_ARRAY_SIZE = 200 ;function baseDifference (array, values, iteratee, comparator ) { let includes = arrayIncludes; let isCommon = true ; const result = []; const valuesLength = values.length; if (!array.length) { return result; } if (iteratee) { values = map(values, (value) => iteratee(value)); } if (comparator) { includes = arrayIncludesWith; isCommon = false ; } else if (values.length >= LARGE_ARRAY_SIZE) { includes = cacheHas; isCommon = false ; values = new SetCache(values); } outer: for (let value of array) { const computed = iteratee == null ? value : iteratee(value); value = comparator || value !== 0 ? value : 0 ; if (isCommon && computed === computed) { let valuesIndex = valuesLength; while (valuesIndex--) { if (values[valuesIndex] === computed) { continue outer; } } result.push(value); } else if (!includes(values, computed, comparator)) { result.push(value); } } return result; } export default baseDifference;
原生实现 map 我觉得 map 也不用太多说了,毕竟Array.prototype.map
是原生实现了的。
1 2 3 4 5 6 7 var array1 = [1 , 2 , 3 ];var array2 = array1.map(function (value, index ) { return value * 2 ; }); console .log(array2);
总结 lodash 中,实现一个baseDifference
方法之所以这么复杂,一是因为它支撑了三个方法的实现difference
、differenceBy
、differenceWith
,二是因为lodash
对 length 大于200
的数组进行了优化,使用一种对象缓存的方式模拟了数组的增删查改。