卑鄙是卑鄙者的通行证,高尚是高尚者的墓志铭。

——北岛《回答》

看北岛就是从这两句诗开始的,高尚者已死,只剩卑鄙者在世间横行。

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

gitbook也会同步仓库的更新,gitbook地址:pocket-lodash

引言

你可能会有点奇怪,原生的 slice 方法基本没有兼容性的问题,为什么 lodash 还要实现一个 slice 方法呢?

这个问题,lodash 的作者已经在 why not the 'baseslice' func use Array.slice(), loop faster than slice? 的 issue 中给出了答案:lodash 的 slice 会将数组当成密集数组对待,原生的 slice 会将数组当成稀疏数组对待。

密集数组VS稀疏数组

我们先来看看犀牛书是怎样定义稀疏数组的:

稀疏数组就是包含从0开始的不连续索引的数组。通常,数组的length属性值代表数组中元素的个数。如果数组是稀疏的,length属性值大于元素的个数。

如果数组是稀疏的,那么这个数组中至少有一个以上的位置不存在元素(包括 undefined )。

例如:

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参数

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参数

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 返回。

参考

  1. javascript权威指南(第6版), David Flanagan著,淘宝前端团队译,机械工业出版社
  2. why not the 'baseslice' func use Array.slice(), loop faster than slice?
  3. Array.prototype.slice()
  4. JavaScript: sparse arrays vs. dense arrays
  5. [译]JavaScript中的稀疏数组与密集数组

License

署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)

作者:对角另一面

读lodash源码之从slice看稀疏数组与密集数组的更多相关文章

  1. lodash源码分析之chunk的尺与刀

    以不正义开始的事情,必须用罪恶使它巩固. --莎士比亚<麦克白> 最近很多事似乎印证了这句话,一句谎言最后要用一百句谎言来圆谎. 本文为读 lodash 源码的第二篇,后续文章会更新到这个 ...

  2. lodash源码分析之compact中的遍历

    小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...

  3. lodash源码分析之NaN不是NaN

    暗恋之纯粹,在于不求结果,完全把自己锁闭在一个单向的关系里面. --梁文道<暗恋到偷窥> 本文为读 lodash 源码的第五篇,后续文章会更新到这个仓库中,欢迎 star:pocket-l ...

  4. lodash源码分析之List缓存

    昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方 顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免/因为风的缘故 --洛夫<因为风的缘故& ...

  5. lodash源码分析之baseFindIndex中的运算符优先级

    我悟出权力本来就是不讲理的--蟑螂就是海米:也悟出要造反,内心必须强大到足以承受任何后果才行. --北岛<城门开> 本文为读 lodash 源码的第十篇,后续文章会更新到这个仓库中,欢迎 ...

  6. lodash源码分析之数组的差集

    外部世界那些破旧与贫困的样子,可以使我内心世界得到平衡. --卡尔维诺<烟云> 本文为读 lodash 源码的第十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodas ...

  7. lodash源码分析之获取数据类型

    所有的悲伤,总会留下一丝欢乐的线索,所有的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找希望的缺口,却在惊醒时,瞥见绝美的阳光! --几米 本文为读 lodash 源码的第十八篇,后续文章会更新到 ...

  8. lodash源码分析之Hash缓存

    在那小小的梦的暖阁,我为你收藏起整个季节的烟雨. --洛夫<灵河> 本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbo ...

  9. lodash源码分析之自减的两种形式

    这个世界需要一个特定的恶人,可以供人们指名道姓,千夫所指:"全都怪你". --村上春树<当我谈跑步时我谈些什么> 本文为读 lodash 源码的第六篇,后续文章会更新到 ...

随机推荐

  1. zookeeper curator使用caches实现各种监听

    1.篇首语 curator是zookeeper的一个高级api开发包.封装了zookeeper众多的recipes,并且实现了一些新的recipes原语,最重要的是基于zookeeper提供的各种机制 ...

  2. iOS自动化环境搭建——macaca

    macaca-java for ios 自动化环境搭建 基础原理解析:https://testerhome.com/topics/6608 一.环境搭建 1.安装eclipse; -----Java开 ...

  3. Mysql数据库导出数据字典文档Word或者HTML的3个工具

    最近需要将Mysql的数据库导出一份Word的文档出来,本文记录调研后几个可用的工具和方法: 阿里云DMS工具导出 适用于存储在阿里云RDS服务中的Mysql数据库 导出格式支持:Word.Excel ...

  4. C++每次读取一行字符串输入(学习笔记) (转)

    1. 面向行的输入:getline()getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾. cin.getline(Arr, 20) // Arr为用来输入行的数组的名称:   ...

  5. 系统讲解CSS,前端开发最神奇的技术,新手的你一定不能错过

    前面小编带领大家重温了前端开发中最基本的HTML语言.如果你已经掌握了这门语言,那么恭喜你,可以去深入了解CSS技术了.CSS技术最主要的功能就是弥补HTML标记对在页面中显示外观的不足,对这些标记对 ...

  6. 在WebBrowser控件使用js调用C#方法

    有时我们需要在WebBrowser控件中嵌入了网页,然后通过html页面调用后台方法,如何实现呢?其实很简单,主要有三步: 在被调用方法所属的类上加上[ComVisible(true)]标签,意思就是 ...

  7. MySQL数据库主从复制实践

        MySQL 主从(MySQL Replication),主要用于 MySQL 的实时备份.高可用HA.读写分离.在配置主从复制之前需要先准备 2 台 MySQL 服务器. 一.MySQL主从原 ...

  8. jQuery实现用户输入自动完成功能

    jQuery实现用户输入自动完成功能 利用jQuery UI中Auto-complete插件实现输入自动完成功能,大家在使用诸如淘宝.京东等电商平台搜索商品时,往往只要输入商品的一些特殊字符,就可以显 ...

  9. Promise对象解读

    首先强调的是"Promise"是对象,也就是说与其他JavaScript对象的用法,没有什么两样:其次,它起到代理作用(proxy),充当异步操作与回调函数之间的中介.它使得异步操 ...

  10. 小技巧:Oracle:sqlplus 显示行列字符数

    遇到这种情况可以判断:行显示字符数不够,可以增加行显示字符数 01.可以当前会话HR@ACE >set line 400; 02.上面的方法其它会话不生效,懒不想每次设置怎么办? Oracle: ...