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

——北岛《回答》

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

本文为读 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)

其中 sparselength 为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 是否为负数的情况。这里用的是 startend 的比较,如果 startend 大,则新数组长度为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. 简单的CSS颜色查看工具

    可以通过输入ARGB(A代表透明度)格式或者HEX格式查看颜色,也可以进行ARGB格式和者HEX格式转换,如下图 使用C#编写,我已将源代码压缩上传 下载地址:http://files.cnblogs ...

  2. Python实现采集wordpress整站数据的爬虫

    最近爱上了python,就非常喜欢使用python来练手,在上次的基础上完善一下代码,实现采集wordpress程序的网站的整站数据的爬虫程序,本站也是采用的wordpress,我就拿吾八哥网(htt ...

  3. 对ajax请求的简单封装,操作更方便

    我这里的接口数据调用的js叫interface.js,接口路径管理的js叫webSiteControl.js /** * Created by l2776 on 2017/7/11. * 接口数据调用 ...

  4. 基于zookeeper的Swarm集群搭建

    简介 Swarm:docker原生的集群管理工具,将一组docker主机作为一个虚拟的docker主机来管理. 对客户端而言,Swarm集群就像是另一台普通的docker主机. Swarm集群中的每台 ...

  5. RabbitMQ 笔记-工作队列

    工作队列的主要思想是不用等待资源密集型的任务处理完成, 为了确保消息或者任务不会丢失,rabbitmq 支持消息确信 ACK.ACK机制是消费者端从rabbitmq收到消息并处理完成后,反馈给rabb ...

  6. 主要讲下hack的兼容用法,比较浅,哈哈

    hack是主要来处理IE的兼容,不同的IE,不同的兼容方式 /*   属性前缀法(即类内部Hack):       *color:#000; *号对IE6,IE7都生效   +color:#555; ...

  7. JAVA基础知识总结:八

    面向对象语言的三大特性;封装.继承.多态 一.面向对象语言特性之封装 1.什么是封装? 一个类中某些属性,如果不希望外界直接访问,我们可以将这个属性作为私有的,可以给外界暴露出来一个访问的方法 使用封 ...

  8. PhiloGL学习(4)——三维对象、加载皮肤

    前言 上一篇文章中介绍了如何响应鼠标和键盘事件,本文介绍如何加载三维对象并实现给三维对象添加一个漂亮的皮肤. 一. 原理分析 我对三维的理解为:所谓三维对象无非是多个二维对象拼接到一起,贴图就更简单了 ...

  9. 浅析C#中单点登录的原理和使用

    什么是单点登录?我想肯定有一部分人"望文生义"的认为一个用户只能在一处登录.其实这是错误的理解(我记得我第一次也是这么理解的).单点登录指的是多个子系统只需要登录一个,其他系统不需 ...

  10. python爬虫xpath

    又是一个大晴天,因为马上要召开十九大,北京地铁就额外的拥挤,人贴人到爆炸,还好我常年挤地铁早已练成了轻功水上漂,挤地铁早已经不在话下. 励志成为一名高级测试工程师的我,目前还只是个菜鸟,难得有机会,公 ...