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)

作者:对角另一面

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏对角另一面

lodash源码分析之compact中的遍历

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

1926
来自专栏C#

数组未必一定需从0开始,谈一下非0开始的数组

  谈到数组时,当被问及数组是从什么数开始时,估计大部分程序员都会直接说出数组当然是从0开始的。这个回答当然没有错,现在我们就来了解一下C#中的下限非...

1755
来自专栏杨建荣的学习笔记

使用SQL来分析数据库参数(二)(r10笔记第82天)

拖延症的我终于接下来第二篇数据库参数的分析。 数据库的参数分析一直以来是调优中的重要一环,而感觉有时候却感觉找不到一些方法,我分析了一下,还是蛮有意思。数据库的...

2628
来自专栏数据小魔方

图表中包含负值的双色填充技巧

今天教大家怎么在Excel里制作带负值的双色填充图表 正负值双色填充 ▼ 通常如果数据中带负值 默认的图表输出虽然能够显示负值 但是负值颜色与正值并没有任何区别...

2136
来自专栏前端杂货铺

URI编码解码和base64

概述 对于uri的编解码,在js中有3对函数,分别是escape/unescape,encodeURI/decodeURI,encodeURIComponent...

2727
来自专栏分布式系统和大数据处理

C#中的委托和事件 - Part.1

文中代码在VS2005下通过,由于VS2003(.Net Framework 1.1)不支持隐式的委托变量,所以如果在一个接受委托类型的位置直接赋予方法名,在V...

693
来自专栏Danny的专栏

设计模式奠基石——UML关系转化为代码

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

813
来自专栏Android知识点总结

00--图解数据结构之开篇+集合基类

1048
来自专栏小樱的经验随笔

UESTC 1599 wtmsb【优先队列+排序】

题目链接:UESTC 1599 wtmsb 题意:给你一组数,每一次取出两个最小的数,将这两个数的和放入这组数中,直到这组数只剩下一个,求最后剩下那个数的大小!...

2576
来自专栏杂七杂八

css布局使用

目录 常用居中 垂直居中 水平居中 垂直水平居中 单列布局 双列&三列布局 ---- 常用居中 垂直居中 单行文本垂直居中 <div class="pare...

2829

扫码关注云+社区