Lodash 源码阅读-assignValue
功能概述
assignValue
是 Lodash 中的一个内部工具函数,主要负责智能地将值赋给对象的属性。它与直接赋值不同的是,它会先检查当前对象是否已经有相同的值,如果有则跳过赋值操作,只有在值不同或属性不存在时才执行赋值。这种优化可以避免不必要的属性操作,提高性能并防止触发不必要的副作用。
这个函数在 Lodash 的很多核心功能中被使用,比如 _.assign
、_.merge
等对象操作方法。
前置学习
依赖函数
- baseAssignValue:底层的属性赋值实现,不进行相等性检查
- hasOwnProperty:检查对象自身是否含有指定属性
- eq:使用 SameValueZero 算法比较两个值是否相等
技术知识
- JavaScript 对象属性操作:包括属性访问、赋值、检查等
- 相等性比较:JavaScript 中的松散相等(==)、严格相等(===)和 SameValueZero 比较
- 属性描述符:了解属性的特性(可写、可枚举等)
源码实现
javascript
function assignValue(object, key, value) {
var objValue = object[key];
if (
!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
(value === undefined && !(key in object))
) {
baseAssignValue(object, key, value);
}
}
实现思路
assignValue
的实现思路可归纳为:
- 首先获取对象现有的属性值
objValue = object[key]
- 然后检查是否需要进行赋值,满足以下任一条件时才执行赋值操作:
- 对象没有该属性(
!hasOwnProperty.call(object, key)
),或者 - 对象有该属性但值不相等(
!eq(objValue, value)
),或者 - 要赋的值是
undefined
且对象上不存在该属性(value === undefined && !(key in object)
)
- 对象没有该属性(
- 如果需要赋值,则调用
baseAssignValue
函数执行实际的赋值操作
这种实现可以避免不必要的属性重新赋值,特别是在处理大量属性或复杂对象时能提高性能。
源码解析
让我们逐行分析 assignValue
函数的实现:
javascript
function assignValue(object, key, value) {
函数定义,接收三个参数:
object
:要修改的目标对象key
:要设置的属性名value
:要赋的值
javascript
var objValue = object[key];
获取对象上已有的属性值。这一步很重要,因为后续需要比较这个值和要赋的新值是否相等。
javascript
if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
(value === undefined && !(key in object))) {
这个条件判断是 assignValue
的核心,由两部分组成:
-
!(hasOwnProperty.call(object, key) && eq(objValue, value))
:hasOwnProperty.call(object, key)
:检查对象自身是否有该属性(不包括原型链上的属性)eq(objValue, value)
:使用eq
函数比较属性的当前值与要赋的值是否相等- 整体条件的含义是:如果对象没有该属性,或者对象有该属性但值与要赋的值不同,则需要赋值
-
(value === undefined && !(key in object))
:- 这是一个特殊情况的处理:如果要赋的值是
undefined
,且对象上不存在该属性(包括自身和原型链),才进行赋值 - 处理这个特殊情况是因为,对于不存在的属性,访问它会返回
undefined
,但这不意味着该属性设置了undefined
值
- 这是一个特殊情况的处理:如果要赋的值是
这个条件设计得很巧妙,既避免了不必要的属性赋值,又处理了特殊情况。
javascript
baseAssignValue(object, key, value);
}
}
如果满足条件,则调用 baseAssignValue
执行实际的赋值操作。baseAssignValue
负责处理最终的属性赋值,包括对 __proto__
属性的特殊处理。
与 baseAssignValue 的区别
assignValue
和 baseAssignValue
的主要区别在于:
- 相等性检查 :
assignValue
会先检查对象是否已有相同值的属性,只有在需要时才赋值;而baseAssignValue
直接执行赋值操作,不进行任何检查 - 调用关系 :
assignValue
是一个高级包装函数,而baseAssignValue
是底层实现 - 性能优化 :
assignValue
的检查逻辑有助于避免不必要的属性重新定义,特别是当处理可能有副作用的属性时(如使用 getter/setter 定义的属性)
性能考量
assignValue
的实现包含了几个重要的性能优化:
- 避免不必要的赋值操作:通过先检查值是否已经相等,可以避免重复赋值相同的值
- 减少副作用:在使用 getter/setter 定义的属性上,避免不必要的 setter 调用很重要
- 高效的相等性比较 :使用
eq
函数实现高效的相等性比较,包括对 NaN 的正确处理
这些优化在处理大型对象或频繁的属性更新操作时尤为重要。
总结
assignValue
是 Lodash 中一个内部工具函数,它通过智能地判断是否需要进行属性赋值,提高了性能并减少了不必要的操作。这个函数展示了几个重要的编程思想:
- 效率优先:只在必要时执行操作,避免重复工作
- 边界情况处理 :正确处理
undefined
值和对象属性不存在的情况 - 分层设计 :高级函数
assignValue
负责判断逻辑,底层函数baseAssignValue
负责实际执行 - 正确的相等性比较:使用符合规范的 SameValueZero 比较算法
虽然 assignValue
本身很简洁,但它体现了优秀的函数设计原则:单一职责、关注性能、处理边界情况。