JavaScript对象合并的陷阱

3 分钟
57 阅读
JavaScript对象合并的陷阱

为什么改一行代码就解决了时间丢失的Bug?

🏠 生活中的例子

想象你在装修房子,有两种方式设置默认家具:

第一种方式(有问题):

复制代码
1. 先放一张桌子和一把椅子
2. 然后按照设计图纸重新摆放所有家具
3. 如果图纸上写着"桌子:无",你的桌子就没了!

第二种方式(正确):

复制代码
1. 买一套完整的家具(桌子椅子都是配套的)
2. 按照设计图纸调整
3. 即使图纸上写着"桌子:无",至少椅子还在

📝 代码实例

让我们用一个简单的例子来演示:

javascript 复制代码
// 模拟 parseDateFilter 的返回值
function getServerData(hasData) {
  if (hasData) {
    return {
      name: "张三",
      age: 25,
      city: "北京"
    };
  } else {
    return {
      name: "李四", 
      age: null,  // ⚠️ 注意这里是 null
      city: "上海"
    };
  }
}

// 第一种方式(有问题)
const userData1 = {
  name: "默认姓名",
  age: 18,           // 设置默认年龄
  city: "默认城市",
  ...getServerData(false)  // ⚠️ null 会覆盖默认值!
};

console.log("第一种方式:", userData1);
// 输出: { name: "李四", age: null, city: "上海" }
// 😱 年龄丢失了!变成了 null

// 第二种方式(改进版)
const defaultData = { name: "默认姓名", age: 18, city: "默认城市" };
const userData2 = {
  ...defaultData,
  ...getServerData(false)
};

console.log("第二种方式:", userData2);
// 输出: { name: "李四", age: null, city: "上海" }
// 😱 问题依然存在!

// 最佳方式(真正的解决方案)
const serverData = getServerData(false);
const userData3 = {
  name: serverData.name || "默认姓名",
  age: serverData.age || 18,        // 只在服务器数据无效时使用默认值
  city: serverData.city || "默认城市"
};

console.log("最佳方式:", userData3);
// 输出: { name: "李四", age: 18, city: "上海" }
// ✅ 完美!null 被默认值替代了

🔍 你的DateFilter问题分析

第一种方式(有问题):

javascript 复制代码
const initialVals = useMemo(() => {
  const parsed = parseDateFilter(existingDate);
  return {
    value: new Date(),        // 🕐 设置当前时间
    endTimeValue: new Date(), // 🕐 设置当前时间
    ...parsed,               // ⚠️ parsed.value: null 覆盖了上面的时间!
  };
}, [existingDate]);

console.log(9999, parsed) 可能输出:

javascript 复制代码
{
  filterType: "InTheLast",
  value: null,              // 💥 这个null会覆盖你的 new Date()
  relativeAmount: "7",
  daysMonthsValue: "days"
}

第二种方式(有改善):

javascript 复制代码
const initialVals = useMemo(() => {
  const parsed = parseDateFilter(existingDate);
  const defaultDate = new Date();  // ✅ 同一个时间点
  return {
    value: defaultDate,
    endTimeValue: defaultDate,
    ...parsed,  // 虽然还是会覆盖,但至少时间是一致的
  };
}, [existingDate]);

🎯 为什么第二种方式"看起来"解决了问题?

  1. 时间一致性:使用同一个 defaultDate 确保了时间点完全相同
  2. 减少竞态条件:避免了两次 new Date() 调用的微小时间差
  3. 巧合性修复:可能在你的测试场景中,parsed.value 不是 null

⚡ 真正的解决方案

javascript 复制代码
const initialVals = useMemo(() => {
  const parsed = parseDateFilter(existingDate);
  const defaultDate = new Date();
  
  return {
    filterType: parsed.filterType || options[0],
    value: parsed.value || defaultDate,           // ✅ 防御性编程
    endTimeValue: parsed.endTimeValue || defaultDate, // ✅ 防御性编程
    relativeAmount: parsed.relativeAmount,
    daysMonthsValue: parsed.daysMonthsValue || "days",
  };
}, [existingDate]);

🔑 核心教训

  1. JavaScript对象合并的陷阱... 展开运算符会用 null 覆盖有效值
  2. 防御性编程:使用 || 运算符提供备用值
  3. 调试的重要性console.log(parsed) 帮你发现了真正的问题

这就是为什么看似简单的一行代码改动,却解决了一个困扰你的大问题!🎉

评论

评论

发表评论