返回

JavaScript 对象展开与 Undefined 属性处理:3 大策略

vue.js

Javascript 对象展开与未定义属性的处理

在 Javascript 中使用对象展开语法 (...) 时,经常需要考虑一种情况,那就是源对象中的某些属性值可能为 undefined。当目标对象(例如,用作模板或占位符的对象)包含嵌套对象结构,且这些嵌套结构不能因为源对象对应属性为 undefined 而被移除或修改时,就需要特殊处理。若不加处理,使用 ... 直接展开源对象可能导致结构丢失。以下介绍处理这种场景的几种有效策略。

问题根源

对象展开 (...) 本身不会做任何额外的逻辑处理,只是简单地将源对象的键值对复制到目标对象。当源对象中的某个属性为 undefined 时,目标对象相应键的值也会被设置为 undefined,这会导致原本存在的对象被覆盖或者移除,从而结构改变。例如,{...{a: 1}, b: undefined}b 也会被设成 undefined,这就是我们不想要的结果。

解决方案

1. 明确类型判断和条件展开

最直接的方法是在展开源对象前,明确地检查需要保留结构的目标对象的各个属性在源对象中是否存在并且不为 undefined。对于嵌套对象,需逐层进行检查。这个方法确保了当源对象中对应属性值为 undefined 时,目标对象的原有结构可以保留。

代码示例:

let object1 = { a : '', b : '', c : {name : 'Mark', title: 'Mr'}};
let object2 = { a : 'AAA', b : 'BBB', c : undefined};


let mergedObject = {
  ...object1,
    a: object2.a !== undefined ? object2.a : object1.a,
    b: object2.b !== undefined ? object2.b : object1.b,
    c: object2.c !== undefined ? object2.c : object1.c,

};
console.log(mergedObject); // 输出 { a: 'AAA', b: 'BBB', c: { name: 'Mark', title: 'Mr' } }

操作步骤:

  • 检查 object2 中的每个属性。
  • 若属性值不为 undefined,则用该值更新目标对象的对应属性,若为 undefined 则保留原object1结构。

这个方法代码略显繁琐,需要手动处理每个可能为 undefined 的属性。

2. 递归合并策略

对于深层嵌套的结构,可采用递归方式来合并对象。此方法能够智能地处理任意层级的嵌套,避免重复的类型判断逻辑。核心思想是对目标对象的属性进行递归检查和赋值,仅当源对象的对应属性存在并且不是 undefined 的时候才进行更新。

代码示例:

function deepMerge(target, source) {
  for (const key in source) {
    if (source.hasOwnProperty(key)) {
        if (source[key] !== undefined) {
        if (
          target[key] != null &&
          typeof target[key] === 'object' &&
          typeof source[key] === 'object' && !Array.isArray(source[key]) //新增类型检测防止数组递归。
          )
           {

                deepMerge(target[key], source[key]);

        }
        else {
            target[key] = source[key];

            }

       }
    }
  }
  return target;
}
let object1 = { a : '', b : '', c : {name : 'Mark', title: 'Mr'}, d: [{value:1}, {value:2}], e :1 };
let object2 = { a : 'AAA', b : 'BBB', c : undefined,d:undefined, e:{test:1}};

let mergedObject = deepMerge(JSON.parse(JSON.stringify(object1)), object2); // 使用 JSON.parse(JSON.stringify()) 实现浅拷贝。
console.log(mergedObject)
// Output:
// {
//   a: 'AAA',
//   b: 'BBB',
//   c: { name: 'Mark', title: 'Mr' },
//  d: [ { value: 1 }, { value: 2 } ],
//  e: { test: 1 }
// }


操作步骤:

  • 创建一个 deepMerge 函数。
  • 循环源对象 source 中的所有属性。
  • 针对每个属性,检查其是否是对象,以及源对象中的值是否是对象并且非undefined,如果两者都满足,递归调用 deepMerge 继续合并。
  • 若不是对象或者为空,或者undefined, 直接将 source 对应属性赋值给目标对象(仅当 source 的值非 undefined)。
  • 使用 JSON.parse(JSON.stringify(object1)) 实现 object1 的浅拷贝, 避免对原对象修改
  • 合并 object1object2 并得到最终合并的对象

这种方法具有很高的灵活性和复用性,适合处理多种嵌套层级的数据结构,同时防止数组类型递归调用时报错。
3. 使用库方法

有一些第三方库例如Lodash提供了强大的 merge 方法,也可以满足此类需求。其默认行为就是会忽略 undefined 属性,同时会进行深度的对象合并,对于复杂对象的操作非常便捷。

代码示例 (使用 lodash):

首先需要安装 Lodash 库:

npm install lodash
const _ = require('lodash');

let object1 = { a : '', b : '', c : {name : 'Mark', title: 'Mr'}};
let object2 = { a : 'AAA', b : 'BBB', c : undefined};

let mergedObject = _.merge({}, object1, object2);
console.log(mergedObject);  // 输出 { a: 'AAA', b: 'BBB', c: { name: 'Mark', title: 'Mr' } }

操作步骤:

  • 使用 npm 安装 lodash 库
  • 引入 lodash 库。
  • 使用 _.merge({}, object1, object2) 方法进行合并,并将结果输出。

第三方库往往在效率和稳定性上优于自写代码。不过引入新依赖也是需要在技术选型中纳入考量的。

安全建议

  • 防御式编程: 在进行对象操作前,始终验证数据结构的预期状态,并处理可能的 nullundefined 值。
  • 不可变性: 当涉及到状态管理的时候尽量避免直接修改传入的目标对象,应该创建拷贝进行处理,避免 side-effect。使用扩展运算或深拷贝方法都可以。
  • 单元测试: 针对这种类型的逻辑编写测试用例非常必要,确保代码在各种边缘情况下的行为都符合预期,测试包括:测试 undefined 值被处理,保留嵌套结构,不同数据类型情况下的处理。

对象展开和对象合并看似简单,但当遇到需要保持结构的嵌套对象和 undefined 属性时,则需注意。在项目实践中可以根据具体的场景和要求来选择合适的方式。明确的条件判断,深度递归合并,或是利用第三方库,这些方式都可以在项目中实现优雅且稳定的逻辑。