_.difference(array, [values])
复制代码
创建一个新数组,将入参数组深复制新数组,将去掉数组中与第二个参数数组相同的值,返回新数组。
使用方法
_.difference([2, 1], [2, 3]);
// => [1]
复制代码
源码分析
import baseDifference from './.internal/baseDifference.js'
import baseFlatten from './.internal/baseFlatten.js'
import isArrayLikeObject from './isArrayLikeObject.js'
/**
* Creates an array of `array` values not included in the other given arrays
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* for equality comparisons. The order and references of result values are
* determined by the first array.
*
* **Note:** Unlike `pullAll`, this method returns a new array.
*
* @since 0.1.0
* @category Array
* @param {Array} array The array to inspect.
* @param {...Array} [values] The values to exclude.
* @returns {Array} Returns the new array of filtered values.
* @see union, unionBy, unionWith, without, xor, xorBy, xorWith,
* @example
*
* difference([2, 1], [2, 3])
* // => [1]
*/
function difference(array, ...values) {
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
: []
}
export default difference
复制代码
这个方法本身其实并没有什么好多说的,主要是了解isArrayLikeObject,baseDifference和baseFlatten三个方法,下面我们一个一个来看。
import isArrayLike from './isArrayLike.js'
import isObjectLike from './isObjectLike.js'
/**
* This method is like `isArrayLike` except that it also checks if `value`
* is an object.
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array-like object,
* else `false`.
* @example
*
* isArrayLikeObject([1, 2, 3])
* // => true
*
* isArrayLikeObject(document.body.children)
* // => true
*
* isArrayLikeObject('abc')
* // => false
*
* isArrayLikeObject(Function)
* // => false
*/
function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value)
}
export default isArrayLikeObject
复制代码
首先是isArrayLikeObject方法,这个方法其实就是isArrayLike和isObjectLike两个方法的联合,从命名可以看出其实这两个方法就是判断是否为类数组或类对象。
import isLength from './isLength.js'
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* isArrayLike([1, 2, 3])
* // => true
*
* isArrayLike(document.body.children)
* // => true
*
* isArrayLike('abc')
* // => true
*
* isArrayLike(Function)
* // => false
*/
function isArrayLike(value) {
return value != null && typeof value != 'function' && isLength(value.length)
}
export default isArrayLike
复制代码
什么是类数组呢?这里定义为只要不是null或者undefined,并且类型也不是function,并且有一个合法的length属性,什么是合法的length呢?我们接下去看isLength方法。
/** Used as references for various `Number` constants. */
const MAX_SAFE_INTEGER = 9007199254740991
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* isLength(3)
* // => true
*
* isLength(Number.MIN_VALUE)
* // => false
*
* isLength(Infinity)
* // => false
*
* isLength('3')
* // => false
*/
function isLength(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER
}
export default isLength
复制代码
合法的length要满足4个条件: 1. 这是一个number 2.大于-1 3.是整数 4.小于等于MAX_SAFE_INTEGER,MAX_SAFE_INTEGER常量值为 9007199254740991。这个数字形成的原因是,Javascript 使用 IEEE 754中规定的 double-precision floating-point format numbers,在这个规定中能安全的表示数字的范围在 -(2^53- 1) 到 2^53- 1 之间。
到这里为止,isArrayLike已经明确了,不是null,也不是function(防止你在function自己定义length属性),然后需要有一个合法的length属性,我们可以想象,可能常见的有三种情况,第一种直接就是数组,第二种形如{a: 1, b: 2, length: 2}这样,dom的节点正好符合这样的形式,当然也算类数组,第三种就是字符串。接下去我们来看isObjectLike。
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* isObjectLike({})
* // => true
*
* isObjectLike([1, 2, 3])
* // => true
*
* isObjectLike(Function)
* // => false
*
* isObjectLike(null)
* // => false
*/
function isObjectLike(value) {
return typeof value == 'object' && value !== null
}
export default isObjectLike
复制代码
这个方法就更加简单了,只要不是null的对象都是类对象,包括数组。
import isFlattenable from './isFlattenable.js'
/**
* The base implementation of `flatten` with support for restricting flattening.
*
* @private
* @param {Array} array The array to flatten.
* @param {number} depth The maximum recursion depth.
* @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
* @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
* @param {Array} [result=[]] The initial result value.
* @returns {Array} Returns the new flattened array.
*/
function baseFlatten(array, depth, predicate, isStrict, result) {
predicate || (predicate = isFlattenable)
result || (result = [])
if (array == null) {
return result
}
for (const value of array) {
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
baseFlatten(value, depth - 1, predicate, isStrict, result)
} else {
result.push(...value)
}
} else if (!isStrict) {
result[result.length] = value
}
}
return result
}
export default baseFlatten
复制代码
其实这就是一个普通的展开函数,核心就是递归调用,根据depth层数一层一层展开,每次depth-1,但是lodash考虑的方面还是挺多的,predicate是用来过滤的,满足条件的才会push进去,然后还开了一个后门(isStrict),如果没有满足predicate,但又不是在严格模式(isStrict)下,就直接把这个元素放到result的最后一个。在baseDifference中其实这个方法与其说是展开,不如说是用来过滤,过滤掉哪些不是类数组或者类对象的元素。
这里有个两个疑问,第一,result.push的时候为什么要把value解构,第二,为什么要用result[result.length],而不直接用push方法。
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'
/** 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.
*/
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
复制代码
baseDifference方法是diference的核心方法,前两个参数很容易理解,第三个参数是遍历时调用的方法,第四个参数是,isCommon用来标记是否为普通情况,非普通情况为有comparator和length大于等于200时,我们先来看普通情况,循环遍历values,如果在values的元素在array中存在则跳过,否则push到result中。
我们再来看非普通情况,非普通情况下values中不包含这些元素,则直接push到result中。那么我们只要搞明白两种非正常情况的定义,他们主要是对于includes方法的定义不同。这里再提一下循环的continue,break以及label,定义好label的话,可以直接跳到最外层,否则只能跳一层循环。
/**
* This function is like `arrayIncludes` except that it accepts a comparator.
*
* @private
* @param {Array} [array] The array to inspect.
* @param {*} target The value to search for.
* @param {Function} comparator The comparator invoked per element.
* @returns {boolean} Returns `true` if `target` is found, else `false`.
*/
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
复制代码
先看有comparator的情况,此时includes方法为arrayIncludesWith,在这个方法中用comparator去对比array中的每个元素和target是否相等,如果相等则直接返回true,否则返回false。
/**
* Checks if a `cache` value for `key` exists.
*
* @private
* @param {Object} cache The cache to query.
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function cacheHas(cache, key) {
return cache.has(key)
}
export default cacheHas
复制代码
其次是数组元素数量比较多的时候会用setCache来存数据,用cacheHas来判断是否含有此cache。关于SetCache会在下一篇再做详细说明。
总结: difference方法大概是做了这样一件事,先看这个原数组是否是一个类对象或类数组,如果是的话就先将表示exclude范围的参数进行过滤,过滤条件为是否为类对象或类数组,然后进行baseDifference对比,常规的baseDifference其实就是循环遍历,非常规的分为两种,一种是需要自己判断“相等”条件的,一种是数组length比较大的,需要采用cache的。