lodash源码分析之compact中的遍历

小时候,

乡愁是一枚小小的邮票,

我在这头,

母亲在那头。

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

我在这头,

新娘在那头。

后来啊,

乡愁是一方矮矮的坟墓,

我在外头,

母亲在里头。

而现在,

乡愁是一湾浅浅的海峡,

我在这头,

大陆在那头。

——余光中《乡愁》

本文为读 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
    }
  }

这样写,肯定是没有问题的,但是数组不总是密集的,也有可能是稀疏数组,假如:var arr = [1,2,3,,4,,5] 这样的稀疏数组,会出现2次无效的循环。

关于稀疏数组,可以看本系列的第一篇文章《读lodash源码之从slice看稀疏数组与密集数组》。

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 来遍历数组是安全的,因为这个方法是数组的原生方法,而且使用 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)

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

作者:对角另一面

时间: 2024-10-08 00:48:36

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

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

在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbook也会同步仓库的更新,gitbook地址:pocket-lodash 前言 在之前的<lodash源码分析之Hash缓存>和<lodash源码分析之List缓存>介绍过 lodash 的两种缓存方式,在<lodash源码分析之缓存方式的选择&g

Live555源码分析[2]:RTSPServer中的用户认证

http://blog.csdn.net/njzhujinhua @20140601 说到鉴权,这是我多年来工作中的一部分,但这里rtsp中的认证简单多了,只是最基本的digest鉴权的策略. 在Live555的实现中, 用户信息由如下类维护,其提供增删查的接口.realm默认值为"LIVE555 Streaming Media" class UserAuthenticationDatabase { public: UserAuthenticationDatabase(char con

lodash源码分析之Hash缓存

在那小小的梦的暖阁,我为你收藏起整个季节的烟雨. --洛夫<灵河> 本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbook也会同步仓库的更新,gitbook地址:pocket-lodash 作用与用法 Hash 顾名思义,就是要有一个离散的序列,根据 key 来储取数据.而在 javascript 中,最适合的无疑是对象了. Hash 在 lodash 的 .internal 文件夹中,作为内部文件来使用.lodash 会根

Spring Boot(3):加载DataSource过程的源码分析及yml中DataSource的配置

Spring Boot实现了自动加载DataSource及相关配置.当然,使用时加上@EnableAutoConfiguration注解是必须的.下面就是对这一部分的源码分析. (1)Spring Boot启动后会调用org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.下面是部分源码. 1 @Configuration 2 @ConditionalOnClass({ DataSource.class, E

lodash源码分析之去重--uniq方法

lodash.js包是node开发中常用的js工具包,里面有许多实用的方法,今天分析常用的一个去重方法---uniq 用法 _.uniq([2, 1, 2]) // => [2, 1] 源码包 // uniq.js import baseUniq from './.internal/baseUniq.js' function uniq(array) { return (array != null && array.length) ? baseUniq(array) : [] } ex

String源码分析之Java中的String为什么是不可变的以及replace方法源码分析

什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变. 区分对象和对象的引用 对于Java初学者, 对于String是不可变对象总是存有疑惑.看下面代码: String s = "ABCabc";

【转】【java源码分析】Map中的hash算法分析

全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.ConcurrentHashMap中hash方法的实现以及原因吗?你知道为什么要这么实现吗?你知道为什么JDK 7和JDK 8中hash方法实现的不同以及区别吗?如果你不能很好的回答这些问题,那么你需要好好看看这篇文章.文中涉及到大量代码和计算机底层原理知识.绝对的干货满满.整个互联网,把hash()分析的

【Flume】【源码分析】flume中sink到hdfs,文件系统频繁产生文件,文件滚动配置不起作用? ERROR hdfs.BucketWriter: Hit max consecutive under-replication rotations (30)

[转载] http://blog.csdn.net/simonchi/article/details/43231891 ERROR hdfs.BucketWriter: Hit max consecutive under-replication rotations (30) 本人在测试hdfs的sink,发现sink端的文件滚动配置项起不到任何作用,配置如下: [plain] view plain copy print? a1.sinks.k1.type=hdfs a1.sinks.k1.cha

MapReduce源码分析之Task中关于对应TaskAttempt存储Map方案的一些思考

我们知道,MapReduce有三层调度模型,即Job-->Task-->TaskAttempt,并且: 1.通常一个Job存在多个Task,这些Task总共有Map Task和Redcue Task两种大的类型(为简化描述,Map-Only作业.JobSetup Task等复杂的情况这里不做考虑): 2.每个Task可以尝试运行1-n此,而且通常很多情况下都是1次,只有当开启了推测执行原理且存在拖后腿Task,或者Task之前执行失败时,Task才执行多次. 而TaskImpl中存在一个成员变