小时候,

乡愁是一枚小小的邮票,

我在这头,

母亲在那头。

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

我在这头,

新娘在那头。

后来啊,

乡愁是一方矮矮的坟墓,

我在外头,

母亲在里头。

而现在,

乡愁是一湾浅浅的海峡,

我在这头,

大陆在那头。

——余光中《乡愁》

本文为读 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. Lua 5.3 协程简单示例

    Lua 5.3 协程简单示例 来源 http://blog.csdn.net/vermilliontear/article/details/50547852 生产者->过滤器->消费者 模 ...

  2. HDU1255覆盖的面积

    覆盖的面积 Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submis ...

  3. CentOS6.8通过yum安装MySQL5.7

    Centos6.8通过yum安装mysql5.7 1.安装mysql的yum源 a.下载配置mysql的yum源的rpm包 根据上面3张图片中的操作下载下来的rpm文件可以通过如下命令获取: wget ...

  4. SVG绘制loading效果

    <div class="loading"> <svg width='40px' height='40px' xmlns="http://www.w3.o ...

  5. 《Github入门与实践》读书笔记 蟲咋先生的追求之旅(上)

    <Github入门与实践>作者: [日] 大塚弘记 译者:支鹏浩/刘斌   简介 本书从Git的基本知识和操作方法入手,详细介绍了GitHub的各种功能,GitHub与其他工具或服务的协作 ...

  6. freeMarker遍历map的正确方式

    假设selectDateModel 是我们后台返回的map<String, String>; <#list selectDateModel?keys as key> <o ...

  7. 再学习之MyBatis

    一.框架基本介绍 1.概念 支持普通SQL查询.存储过程和高级映射,简化和实现了Java 数据持久化层的的开源框架,主要流行的原因在于他的简单性和易使用性. 2.特点 持久层 .ORM(对象关系映射) ...

  8. selenium元素定位

    在网页自动化测试中,我们要让程序自动模拟我们的点击.输入.悬浮.拖动等操作,完成我们的测试用例组. 输入.点击.打开这样的动词,已经包含在了selenium的方法中,可以直接调用(当然你也可以自己写) ...

  9. 阿里云全民云计算活动:云服务器ECS二折起(云主机)采购指南

    首先要注册并登录阿里云,完成实名认证 可以用手机号新注册账号, 也可以使用淘宝账号直接登录,其他的登录方式还支持微博账号和支付宝账号等. 登录后如下图,先点"控制台", 然后鼠标移 ...

  10. Java字符编码

    今天在往oracle数据库里插入数据时发现,往一个20字节的字段里插入8个汉字加上一个括号,并没有提示字段超长.猜想数据库应该并没有用万国码(utf-8). 查询数据库编码sql:select * f ...