小时候,

乡愁是一枚小小的邮票,

我在这头,

母亲在那头。

长大后,乡愁是一张窄窄的船票,

我在这头,

新娘在那头。

后来啊,

乡愁是一方矮矮的坟墓,

我在外头,

母亲在里头。

而现在,

乡愁是一湾浅浅的海峡,

我在这头,

大陆在那头。

——余光中《乡愁》

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

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

作用与用法

compact 函数用来去除数组中的假值,并返回由不为假值元素组成的新数组。

falsenull0""undefinedNaN 都为假值。

例如:

var arr = [1,false,2,null,3,0,4,NaN,5,undefined]
_.compact(arr) // 返回 [1,2,3,4,5]

源码

function compact(array) {
let resIndex = 0
const result = [] if (array == null) {
return result
} for (const value of array) {
if (value) {
result[resIndex++] = value
}
}
return result
}

compact 的源码只有寥寥几行,相当简单。

首先判断传入的数组是否为 null 或者 undefined,如果是,则返回空数组。

然后用 for...of 来取得数组中每项的值,如果不为假值,则存入新数组 result 中,最后将新数组返回。

到这里,源码分析完了。

但是在看源码的时候,发现这里用了 for...of 来做遍历,其实除了 for...of 外,也可以用 for 或者 for...in 来做遍历,那为什么最后选了 for...of 呢?

数组中的for循环

使用 for 循环,很容易就将 compact 中关于循环部分的源码改写成以下形式:

for (let i = 0; i < array.length; i++) {
const value = array[i]
if (value) {
result[resIndex++] = value
}
}

这样写,肯定是没有问题的,但是不够简洁。

for…in

再来看 for...in 循环,先来将源码改写一下:

for (let index in array) {
const value = array[i]
if (value) {
result[resIndex++] = value
}
}

先看看MDN上关于 for...in 的用法:

for...in语句以任意顺序遍历一个对象的可枚举属性

关于可枚举属性,可以点击上面的链接到MDN上了解一下,这里不做太多的解释。

在数组中,数组的索引是可枚举属性,可以用 for...in 来遍历数组的索引,数组中的稀疏部分不存在索引,可以避免用 for 循环造成无效遍历的弊端。

但是,for...in 有两个致命的特性:

  1. for...in 的遍历不能保证顺序
  2. for...in 会遍历所有可枚举属性,包括继承的属性。

for...in 的遍历顺序依赖于执行环境,不同执行环境的实现方式可能会不一样。单凭这一点,就断然不能在数组遍历中使用 for...in,大多数情况下,顺序对于数组的遍历都相当重要。

关于第二点,先看个例子:

var arr = [1,2,3]
arr.foo = 'foo'
for (let index in arr) {
console.log(index)
}

在这个例子中,你期望输出的是 0,1,2,但是最后输出的可能是 0,1,2,foofor...in 不能保证顺序)。因为 foo 也是可枚举属性,在 for..in 会被遍历出来。

for…of

最后来看看 for...of

当我们在控制台中打印一个数组,并将它展开来查看时,会在数组的原型链上发现一个很特别的属性 Symbol.iterator

其实 for...of 循环内部调用的就是数组原型链上的 Symbol.iterator 方法。

Symbol.iterator 在调用的时候会返回一个遍历器对象,这个遍历器对象中包含 next 方法,for...of 在每次循环的时候都会调用 next 方法来获取值,直到 next 返回的对象中的 done属性值为 true 时停止。

其实我们也可以手动调用来模拟遍历的过程:

const arr = [1,2,3]
const iterator = a[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}

知道这些原理后,完全可以改写数组中的 Symbol.iterator 方法,例如遍历时将数组中的值都乘2:

Array.prototype[Symbol.iterator] = function () {
let index = 0
const _self = this
return {
next: function () {
if (index < _self.length) {
return {value: _self[index++] * 2, done: false}
} else {
return {done: true}
}
}
}
}

使用 Generator 函数可以写成以下的形式:

Array.prototype[Symbol.iterator] = function* () {
let index = 0
while (index < this.length) {
yield this[index++] * 2
}
}

因此在不改写 Symbol.iterator 的情况下,使用 for...of 来遍历数组是安全的,因为这个方法是数组的原生方法。

关于 IteratorGenerator 可以点击参考中的链接详细查看。

参考

  1. MDN:迭代器和生成器
  2. Iterator 和 for...of 循环
  3. Generator 函数的语法
  4. Lodash源码讲解(3)-compact函数
  5. MDN:for...of
  6. MDN:for…in

License

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

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

作者:对角另一面

lodash源码分析之compact中的遍历的更多相关文章

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

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

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

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

  3. lodash源码分析之List缓存

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

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

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

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

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

  6. lodash源码分析之数组的差集

    外部世界那些破旧与贫困的样子,可以使我内心世界得到平衡. --卡尔维诺<烟云> 本文为读 lodash 源码的第十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodas ...

  7. angular源码分析:angular中脏活累活的承担者之$interpolate

    一.首先抛出两个问题 问题一:在angular中我们绑定数据最基本的方式是用两个大括号将$scope的变量包裹起来,那么如果想将大括号换成其他什么符号,比如换成[{与}],可不可以呢,如果可以在哪里配 ...

  8. angular源码分析:angular中入境检察官$sce

    一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...

  9. angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

    angular的工具函数 在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来 angular.bind //用户将函数和对象绑定在一起,返回一个新的函数 angu ...

随机推荐

  1. Ruby on Rails---Active Admin使用(一)

    概述 Active Admin提供了一个友好的后台管理界面,将CRUD等操作可视化,操作极其方便 安装 1. 添加gem gem "devise", :github => ' ...

  2. 2-SAT问题总结

    2-SAT问题总结 2-SAT问题:n个布尔型的变量,给出m个约束条件,约束条件例如:A,B不能同时为真,A,B必须同时为真等. 看了算法入门经典中的解决办法,关于这种解决办法比较容易理解,并且效率也 ...

  3. HDU1792A New Change Problem(GCD规律推导)

    A New Change Problem Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  4. Constructing Roads(最小生成树)

    Constructing Roads Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...

  5. linux进程资源占用高原因分析命令记录

    1.查看进程的线程: ps -eLf|egrep 'gateserver|UID' 2.跟踪线程调用: strace -p 15530 3.统计线程中函数的调用小号CPU时间: strace -p 1 ...

  6. jsoup从表单中取数据

    表单的格式如下 <td>user</td> <td>cc</td> </tr> <tr> <td>pass</ ...

  7. 验证Oracle处理速度

    (这是2009年写的东西了,在网上看到有人对数据库批量操作的'速度'比较关注,于是就把这篇老文章整理了一下) 一.环境及前提 在244上(一台稍好一些的机器,做了RAID,机械硬盘,Raid几忘了), ...

  8. Web桌面应用框架3:Web桌面应用开发的N种Style

    研究Web桌面应用开发有一段时间了,总结了Web桌面应用开发的一些主流方式. 一.前端Style 这种方式的就是直接实现一个Web程序,再封装一个浏览器展示,相当粗暴和有效.著名的框架就是Electr ...

  9. spring返回@ResponseBody报406

    HTTP Status 406 - type Status report message description The resource identified by this request is  ...

  10. OpenCV二维Mat数组(二级指针)在CUDA中的使用

    CUDA用于并行计算非常方便,但是GPU与CPU之间的交互,比如传递参数等相对麻烦一些.在写CUDA核函数的时候形参往往会有很多个,动辄达到10-20个,如果能够在CPU中提前把数据组织好,比如使用二 ...