lodash源码分析之baseSet

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

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

依赖

import assignValue from './assignValue.js'
import castPath from './castPath.js'
import isIndex from './isIndex.js'
import isObject from '../isObject.js'
import toKey from './toKey.js'

《lodash源码分析之assignValue》 《lodash源码分析之castPath》 《lodash源码分析之isIndex》 《lodash源码分析之isObject》 《lodash源码分析之toKey》

源码分析

baseSet 接入一个 object ,然后将值 value 设置到属性路径 key 上。

源码如下:

function baseSet(object, path, value, customizer) {
  if (!isObject(object)) {
    return object
  }
  path = castPath(path, object)

  const length = path.length
  const lastIndex = length - 1

  let index = -1
  let nested = object

  while (nested != null && ++index < length) {
    const key = toKey(path[index])
    let newValue = value

    if (index != lastIndex) {
      const objValue = nested[key]
      newValue = customizer ? customizer(objValue, key, nested) : undefined
      if (newValue === undefined) {
        newValue = isObject(objValue)
          ? objValue
          : (isIndex(path[index + 1]) ? [] : {})
      }
    }
    assignValue(nested, key, newValue)
    nested = nested[key]
  }
  return object
}

处理 object 不是对象类型的情况

if (!isObject(object)) {
  return object
}

object 参数要通过 isObject 的检测,可以是数组、对象和函数等。

如果通不过检测,不需要再走后续的步骤,因为其他类型没办法往上面设置属性。

路径转换和几个变量

path = castPath(path, object)

const length = path.length
const lastIndex = length - 1

let index = -1
let nested = object

使用 castPathpath 转换成路径数组。

length 保存路径数组 path 的长度,方便后续遍历的终止条件判断。

使用 lastIndex 保存路径数组最后一个路径的索引,这个变量的作用后面再说。

使用 index 来保存当前遍历到的属性的索引值,也作为遍历指示器。

使用 nested 保存当前路径层级的值。

遍历及设置值

假设传入了这样的对象:

const object = {
  a: {
    b: {
      c: 1
    }
  }
}

要将路径 a.b.c 的值设置为 2

在这种情况下,不需要考虑异常情况,使用源码中的以下代码即可实现:

while (++index < length) {
  const key = toKey(path[index])
  let newValue = value

  if (index != lastIndex) {
    const objValue = nested[key]
    newValue = objValue
  }
  assignValue(nested, key, newValue)
  nested = nested[key]
}

在遍历 path 的过程中,判断当前的索引 index 是不是最后的属性,如果不是,则使用 nexted[key] 将当前层级的值取出来。例如在第一层 a 处,取得的值为 b:{c: 1} 。然后使得 assignValue 将值重新设置回去,其实在最后一层属性之前,原对象一点变化都没有,就是将当层的值取出,再设置回去。

但是在最后一层时,newValue 的值为传入的 value ,也就实现了指定路径的值的更新。

处理异常情况

同样是 a.b.c 的路径,但是传入的对象如下:

const object = {}

这时,取第一层 a 时,取得的值为 undefined ,也即当前的 nexted 值为 undefined ,此时使用 assignValue 来赋值到 undefined 上,肯定与结果不符。

这时,就需要对每一层都要进行一个判断,如果当前值不能通过 isObject 检测,也即没办法往上面设置属性,就需要帮它生成一个空的对象或者数组,以便后续的层级能在上面设置属性。

代码修改如下:

while (++index < length) {
  const key = toKey(path[index])
  let newValue = value

  if (index != lastIndex) {
    const objValue = nested[key]
    newValue = isObject(objValue) 
      ? objValue
        : (isIndex(path[index + 1]) ? [] : {})
  }
  assignValue(nested, key, newValue)
  nested = nested[key]
}

使用 isIndex 来判断是不是数组的索引类型,如果是,则使用空数组,否则使用空对象。

customizer 支持

baseSet 作为内部方法,支持传入 customizer 来返回下一个值,不一定是 isObject 这样的判断。

如果有传 customizer ,则 newValue 直接使得 customizer 的值。

if (index != lastIndex) {
  const objValue = nested[key]
  newValue = customizer ? customizer(objValue, key, nested) : undefined
  if (newValue === undefined) {
    newValue = isObject(objValue)
      ? objValue
    : (isIndex(path[index + 1]) ? [] : {})
  }
}

如果 newValueundefined ,则还是走 isObject 的判断。

License

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

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

作者:对角另一面

results matching ""

    No results matching ""