外部世界那些破旧与贫困的样子,可以使我内心世界得到平衡。

——卡尔维诺《烟云》

本文为读 lodash 源码的第十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash

gitbook也会同步仓库的更新,gitbook地址:pocket-lodash

作用与用法

baseDifference 可以用来获取指定数组与另一个数组的差集。

这个函数是内部函数,是后面实现其它比较函数的核心函数。

baseDifference 的方法签名如下:

baseDifference(array, values, iteratee, comparator)

第一和第二个参数是需要比较的两个数组;iteratee 可以返回一值映射值,比较时,可以使用映射的值来进行比较; comparator 是自定义比较函数,如果有传递,则调用自定义的比较函数来进行交集的比较。

依赖

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'

lodash源码分析之缓存使用方式的进一步封装

lodash源码分析之arrayIncludes

lodash源码分析之arrayIncludesWith

lodash源码分析之map的实现

lodash源码分析之cacheHas

源码分析

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
}

iteratee的调用

if (iteratee) {
values = map(values, (value) => iteratee(value))
}

如果有传递 iteratee ,则先调用 map ,使用 iteratee 生成要比较数组的映射数组 values。

因为后面会有嵌套循环,避免重复调用 iteratee ,影响性能,所以一开始就需要生成 values 的映射数组。

性能优化

这里使用了 isCommon 来标志是否使用普通方式来处理。

if (comparator) {
includes = arrayIncludesWith
isCommon = false
}

如果有传递比较函数,则将 isCommon 标记为 false,表示不用普通的方式来处理,后面可以看到,最后会使用 includes 方法来处理,也即 arrayIncludesWith 方法。

else if (values.length >= LARGE_ARRAY_SIZE) {
includes = cacheHas
isCommon = false
values = new SetCache(values)
}

如果不需要使用自定义的比较方式,并且数组较大时(这里限定了200),则使用 SetCache 类来缓存数组。

SetChche 其实使用的是 Map/Set 或者对象的方式来存储,避免大数组嵌套循环时造成的性能损耗。

循环比较

接下来就遍历第一个数组 array,将数组中的每一项和第二个数组的每一项比较。

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)
}

可以看到,如果 isCommon 没有标记为 false, 或者需要比较的值 computed 不为 NaN 时,都采用嵌套循环的方式来比较。循环完毕,没有在第二个数组中发现相同的项时,将该项存入数组 result 中。

如果 isCommon 为 false 或者需要比较的值为 NaN 时,则调用 includes 方法来比较。

由之前的分析得知:

  • 如果指定 comparator ,则 includes 为 arrayIncludesWith
  • 如果被比较的数组 values 的长度超过 200 ,则 includes 为 cacheHas
  • 否则,includes 为 arrayIncludes

+0与-0的处理

在看代码的时候,有一段十分奇怪:

value = (comparator || value !== 0) ? value : 0

这段代码的意思是,在没有提供 comparator 的情况下,如果 value === 0 ,则将 value 赋值为 0 。

value === 0 时,可能为 +0 、-0 和 0 ,lodash 为什么要将它们都转为 0 呢?

后来看到 lodash 作者在 issue 中说,因为比较会用到 Set ,而 Set 是不能区分 +0 和 -0 的。

参考

Lodash系列——difference函数源码解析

value = (comparator || value !== 0) ? value : 0; does it work?

License

署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)

最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

作者:对角另一面

lodash源码分析之数组的差集的更多相关文章

  1. lodash源码分析之自减的两种形式

    这个世界需要一个特定的恶人,可以供人们指名道姓,千夫所指:"全都怪你". --村上春树<当我谈跑步时我谈些什么> 本文为读 lodash 源码的第六篇,后续文章会更新到 ...

  2. lodash源码分析之List缓存

    昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方 顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免/因为风的缘故 --洛夫<因为风的缘故& ...

  3. lodash源码分析之缓存方式的选择

    每个人心里都有一团火,路过的人只看到烟. --<至爱梵高·星空之谜> 本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitb ...

  4. lodash源码分析之缓存使用方式的进一步封装

    在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...

  5. lodash源码分析之baseFindIndex中的运算符优先级

    我悟出权力本来就是不讲理的--蟑螂就是海米:也悟出要造反,内心必须强大到足以承受任何后果才行. --北岛<城门开> 本文为读 lodash 源码的第十篇,后续文章会更新到这个仓库中,欢迎 ...

  6. lodash源码分析之chunk的尺与刀

    以不正义开始的事情,必须用罪恶使它巩固. --莎士比亚<麦克白> 最近很多事似乎印证了这句话,一句谎言最后要用一百句谎言来圆谎. 本文为读 lodash 源码的第二篇,后续文章会更新到这个 ...

  7. lodash源码分析之compact中的遍历

    小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...

  8. lodash源码分析之Hash缓存

    在那小小的梦的暖阁,我为你收藏起整个季节的烟雨. --洛夫<灵河> 本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbo ...

  9. lodash源码分析之NaN不是NaN

    暗恋之纯粹,在于不求结果,完全把自己锁闭在一个单向的关系里面. --梁文道<暗恋到偷窥> 本文为读 lodash 源码的第五篇,后续文章会更新到这个仓库中,欢迎 star:pocket-l ...

随机推荐

  1. [bzoj4552][Tjoi2016&Heoi2016]排序-二分+线段树

    Brief Description DZY有一个数列a[1..n],它是1∼n这n个正整数的一个排列. 现在他想支持两种操作: 0, l, r: 将a[l..r]原地升序排序. 1, l, r: 将a ...

  2. angular+require前端项目架构搭建

    app //应用入口 directive //自定义指令 require-main //require的主配置文件  存放公共调用的js service //请求后端数据公有类 controllers ...

  3. Node.js 基础介绍(一)

    Node.js 学习笔记一) 简单介绍--名称 Node.js,平时听到有好几种叫法,node .Node.js.nodejs ,但是比较正式的称呼还是"Node.js",由于它是 ...

  4. vim学习、各类插件配置与安装

    vim学习.各类插件配置与安装 vim 插件 配置 1. vim学习 vim基础学习:根据网上流行基础文章<简明Vim练级攻略>,进阶书籍<vim实用技巧>.注:进阶书籍可以在 ...

  5. Yii2重置密码

    //Yii框架下hash密码正确的比较方法$result = Yii::$app->security->validatePassword($password, $hash);echo $r ...

  6. 1.2 Python开发环境

    1.2.1 百家争鸣的繁荣景象 工欲善其事,必先利其器.学习编程也是同样的道理,熟悉开发环境应该是学习一门编程语言的第一步. IDLE是Python的官方标准开发环境,从官网www.python.or ...

  7. POJ - 3087 模拟 [kuangbin带你飞]专题一

    模拟洗牌的过程,合并两堆拍的方式:使先取s2,再取s1:分离成两堆的方式:下面C张放到s1,上面C张到s2.当前牌型与第一次相同时,说明不能搜索到答案. AC代码 #include<cstdio ...

  8. Effective Java 第三版——34. 使用枚举类型替代整型常量

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Shell脚本——特殊符号

    1. # 井号(pound) 脚本文件运行时,使用的解释器. #!/bin/sh 其他时候表示注释. # This is a comment. 2. ~ 波浪号(tilde) 表示当前用户使用的hom ...

  10. Ubuntu上搭建Hadoop环境(单机模式+伪分布模式)

    首先要了解一下Hadoop的运行模式: 单机模式(standalone)        单机模式是Hadoop的默认模式.当首次解压Hadoop的源码包时,Hadoop无法了解硬件安装环境,便保守地选 ...