如何以不可变方式扩展嵌套对象?仅重建变更路径对象
实现嵌套对象的不可变深度扩展(仅重建变更节点)
需求背景
需要对多层嵌套的对象进行不可变扩展:添加或修改属性时,不直接修改原对象,且仅重建发生变更的节点,未变更的节点保持原引用,避免全量重建复杂嵌套对象带来的性能开销。
示例说明
基础对象(base)
{ foo: { bar: { baz: 123 }, hello: 'world', something: { deeply: { nested: true } } } }
扩展对象(updates)
{ foo: { hello: 'earth', something: { deeply: { iAmNew: 32134 } } } }
预期结果
{ foo: { bar: { baz: 123 }, hello: 'earth', something: { deeply: { nested: true, iAmNew: 32134 } } } }
注:{baz:123}节点未发生变更,因此不需要重建,保持原对象引用。
现有不完整实现
export function extend( start, updates, ) { const end = { ...start } let node = end for (const i in updates) { const val = updates[i] if (val != null) { if (typeof val === 'object') { } } } return end }
完整实现方案
通过递归处理嵌套对象,仅在需要变更的层级创建新对象,未变更的层级直接复用原引用:
export function extend(start, updates) { // 若扩展对象无效,直接返回原对象 if (!updates || typeof updates !== 'object') { return start; } // 创建当前层级的新对象,复制原对象所有属性 const end = { ...start }; for (const key in updates) { const updateVal = updates[key]; if (updateVal == null) { // 处理 null/undefined 类型的更新值 end[key] = updateVal; continue; } const startVal = start[key]; // 仅当两边都是纯对象(非数组)时,递归处理嵌套层级 if (typeof updateVal === 'object' && typeof startVal === 'object' && !Array.isArray(updateVal) && !Array.isArray(startVal)) { // 递归扩展嵌套对象,仅重建有变更的子节点 end[key] = extend(startVal, updateVal); } else { // 非对象类型直接赋值(覆盖或新增属性) end[key] = updateVal; } } return end; }
逻辑说明
- 边界处理:如果传入的扩展对象无效(非对象或为空),直接返回原对象,避免不必要的操作。
- 当前层级复用与重建:创建当前层级的新对象,复制原对象的所有属性,但只有当子节点发生变更时,才会对该子节点进行重建。
- 递归处理嵌套:当原对象和扩展对象的对应属性都是纯对象时,递归调用
extend函数处理嵌套层级,确保仅重建有变更的子节点。 - 直接赋值场景:对于非对象类型(如字符串、数字)或数组,直接赋值覆盖或新增属性。
验证示例
const base = { foo: { bar: { baz: 123 }, hello: 'world', something: { deeply: { nested: true } } } }; const updates = { foo: { hello: 'earth', something: { deeply: { iAmNew: 32134 } } } }; const result = extend(base, updates); // 验证未变更节点的引用一致性 console.log(result.foo.bar === base.foo.bar); // 输出 true,说明{baz:123}未被重建 console.log(result.foo.something.deeply.nested); // 输出 true,保留原属性 console.log(result.foo.something.deeply.iAmNew); // 输出 32134,新增属性 console.log(result.foo.hello); // 输出 'earth',覆盖属性
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

