Code Monkey home page Code Monkey logo

blog's People

blog's Issues

flatten、flattenDeep、flattenDepth

flatten()、flattenDeep()、flattenDepth()

每天更新一个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])
}

看了上面的源码解析后可以通过下面例子加深一下:

example:
_.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

compact

_.compact(array)

每天更新一个lodash方法源码解析

compact翻译过来表示紧凑的、紧密的;在lodash中,它用于将数组中虚假值过滤掉,虚假值表示该值为空或者无意义,虚假值都包括:falsenull0" "undefinedNaN,与其对应的是真值,表示该值有意义,compact方法就是将上面所提到的虚假值过滤掉。

example:
_.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

chunk

_.chunk(array, [size = 1])

每天更新一个lodash方法源码解析

chunk()方法是将数组中的元素进行分块,每一块为一个数组,最终返回由每个块组成的数组。

example:
_.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

drop()、dropRight()、dropWhile()、dropRightWhile()

drop()、dropRight()、dropWhile()、dropRightWhile()方法也是用来截取数组中的元素,其内部都是基于slice方法实现的。_.drop(array, n)是从索引值n开始截取到array.length为止;_.dropRight(array, n)就是从索引0开始且截取到索引为array.length - n为止;

example:
_.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

slice

_.slice(array, [start = 0], [end = array.length])

每天更新一个lodash方法源码解析

slice用于对数组元素进行截取,返回值为截取元素组成的一个新数组。slice方法很容易让人联想到Array.slice(),它是不是对Array.slice()的一层封装?答案是no,lodash自己实现了一个slice方法来替代Array.slice()方法。

在这里插入图片描述
官方文档中这句话也证明了,上面的话意思就是:使用该方法(_.slice())来替代Array.slice()以确保返回密集数组。

example:
_.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

_.difference()、 _.differenceBy()、 _.differenceWith()

为何将 _.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中该值需要剔除,否则会添加到新数组返回;

看完上面的长篇文字后,可以通过几个例子来看一下结果:

example:
_.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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.