读lodash源码之从slice看稀疏数组与密集数组
卑鄙是卑鄙者的通行证,高尚是高尚者的墓志铭。
——北岛《回答》
看北岛就是从这两句诗开始的,高尚者已死,只剩卑鄙者在世间横行。
本文为读 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
返回。
参考
- javascript权威指南(第6版), David Flanagan著,淘宝前端团队译,机械工业出版社
- why not the 'baseslice' func use Array.slice(), loop faster than slice?
- Array.prototype.slice()
- JavaScript: sparse arrays vs. dense arrays
- [译]JavaScript中的稀疏数组与密集数组
License
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
作者:对角另一面
读lodash源码之从slice看稀疏数组与密集数组的更多相关文章
- lodash源码分析之chunk的尺与刀
以不正义开始的事情,必须用罪恶使它巩固. --莎士比亚<麦克白> 最近很多事似乎印证了这句话,一句谎言最后要用一百句谎言来圆谎. 本文为读 lodash 源码的第二篇,后续文章会更新到这个 ...
- lodash源码分析之compact中的遍历
小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...
- lodash源码分析之NaN不是NaN
暗恋之纯粹,在于不求结果,完全把自己锁闭在一个单向的关系里面. --梁文道<暗恋到偷窥> 本文为读 lodash 源码的第五篇,后续文章会更新到这个仓库中,欢迎 star:pocket-l ...
- lodash源码分析之List缓存
昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方 顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免/因为风的缘故 --洛夫<因为风的缘故& ...
- lodash源码分析之baseFindIndex中的运算符优先级
我悟出权力本来就是不讲理的--蟑螂就是海米:也悟出要造反,内心必须强大到足以承受任何后果才行. --北岛<城门开> 本文为读 lodash 源码的第十篇,后续文章会更新到这个仓库中,欢迎 ...
- lodash源码分析之数组的差集
外部世界那些破旧与贫困的样子,可以使我内心世界得到平衡. --卡尔维诺<烟云> 本文为读 lodash 源码的第十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodas ...
- lodash源码分析之获取数据类型
所有的悲伤,总会留下一丝欢乐的线索,所有的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找希望的缺口,却在惊醒时,瞥见绝美的阳光! --几米 本文为读 lodash 源码的第十八篇,后续文章会更新到 ...
- lodash源码分析之Hash缓存
在那小小的梦的暖阁,我为你收藏起整个季节的烟雨. --洛夫<灵河> 本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbo ...
- lodash源码分析之自减的两种形式
这个世界需要一个特定的恶人,可以供人们指名道姓,千夫所指:"全都怪你". --村上春树<当我谈跑步时我谈些什么> 本文为读 lodash 源码的第六篇,后续文章会更新到 ...
随机推荐
- 移动端分享到微信和QQ
关于在H5页面实现分享到微信和QQ,当初做的时候由于没有做过这方面的功能,也查了很多资料,找了很多插件,试了很多方法,大部分的都是点击后出现一个二维码,这不 符合我的需求,所以在网上找了一个 nati ...
- mySQL、mariaDB、noSQL、SQL server、redis之间是什么关系?
1.首先,从数据库类型上分类,mySQL.mariaDB.SQL server这3种属于关系型数据库. noSQL属于非关系型数据库,被视为数据库革命者. redis成为内存缓存数据库,而前面的两种类 ...
- 【NOIP2012】提高组初赛试题(个人错题解析+相关知识点扩展)C++版
初赛14号就要开始了,从今天到14号还有三天,已经请了两节的自习来刷题,每天三四套题,尽量把01年到16年的题目全刷一遍.[fighting!!!] 4.无论是TCP/IP模型还是OSI模型,都可以视 ...
- JavaScript--我发现,原来你是这样的JS(四)(看看变量,作用域,垃圾回收机制是啥)
一.介绍 这是红宝书(JavaScript高级程序设计 3版)的读书笔记第四篇,是红宝书第四章内容(主要是变量和作用域问题),当然其中还有我个人的理解.红宝书这本书可以说是难啃的,要看完不容易,挺厚的 ...
- PhoneWindow,ViewRoot,Activity之间的大致关系
http://www.nowamagic.net/academy/detail/50160216 在android里,我们都知道activity.但是一个activity跟一个Window是一个什么关 ...
- win10 输入法禁用IME
发现了win10 没法输入,因为禁用IME 右击开始 计算机管理 任务计划程序 打开到Microsoft/Windows/TextServicesFramework 选择操作运行 选择如果任务失 ...
- eclipse环境下,java操作MySQL的简单演示
首先先通过power shell 进入MySQL 查看现在数据库的状态(博主是win10系统) 右键开始,选择Windows powershell ,输入MySQL -u用户名 -p密码 选择数据库( ...
- Java 递归调用 recursive 给一个参数 返回一大堆
需求: 需要组装成对象多层嵌套式的 json字符串; 想到使用 递归来完成这个多层嵌套: 憋了四个小时,终于写出来了; 先看效果: 数据库中的数据: 拼装后的效果: [ EmpVO{ ename='孙 ...
- svn的简介
Svn(Subversion)是近年来崛起的版本管理工具,在当前的开源项目里(J2EE),几乎95%以上的项目都用到了SVN.Subversion项目的初衷是为了替换当年开源社区最为流行的版本控制软件 ...
- C#设计模式之十组合模式(Composite)【结构型】
一.引言 今天我们要讲[结构型]设计模式的第四个模式,该模式是[组合模式],英文名称是:Composite Pattern.当我们谈到这个模式的时候,有一个物件和这个模式很像,也符合这个模式要表达 ...