卑鄙是卑鄙者的通行证,高尚是高尚者的墓志铭。javascript
——北岛《回答》html
看北岛就是从这两句诗开始的,高尚者已死,只剩卑鄙者在世间横行。前端
本文为读 lodash 源码的第一篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodashjava
gitbook也会同步仓库的更新,gitbook地址:pocket-lodashgit
你可能会有点奇怪,原生的 slice 方法基本没有兼容性的问题,为何 lodash 还要实现一个 slice 方法呢?github
这个问题,lodash 的做者已经在 why not the 'baseslice' func use Array.slice(), loop faster than slice? 的 issue 中给出了答案:lodash 的 slice 会将数组当成密集数组对待,原生的 slice 会将数组当成稀疏数组对待。数组
咱们先来看看犀牛书是怎样定义稀疏数组的:微信
稀疏数组就是包含从0开始的不连续索引的数组。一般,数组的length属性值表明数组中元素的个数。若是数组是稀疏的,length属性值大于元素的个数。oop
若是数组是稀疏的,那么这个数组中至少有一个以上的位置不存在元素(包括 undefined
)。spa
例如:
var sparse = new Array(10) var dense = new Array(10).fill(undefined)
其中 sparse
的 length
为10,可是 sparse
数组中没有元素,是稀疏数组;而 dense
每一个位置都是有元素的,虽然每一个元素都为undefined
,为密集数组 。
那稀疏数组和密集数组有什么区别呢?在 lodash 中最主要考虑的是二者在迭代器中的表现。
稀疏数组在迭代的时候会跳过不存在的元素。
sparse.forEach(function(item){ console.log(item) }) dense.forEach(function(item){ console.log(item) })
sparse
根本不会调用 console.log
打印任何东西,可是 dense
会打印出10个 undefined
。
固然,除了对待稀疏数组跟原生的 slice 不一致外,其余的规则仍是同样的,下面是 lodash 实现 slice 的源码。
function slice(array, start, end) { let length = array == null ? 0 : array.length if (!length) { return [] } start = start == null ? 0 : start end = end === undefined ? length : end if (start < 0) { start = -start > length ? 0 : (length + start) } end = end > length ? length : end if (end < 0) { end += length } length = start > end ? 0 : ((end - start) >>> 0) start >>>= 0 let index = -1 const result = new Array(length) while (++index < length) { result[index] = array[index + start] } return result }
let length = array == null ? 0 : array.length if (!length) { return [] }
不传参时,length
默认为0,不然获取数组的长度。注意这里用的是 array == null
,非 array === null
,包含了 undefined
的判断。
因此在不传参调用 lodash 的 slice 时,返回的是空数组,而原生的 slice 没有这种调用方式。
start
参数用来指定截取的开始位置。
先来看下 MDN 对该参数的描述:
若是该参数为负数,则表示从原数组中的倒数第几个元素开始提取。
若是省略,则从索引0开始
start = start == null ? 0 : start
所以这段是处理省略的状况,省略时,默认值为0。
if (start < 0) { start = -start > length ? 0 : (length + start) }
这段是处理负数的状况。
若是负数取反后比数组的长度还要大,即超出了数组的范围,则取值为0,表示从开始的位置截取,不然用 length + start
,即向后倒数。
start >>>= 0
最后,用在 >>>
来确保 start
参数为整数或0。
由于 lodash 的 slice 除了能够处理数组外,也能够处理类数组,所以第一个参数 array
可能为一个对象, length
属性不必定为数字。
end
参数用来指定截取的结束位置。
一样来看下 MDN 对些的描述:
若是该参数为负数,则它表示在原数组中的倒数第几个元素结束制取。
若是end被省略,则slice会一直提取到原数组的末尾。
若是end大于数组长度,slice也会一直提取到原数组末尾。
end = end === undefined ? length : end
这段是处理 end
被省略的状况,省略时,end
默认为为 length
,即截取到数组的末尾。
end = end > length ? length : end
这是处理 end
比数组长度大的状况,若是被数组长度大,也会截取到数组的末尾。
if (end < 0) { end += length }
这段是处理负值的状况,若是为负值,则从数组末尾开始向前倒数。
这里没有像 start
同样控制 end
的向前倒数完后是否为负数,由于后面还有一层控制。
length = start > end ? 0 : ((end - start) >>> 0)
新数组的长度计算方式很简单,就是用 edn - start
便可得出。
上面说到,没有控制最终 end
是否为负数的状况。这里用的是 start
和 end
的比较,若是 start
比 end
大,则新数组长度为0,即返回一个空数组。不然用 end - start
来计算。
这里一样用了无符号右移位运算符来确保 length
为正数或0。
let index = -1 const result = new Array(length) while (++index < length) { result[index] = array[index + start] } return result
result
为新数组容器。
用 while
循环,从 start
位置开始,获取原数组的值,依次存入新的数组中。
由于是经过索引取值,若是遇到稀疏数组,对应的索引值上没有元素时,经过数组索引取值返回的是 undefined
, 但这并非说稀疏数组中该位置的值为 undefined
。
最后将 result
返回。
javascript权威指南(第6版), David Flanagan著,淘宝前端团队译,机械工业出版社
why not the 'baseslice' func use Array.slice(), loop faster than slice?
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
做者:对角另外一面