小时候,javascript
乡愁是一枚小小的邮票,java
我在这头,git
母亲在那头。es6
长大后,乡愁是一张窄窄的船票,github
我在这头,数组
新娘在那头。安全
后来啊,微信
乡愁是一方矮矮的坟墓,app
我在外头,ide
母亲在里头。
而如今,
乡愁是一湾浅浅的海峡,
我在这头,
大陆在那头。
——余光中《乡愁》
本文为读 lodash 源码的第三篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash
gitbook也会同步仓库的更新,gitbook地址:pocket-lodash
compact
函数用来去除数组中的假值,并返回由不为假值元素组成的新数组。
false
、null
、0
、 ""
、undefined
和 NaN
都为假值。
例如:
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
循环,很容易就将 compact
中关于循环部分的源码改写成如下形式:
for (let i = 0; i < array.length; i++) { const value = array[i] if (value) { result[resIndex++] = value } }
这样写,确定是没有问题的,可是不够简洁。
再来看 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
有两个致命的特性:
for...in
的遍历不能保证顺序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,foo
(for...in
不能保证顺序)。由于 foo
也是可枚举属性,在 for..in
会被遍历出来。
最后来看看 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
来遍历数组是安全的,由于这个方法是数组的原生方法。
关于 Iterator
和 Generator
能够点击参考中的连接详细查看。
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
做者:对角另外一面