Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lodash源码分析之basePullAt #77

Open
HeftyKoo opened this issue Feb 15, 2020 · 0 comments
Open

lodash源码分析之basePullAt #77

HeftyKoo opened this issue Feb 15, 2020 · 0 comments
Labels
internal 内部函数或方法 系列文章

Comments

@HeftyKoo
Copy link
Owner

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

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

依赖

import baseUnset from './baseUnset.js'
import isIndex from './isIndex.js'

《lodash源码分析之baseUnset》

《lodash源码分析之isIndex》

源码分析

basePullAt 是用来实现 pullAt 的内部方法,它的作用是将一个数组 array 指定的所有索引 indexes 的值从数组中删除,并且返回修改后的数组。

如果看 lodash 文档,可以知道,pullAt 在修改数组 array 的同时返回的是对应索引所删除的值,这一点是和 basePullAt 所不同的地方。

还有 basePullAt 要求索引数组 indexes 是按照升序排列好的,但是 pullAt 是没有这种要求的,下面读源码的时候会看到为什么会有这种要求。

这也是 lodash 源码好读的原因,它的 api 所完成的功能并不会只在一个函数内就全部完成,而是会拆分成不同的功能函数,每个功能函数只负责很小部分的功能,精简而且好复用。

源码如下:

function basePullAt(array, indexes) {
  let length = array ? indexes.length : 0
  const lastIndex = length - 1

  let previous
  while (length--) {
    const index = indexes[length]
    if (length === lastIndex || index !== previous) {
      previous = index
      if (isIndex(index)) {
        array.splice(index, 1)
      } else {
        baseUnset(array, index)
      }
    }
  }
  return array
}

首先判断 array 如果为真值,则取 indexes 的长度,后面遍历 indexes

然后将最后的索引 lastIndex 存起来,后面会用到。

indexes为什么要按升序排列

因为要删除指定的索引,如果不考虑特殊情况,其实核心代码如下:

while (length--) {
  const index = indexes[length]
  array.splice(index, 1)  
}

两行代码就搞掂。

这时,就很清楚为什么传入的 indexes 一定要按升序来排列了,因为遍历的时候,indexes 是从后往前取的,也就是说,先将大的索引取出来,再做删除的操作,在做删除的操作时,是会改动到原数组的,因为按照升序排列了,因此即使原数组改动了,也不会影响到下一个索引的位置。

处理index有重复的情况

在传入 indexes 的时候,有可能会有传重复的情况,如传入了 [1, 2,2,3] 的情况,如果直接 splice ,在删除完第一个索引是 2 的值时,原数组会发生变动,原来索引 2 后面的值会递补到这个位置,再在下一次循环时,会将这个递补过来的值删除,这不是想要的结果。

要避免这种情况的发生,因为 indexes 是排序好了的,只需要将前一次删除的索引保存起来,如果下次循环进来,前一次的索引和当前的索引相等,即表示已经进行过删除,那直接跳过这次循环即可。

因此代码改成这样:

let previous
while (length--) {
    const index = indexes[length]
    if (index !== previous) {
      previous = index
      array.splice(index, 1)
    }
  }

要注意,previous 应该定义在 while 的外面,lodash 的源码是错误的,它是定义在 while 的里面,因为 letblock scope ,这样每次循环时,都会重新被赋值为 undefined,达不到保存前一次 index 的目的。

处理非正常索引的情况

js 中,数组其实也是对象,也是可以添加属性的,如果 indexes 包含属性,那就需要调用 baseUnset 将对应的属性删除了。因此在调用 splice 时,需要判断一下,当前的值究竟是索引还是属性。

代码改成如下:

let previous
  while (length--) {
    const index = indexes[length]
    if (index !== previous) {
      previous = index
      if (isIndex(index)) {
        array.splice(index, 1)
      } else {
        baseUnset(array, index)
      }
    }
  }

看到这里,你会发现,lastIndex 已经定义了,但是从来没有用过。那这个有什么用呢?

这个是用来处理一个非常特殊的情况的,假设传入的 indexes 如下:[1,2,3, undefined],它最后的一个值为undefined,而数组中刚好有一个键为 undeinfed 的属性,此时应该要将它删除掉的。

但是,此时 previous 刚定义,还没有赋值,因此取出来的 indexprevious 都为 undefined,并不会执行删除操作,因此还需要加上一个判断条件 length === lastIndex 的时候,即第一次循环时,会执行删除操作。

最终的代码就如源码所示了。

License

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

最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

作者:对角另一面

@HeftyKoo HeftyKoo added 系列文章 internal 内部函数或方法 labels Feb 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
internal 内部函数或方法 系列文章
Projects
None yet
Development

No branches or pull requests

1 participant