xm2by / blog Goto Github PK
View Code? Open in Web Editor NEW记录点滴学习
记录点滴学习
每天更新一个lodash方法源码解析
flatten()、flattenDeep()、flattenDepth()都是用于对数组的扁平化处理,不同之处在于扁平化的层级,flatten()是对数组进行一层扁平化处理,flattenDeep()是对数组完全扁平化处理,flattenDepth()是对数组进行指定层级的扁平化处理,其内部的实现都是基于baseFlatten()方法。
flatten()方法源码:
// flatten.js
// flatten方法用于对数组进行扁平化一层的操作
function flatten(array) {
// 取数组的length
const length = array == null ? 0 : array.length
// 当length大于0时,调用baseFlatten方法,扁平化层级为1
return length ? baseFlatten(array, 1) : []
}
flattenDeep()方法源码:
// flattenDeep.js
// flattenDeep方法用于对数组完全扁平化处理
function flattenDeep(array) {
// 取数组的length
const length = array == null ? 0 : array.length
// 当length大于0时,调用baseFlatten方法,扁平化层级为INFINITY(无限大),意思就是将数组中的元素扁平化到不能扁平化了为止
return length ? baseFlatten(array, INFINITY) : []
}
flattenDepth()方法源码:
// flattenDepth.js
// flattenDepth方法通过指定扁平化层级depth来实现数组扁平化到指定层
function flattenDepth(array, depth) {
// 取数组的length
const length = array == null ? 0 : array.length
// length为0,直接返回空数组
if (!length) {
return []
}
// 判断depth,当depth为undefined时取1,否则通过加运算符对其进行Number类型转换(PS:对任意值前通过一个加运算符操作可以自动实现其Number类型转换)
depth = depth === undefined ? 1 : +depth
// 调用baseFlatten方法,扁平化层级为depth
return baseFlatten(array, depth)
}
baseFlatten()方法源码:
// baseFlatten.js
// baseFlatten方法用于对数组扁平化处理,从源码中可以看出baseFlatten可提供的扁平化处理方式有三种,一种是完全扁平化,一种是扁平化指定层,最后一种是仅扁平化一层
// baseFlatten有5个参数:array:等待扁平化处理的数组,depth:扁平化的层级,predicate:数组中每个元素都需要调用的迭代器(扁平化时需根据predicate返回结果来决定处理方式),isStrict:是否严格模式(用于当depth小于0或者调用predicate返回值为false时的处理方式),result:最终返回的数组
function baseFlatten(array, depth, predicate, isStrict, result) {
predicate || (predicate = isFlattenable)
result || (result = [])
// 如果array为null,返回空数组
if (array == null) {
return result
}
// 遍历array数组
/*
遍历array数组的时候,在想一个问题,如果array数组中的一个元素是个对象,那它可以展开吗?
predicate方法很好的解决了我这个疑问,predicate方法默认是isFlattenable方法,isFlattenable方法就是用来判断一个值是否可以展开进行扁平化处理
*/
for (const value of array) {
// 遍历array数组时,如果depth大于0且当前元素可扁平化,那么继续判断depth是否大于1,大于1,则对当前元素继续调用baseFlatten方法,也就是采用递归的方式进行完全展开
// 如果depth等于1,那么采用es6扩展运算符(扩展运算符用三个点号表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值)处理并添加到result中
// 如果depth小于0或者predicate方法返回false时,此时需要判断isStrict是否为true,为true则直接返回result,否则将当前值添加到result中
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
}
}
// 最后返回result结果
return result
}
isFlattenable()方法源码:
// isFlattenable.js
// isConcatSpreadable是定义的一个只读属性,类型为布尔值,当一个对象拥有该属性且其值为true时,表明该对象可以调用Array.prototype.concat()方法来将其内部所有的元素组合到一个数组中
// 在isFlattenable方法中,它可以用来判断value是否可扁平化
const spreadableSymbol = Symbol.isConcatSpreadable
// isFlattenable方法用于判断value是否可扁平化
// 可扁平化的条件是value为类数组(数组或arguments对象)或者value对象存在isConcatSpreadable属性,且其值为true
function isFlattenable(value) {
return Array.isArray(value) || isArguments(value) ||
!!(value && value[spreadableSymbol])
}
看了上面的源码解析后可以通过下面例子加深一下:
_.flatten([1, 2, [3, [4, 5]], 6])
// [1, 2, 3, [4, 5], 6]
_.flatten([[1, [2, [3, 4, [5, [6]]]]], [7, [9, [10, 11]]]])
// [1, [2, [3, 4, [5, [6]]]], 7, [9, [10, 11]]]
_.flattenDeep([[1, [2, [3, 4, [5, [6]]]]], [7, [9, [10, 11]]]])
// [1, 2, 3, 4, 5, 6, 7, 9, 10, 11]
_.flattenDepth([[1, [2, [3, 4, [5, [6]]]]], [7, [9, [10, 11]]]], 3)
// [1, 2, 3, 4, [5, [6]], 7, 9, 10, 11]
实现思路:
flatten()、flattenDeep()、flattenDepth()都是用于对数组的扁平化处理,其内部的实现都是基于baseFlatten()方法;
baseFlatten()在实现时会对需要进行扁平化处理的数组进行遍历,对于遍历的每个元素都会根据扁平化层级depth以及当前元素是否可扁平化进行处理,当depth为1,则直接使用es6的扩展运算符将当前值添加到返回值result数组中,当depth大于1且当前元素可扁平话,则递归调用baseFlatten()方法,递归调用baseFlatten()方法时depth需不断减1;
2019/6/21
每天更新一个lodash方法源码解析
compact翻译过来表示紧凑的、紧密的;在lodash中,它用于将数组中虚假值过滤掉,虚假值表示该值为空或者无意义,虚假值都包括:false
、null
、0
、" "
、undefined
、NaN
,与其对应的是真值,表示该值有意义,compact方法就是将上面所提到的虚假值过滤掉。
_.compact([1, 0, true, false, 'a', null, {}, '', [], undefined, 'ok', NaN])
// [1, true, 'a', {}, [], 'ok']
// 过滤数组中的虚假值,虚假值包括:false、null、0、""、undefined、NaN
function compact(array) {
let resIndex = 0
// 定义result,一个空的数组
const result = []
// 如果array为null,直接返回一个空数组
if (array == null) {
return result
}
// 遍历数组array,判断数组中的每个元素是否为真值(转换成布尔值类型后是否为true,如果为true,表示真值,否则为虚假值),如果是真值,则添加到result数组中
// 过滤掉虚假值的方法是根据其转换为布尔值类型后是否为true,false表示是虚假值,需要过滤掉
for (const value of array) {
// 对于非布尔值类型的会自动进行一次类型转换
if (value) {
result[resIndex++] = value
}
}
// 最后返回结果result
return result
}
实现思路:遍历数组,对数组中每个元素进行一个真值判断(非布尔值类型的会自动转换成布尔值类型进行判断),只有当元素为真值时才会被添加到最终返回的数组中。
2019/6/18
每天更新一个lodash方法源码解析
chunk()方法是将数组中的元素进行分块,每一块为一个数组,最终返回由每个块组成的数组。
_.chunk([1, 3, 5, 7, 9], 2)
// [[1, 3], [5, 7], [9]]
_.chunk([1, 2, 3, 4, 5, 6], 3)
// [[1, 2, 3], [4, 5, 6]]
_.chunk([1, 2, 3, 4, 5], 0)
// []
_.chunk([1, 2, 3, 4, 5])
// [[1], [2], [3], [4], [5]]
chunk(arr, size)接收两个参数,一个是原数组,一个是分块的大小size,默认值为1,原数组中的元素会按照size的大小从头开始分块,每一块组成一个新数组,如果最后元素个数不足size的大小,那么它们会组成一个快。
function chunk(array, size) {
// size默认值为1
// 如果size小于0,取0处理,大于0,则取size值
size = Math.max(size, 0)
// 如果array为null,length取0,否则取array.length
const length = array == null ? 0 : array.length
// 如果length为0或者size小于1,返回一个空数组
if (!length || size < 1) {
return []
}
let index = 0
let resIndex = 0
// 用数组的长度除以size并向上取整以得到分块的个数,新建一个长度为分块个数的数组result
const result = new Array(Math.ceil(length / size))
// 下面的while循环主要做的事情是遍历array数组,每次截取array中的size个元素并将截取结果添加到result数组中
// while循环中index从0开始,每次增加size大小,直到index大于等于length时跳出循环
// 每次循环时,result数组中的索引resIndex加1
// 在每次循环体中,从array中截取索引为index到(index+size)之间的元素返回一个数组,并将返回结果添加到result数组中
// 截取array元素时使用的方法slice实现可以查看slice对应的源码分析
while (index < length) {
result[resIndex++] = slice(array, index, (index += size))
}
// 返回最终结果result
return result
}
实现思路:利用原数组的长度和size计算出需要分块的数量,然后利用分块数量生成一个新数组,然后遍历原数组,截取index到index+start之间的元素(包含start,不包含index+start)并依次给到新数组中,最后返回新数组。
2019/6/16
drop()、dropRight()、dropWhile()、dropRightWhile()方法也是用来截取数组中的元素,其内部都是基于slice方法实现的。_.drop(array, n)
是从索引值n
开始截取到array.length
为止;_.dropRight(array, n)
就是从索引0
开始且截取到索引为array.length - n
为止;
_.drop([{a:1}, 2, ['b', 'c'], 3 ,5], 2)
// [['b', 'c'], 3 ,5]
_.dropRight([{a:1}, 2, ['b', 'c'], 3 ,5], 2)
// [{a:1}, 2, ['b', 'c']]
function less4(a) {
return a < 4
}
_.dropWhile([3, 2, 5, 1, 4], less4)
// 从左侧开始,开始索引为less4返回值为false时对应的索引,可知5调用less4时返回false,所以返回值为:[5, 1, 4]
_.dropRightWhile([3, 2, 5, 4, 1], less4)
// 从右侧开始,开始索引为less4返回值为false时对应的索引,可知4调用less4时返回false,所以返回值为:[3, 2, 5, 4]
drop方法解析:
// drop.js
// drop方法用于截取数组元素,从索引值n开始截取到索引值为array.length为止,n默认为1
// 内部使用的是slice方法,之前关于slice的源码已经有说过,可见https://blog.csdn.net/XuM222222/article/details/92380598
function drop(array, n=1) {
// 取数组的长度
const length = array == null ? 0 : array.length
// 当数组的长度大于0时调用slice方法,否则返回空数组
// slice是用来截取数组元素的,在调用时需传入开始截取的位置索引值,在drop方法中指的就是参数n,截取结束索引值为数组的长度
return length
? slice(array, n < 0 ? 0 : n, length)
: []
}
dropRight方法解析:
// dropRight.js
// dropRight方法与drop方法相似,不同的地方是drop是从左侧开始截取,dropRight是从右侧开始截取
function dropRight(array, n=1) {
// 获取array的长度
const length = array == null ? 0 : array.length
// 当数组长度大于0,调用slice方法,否则返回空数组
// slice传入参数依次为array、0(数组截取开始索引值)、-n(数组截取结束索引值)
// slice(array, 0, -n)等价于slice(array, 0, length-n)
return length ? slice(array, 0, n < 0 ? 0 : -n) : []
}
dropWhile、dropRightWhile方法内部都调用了baseWhile方法,baseWhile方法内部是基于slice实现的;详细可见下面代码:
dropWhile方法解析:
// dropWhile.js
// dropWhile方法用于从predicate返回值为false的索引值开始截取array数组
// dropWhile内部调用了baseWhile方法
function dropWhile(array, predicate) {
// 当array不为null且长度大于0时,调用baseWhile方法,否则返回空数组
// 调用baseWhile方法时传了三个参数,依次为array、predicate函数(数组元素中循环时都会调用该函数,其返回值决定了是否继续循环)、true表示是drop的行为
return (array != null && array.length)
? baseWhile(array, predicate, true)
: []
}
dropRightWhile方法解析:
// dropRightWhile.js
// dropRightWhile用于从0开始截取array数组,直到predicate返回值为false
function dropRightWhile(array, predicate) {
// 当array不为null且长度大于0时,调用baseWhile方法,否则返回空数组
// 调用baseWhile方法时传了三个参数,依次为array、predicate函数(数组元素中循环时都会调用该函数,其返回值决定了是否继续循环)、true表示是drop的行为、true表示是否从右侧开始
return (array != null && array.length)
? baseWhile(array, predicate, true, true)
: []
}
baseWhile方法解析:
// baseWhile.js
function baseWhile(array, predicate, isDrop, fromRight) {
// 获取array的长度
const { length } = array
// 判断截取是从左侧开始还是从右侧开始,如果左侧开始index为-1,右侧开始index取length
let index = fromRight ? length : -1
// 如果从左侧开始截取,while循环如下:
/*
while((++index < length) && predicate(array[index], index, array)){}
*/
// index值不停的加1直到predicate(array[index], index, array)返回false时停止增加,或者index > length时停止增加
// 此时如果isDrop为true,最终执行结果为slice(array, index, length)
// 此时如果isDrop为false,最终执行结果为slice(array, 0, index)
// 如果从右侧开始截取,while循环如下:
/*
while((index--) && predicate(array[index], index, array)){}
*/
// index值不停的减1直到predicate(array[index], index, array)返回false时停止减小,或者index < 0时停止减小
// 此时如果isDrop为true,最终执行结果为slice(array, 0, index + 1)
// 此时如果isDrop为false,最终执行结果为slice(array, index+1, length)
// predicate是个函数,在index变化过程中,它会对index对应的元素执行predicate函数,当predicate返回值为true时继续执行循环,当predicate为false时结束循环
while ((fromRight ? index-- : ++index < length) &&
predicate(array[index], index, array)) {}
// isDrop用来表示截取数组元素时应该返回哪部分
// 以从左侧开始截取为例,开始索引为index,当isDrop为true,执行结果为slice(array, index, length),当isDrop为false,执行结果为slice(array, 0, index),因此isDrop表明了0到index前的元素时去掉还是保留,true表示去掉,false表示保留
// 右侧截取同理
return isDrop
? slice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
: slice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index))
}
实现思路:
drop(array, n)、dropRight(array, n)、dropWhile(array, predicate)、dropRightWhile(array, predicate)方法是基于slice方法实现的,用于截取数组元素;
drop方法规定了从左侧开始截取,开始索引为n,结束索引为array.length;
dropRight方法规定了从右侧开始截取,开始索引为0,结束索引为length-n;
dropWhile方法规定了从左侧开始截取,开始索引为array数组中元素调用predicate方法时返回值为false时对应的索引,结束索引为array.length;
dropRightWhile规定了从右侧开始截取,开始索引为0,结束索引为array数组中元素调用predicate方法时返回值为false时对应的索引加1;
2019/6/20
每天更新一个lodash方法源码解析
slice用于对数组元素进行截取,返回值为截取元素组成的一个新数组。slice方法很容易让人联想到Array.slice(),它是不是对Array.slice()的一层封装?答案是no,lodash自己实现了一个slice方法来替代Array.slice()方法。
官方文档中这句话也证明了,上面的话意思就是:使用该方法(_.slice())来替代Array.slice()以确保返回密集数组。
_.slice([1, 2, 3, 4, 5], 2, 4)
// [3, 4]
_.slice([1, 2, 3, 4, 5])
// [1, 2, 3, 4, 5]
slice(array, [start = 0], [end = array.length])接收3个参数,第一个参数array为原数组,也就是将要截取的元素;第二个参数为start,数组截取开始时的位置,默认为0;第三个参数为截取结束时的位置,默认为原数组的长度,截取时截取的元素是不包含结束位置的元素的。
function slice(array, start, end) {
// 如果array为null,length为0,否则取array.length
let length = array == null ? 0 : array.length
// 如果length为0,则返回空数组
if (!length) {
return []
}
// 判断第二个参数start有没有传值,没有传值则取0,传值则取传入的值
start = start == null ? 0 : start
// 判断第三个参数end有没有传值,没有传值则取array的长度,传值则取传入的值
end = end === undefined ? length : end
// 处理start小于0的情况
// 如果start小于0且-start的值大于array的长度,则start取0,否则start取数组长度加start的值
if (start < 0) {
start = -start > length ? 0 : (length + start)
}
// 当end值大于数组长度时,end取数组的长度
end = end > length ? length : end
// 处理end小于0的情况
// 当end小于0时,end取数组长度加end的值
if (end < 0) {
end += length
}
// 如果start大于end,则length取0,否则取end-start值
// 其中(end-start) >>> 0作用是保证(end-start)是一个有意义的正整数,详细可参考这篇文章:https://segmentfault.com/a/1190000014613703
// 执行下面语句后,length值将变为返回数组的长度
length = start > end ? 0 : ((end - start) >>> 0)
// 确保start为一个有意义的正整数
start >>>= 0
// 定义一个index,后面将作为数组result的索引值
let index = -1
// 定义返回数组result,其长度为length
const result = new Array(length)
// 遍历数组result,并将原数组array中index+start对应的元素添加到result数组中index对应的位置
while (++index < length) {
result[index] = array[index + start]
}
// 返回结果result
return result
}
实现思路:根据start和end参数计算出返回数组的长度,利用返回数组长度生成一个新数组,然后遍历该新数组,并将原数组中index+start对应的值给到新数组index对应的位置。
2019/6/17
为何将 _.difference()
、 _.differenceBy()
、_.differenceWith()
三个方法放在一起分析呢?因为它们的内部都是基于baseDifference()
方法进行处理,只不过是传入baseDifference()
的参数不同罢了。
下面是difference.js、differenceBy.js、differenceWith.js文件的内容:
// difference.js
function difference(array, ...values) {
// 判断array是否是个类数组对象,如果是,则调用baseDifference方法处理,如果不是,则返回空数组
// 在调用baseDifference处理前,通过调用baseFatten方法将传入的values扁平化处理,关于baseFlatten扁平化处理的方法可见baseFlatten源码解析
// 从baseFlatten传入的参数可知:扁平化处理的层级为1,遍历values数组中的元素时每次都会调用isArrayLikeObject方法判断元素是否是类数组对象,且是严格模式下扁平化处理
// baseDifference方法的比较可见baseDifference源码解析
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
: []
}
// differenceBy.js
function differenceBy(array, ...values) {
// 取values数组中的最后一个元素为迭代器iteratee
let iteratee = last(values)
// 如果迭代器iteratee是个类数组对象,则迭代器iteratee为undefined
if (isArrayLikeObject(iteratee)) {
iteratee = undefined
}
// 如果array是类数组,则调用baseDifference方法,baseDifference参数为array、扁平化处理的values以及迭代器iteratee函数,否则返回空数组
// 这块可能会有个疑问,就是如果values数组的最后一个元素为迭代器iteratee函数,那么在扁平化的时候是不是把这个函数也扁平化进去?答案是no,baseFlatten中第三个参数就是帮我们将非类数组类型的元素过滤掉
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), iteratee)
: []
}
// differenceWidth.js
function differenceWith(array, ...values) {
// 取values数组的最后一个元素为比较器comparator
let comparator = last(values)
// 如果比较器comparator是非数组类型,则比较器comparator为undefined
if (isArrayLikeObject(comparator)) {
comparator = undefined
}
// 如果array为类数组对象,则调用baseDifference方法,baseDifference的参数依次为array、扁平化处理的values数组、undefined、比较器comparator,否则返回空数组
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
: []
}
从上面的代码中可以看出当array为类数组时,调用baseDifference方法,那么baseDifference方法是什么呢?
// baseDifference.js
// baseDifference方法用来将array数组与values数组进行对比,将存在于两者之中的元素从array数组中剔除掉,array中剩余的值组成一个新数组返回
// 对比的时候可以传入迭代器iteratee函数,array和values数组中的每个元素都会调用迭代器iteratee进行处理,然后chubaseDifference对比处理后的值
// 对比的时候也可以传入比较器函数,在对比的时候调用comparator来比较array和values中每个元素,可以理解为比较器comparator定义了对比的规则,默认是看两个值是否相等
// baseDifference方法会接收4个参数,依次为需要处理的array数组、用来对比的values数组、迭代器iteratee、比较器comparator
// 迭代器iteratee是个function,array和values中的每个元素都需要调用该方法处理
function baseDifference(array, values, iteratee, comparator) {
// includes用于储存判断数组中是否存在某个值的方法
let includes = arrayIncludes
// isCommon用于区分是普通对比,还是特殊对比
let isCommon = true
const result = []
const valuesLength = values.length
// array为空直接返回空数组
if (!array.length) {
return result
}
// 如果iteratee存在,则遍历values并对其中每个元素都调用iteratee方法
if (iteratee) {
values = map(values, (value) => iteratee(value))
}
// 如果存在比较器comparator,则为特殊对比,includes为arrayIncludesWith,其中arrayIncludesWith方法中就可以传入比较器comparator
if (comparator) {
includes = arrayIncludesWith
isCommon = false
}
else if (values.length >= LARGE_ARRAY_SIZE) {
// LARGE_ARRAY_SIZE是个常量,值为200,values长度超过200,则为特殊处理,includes为cacheHas
includes = cacheHas
isCommon = false
values = new SetCache(values)
}
// 遍历array数组,
outer:
for (let value of array) {
// 如果存在iteratee,用iteratee处理value
const computed = iteratee == null ? value : iteratee(value)
value = (comparator || value !== 0) ? value : 0
// 如果isCommon为true且computed === computed时遍历values,判断values中的元素是否有与computed相同的,如果没有则将当前value添加result中
if (isCommon && computed === computed) {
let valuesIndex = valuesLength
// 遍历values,当values中有元素与computed相同时,跳出当前array的循环,继续进行array的下一个循环,这样可以减少不必要的循环
// 只有当遍历完values中所有的元素后,如果都没有与computed相同的,说明当前value是array独有的,那么将value添加到result中
while (valuesIndex--) {
if (values[valuesIndex] === computed) {
continue outer
}
}
result.push(value)
}
else if (!includes(values, computed, comparator)) {
// isCommon为false的条件是存在comparator或者values.length超过200,此时会调用includes进行判断
// 如果includes返回的结果为false,则表明在比较器comparator的规则下,values中包含computed,此时需将value添加到result中
result.push(value)
}
}
// 返回结果result
return result
}
实现思路:
difference、differenceBy、differenceWith方法都可以将一个数组(array)与多个数组(array1、array2、array3 ...)进行对比,以剔除其**同存在的元素,在与多个数组(array1、array2、array3 ...)对比的时候,利用数组扁平化处理,将多个数组(array1、array2、array3 ...)整合成一个数组(values),然后进行比较;
比较的时候,根据传入参数的不同采用不同的比较方法,对于difference方法,在对比array数组和整合数组values时,采用双层循环对比的一种方法,在外层遍历array数组,内部遍历values数组,判断values是否存在array数组中的元素,对于values中不存在array数组中的元素,则添加到新数组中返回,其中在双层循环时采用了标签语法,就是源码中的outer和continue outer语句,这样写的好处就是当values数组中存在array中的元素时就跳出当前循环,继续array的下一次循环,减少不必要的变量;
对于differenceBy方法,它的实现跟difference相似,不同的地方在于:difference在对比array和values数组时是对比数组中的原始数据,而differenceBy则是对array和values数组中的每个元素调用迭加器函数进行处理,然后对比处理后的值;
对于differenceWith方法,与前两种有些不太一样,前两种都是默认比较array和values中元素是否相同,而differenceWith通过传入一个比较器来自定义比较规则,differenceWith也是基于双层循环,外层循环array数组,内层遍历values数组,内层遍历时不再是简单的对比values中值与array的元素是否相同,而是在比较器的规则下将values中的值与array中的值对比,比较器返回true的表明array中该值需要剔除,否则会添加到新数组返回;
看完上面的长篇文字后,可以通过几个例子来看一下结果:
_.difference([1, 3, 5], [1, [2, 3]], [4, 6])
// [1, [2, 3]], [4, 6]扁平化处理后得到[1, [2, 3], 4, 6],对比后结果为[3, 5]
// [3, 5]
_.difference([1, [2, 3], 5], [1, [2, 3]], [4, 6])
// [1, [2, 3]], [4, 6]扁平化处理后得到[1, [2, 3], 4, 6],由于两个数组中的[2, 3]不是同一个引用,所以对比后结果为[[2, 3], 5]
// [[2, 3], 5]
_.differenceBy([1.1, 3.6, 5.2], [1.6, 2.3, 3.7], Math.floor)
// 两个数组中每个元素经过Math.floor处理后变成[1, 3, 5]和[1, 2, 3],最终对比结果为[5.2]
// [5.2]
function customCompare(a, b) {
return a - b === 1
}
_.differenceWith([1, 3, 5], [1, 4, 3], customCompare)
// [1, 3, 5]中的每个元素与[1, 4, 3]比较,只有相差1的时候customCompare返回true,即不需要添加到新数组中返回,可以看出[1, 3, 5]中只有5与[1, 4, 3]中的4比较时差1,最终对比结果为[1, 3]
// [1, 3]
其实对于difference()、differenceBy()、differenceWith()方法的源码解析,里面涉及到一些其他方法,如:isArrayLikeObject、baseFlatten、arrayIncludes、arrayIncludesWith、cacheHas等等方法,我们在这儿没有详细展开,仅仅简单的介绍了它们的作用,因为我们主要分析的是difference实现的逻辑,其他的在这儿就不是重点了(看源码,主次要分明),不过这些方法会在后面需要的时候再进一步详细介绍;
遗留一个问题,就是上面baseDifference方法中,有个computed === computed的判断,一直不明白为什么,难道还会有computed与computed不等的情况?
2019/6/19
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.