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
使用 castPath 将 path 转换成路径数组。
用 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]) ? [] : {})
}
}
如果 newValue 为 undefined ,则还是走 isObject 的判断。
License
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
作者:对角另一面