在 JavaScript 开发中,对象合并是一个高频需求。jQuery 提供的 extend() 方法专门用于将两个或多个对象的内容合并到第一个目标对象中,并返回合并后的对象。它本质上是在帮你处理对象的浅拷贝和深拷贝问题,比手写循环合并要简洁且可靠得多。
extend() 是直接挂载在 jQuery 全局对象上的静态方法,调用形式为 $.extend(),而不是实例方法。这一点在使用时容易和插件开发中的 $.fn.extend() 混淆,后者是用来扩展 jQuery 原型链的,两者用途不同。
为什么我倾向于用 $.extend() 而不是 Object.assign()?
在现在 JavaScript 中,Object.assign() 已经可以完成基本的对象合并,但 extend() 有一个 Object.assign() 不具备的关键能力:递归合并。当你的对象包含嵌套的子对象时,Object.assign() 会把内层对象整体替换掉,而不是逐一合并属性。extend() 在传入 true 作为第一个参数后,会深度遍历每个属性,最终得到真正合并后的嵌套结构。这在处理配置文件、组件选项等场景中非常实用。
语法
jQuery.extend([deep], target, object1 [, objectN])
参数说明:
-
deep(可选):布尔值。设为true时启用深度递归合并,子对象不会直接被覆盖,而是逐层合并属性。 -
target:目标对象,所有后续对象的属性都会被合并到这里。这个方直接修改target。 -
object1:第一个合并源对象,其属性将被添加到target中。 -
objectN(可选):更多需要合并到target的源对象。
参数数量的特殊行为:
-
只传一个参数:该参数被视为合并源,
jQuery对象自身成为目标,常用于为 jQuery 命名空间扩展功能。 -
只传两个参数且无
deep:第一个参数是目标对象,第二个是源对象。 -
传入
null或undefined的源对象参数会被自动忽略,不会引发错误。
课程知识要点
-
浅合并是默认行为:不传
deep或deep为false时,遇到同名属性会直接用新值覆盖旧值,即使值是一个对象也会整体替换。 -
深度合并需显式设置 deep: true:启用后,嵌套对象会递归合并,而不是被直接覆盖,适合处理多层结构的配置对象。
-
直接修改目标对象:
extend()会原地修改第一个对象参数,并返回它。如果你不想改动原始对象,可以把{}作为第一个参数传入。 -
忽略 null 和 undefined:源对象中的
null或undefined值也会覆盖目标属性。但如果合并源本身是null或undefined,则会被整个跳过。 -
数组不会被递归合并:即使开启深度合并,数组也是直接覆盖,不会把两个数组的元素进行合并。
示例一:默认浅合并
这个例子展示了两个对象在默认模式(非递归)下的合并效果。注意同名属性 Hindi 被整体替换,源对象中缺失的 Pages 属性不会从目标对象保留。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>extend() 浅合并示例 · 编程学习</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<h4>extend() 方法——默认浅合并</h4>
<p><b>对象 obj1 初始值:</b></p>
<p id="origin1"></p>
<p><b>对象 obj2:</b></p>
<p id="origin2"></p>
<p><b>obj2 合并到 obj1 后的结果:</b></p>
<p id="result"></p>
<script>
var obj1 = {
frontend: { price: 150 },
backend: { price: 280 },
database: { price: 300, pages: 275 }
};
var obj2 = {
devops: { price: 200 },
database: { price: 250, pages: 200 }
};
$("#origin1").text(JSON.stringify(obj1));
$("#origin2").text(JSON.stringify(obj2));
// 默认浅合并,obj1 被直接修改
$.extend(obj1, obj2);
$("#result").text(JSON.stringify(obj1));
</script>
</body>
</html>
合并后你会发现,database 属性的值变成了 obj2 中的版本。原本 obj1.database 里 price: 300 和 pages: 275 都被源对象的对应值覆盖了。这就是浅合并的典型特征——对象类型的属性直接替换。
示例二:深度递归合并
启用深度合并后,嵌套对象的属性会逐层融合,而不是整体替换。下面的代码与示例一结构相似,但合并行为截然不同。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>extend() 深度合并示例 · 编程学习</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<h4>extend() 方法——深度合并(deep: true)</h4>
<p><b>对象 obj1 初始值:</b></p>
<p id="origin1"></p>
<p><b>对象 obj2:</b></p>
<p id="origin2"></p>
<p><b>深度合并后的结果:</b></p>
<p id="result"></p>
<script>
var obj1 = {
frontend: { price: 150 },
backend: { price: 280 },
database: { price: 300, pages: 275 }
};
var obj2 = {
devops: { price: 200 },
database: { pages: 180 },
backend: { pages: 350 }
};
$("#origin1").text(JSON.stringify(obj1));
$("#origin2").text(JSON.stringify(obj2));
// 深度合并,第一个参数为 true
$.extend(true, obj1, obj2);
$("#result").text(JSON.stringify(obj1));
</script>
</body>
</html>
这次的结果里,database 不再是整体替换,而是保留 obj1.database.price 不变,同时将 obj2.database.pages 合并进去。backend 也类似,price: 280 保留,pages: 350 作为新属性加入。这种深度合并的方式在合并用户配置与默认配置时特别有用。
个人经验与实用建议
在项目中,我几乎总是在合并配置对象时开启深度合并。一个典型场景是:组件内部有一套默认配置,用户在调用时传入自定义配置。如果不深度合并,用户传入的嵌套对象会直接替换整个默认子对象,导致其他默认值丢失,进而引发难以排查的运行时错误。
另外有一个容易忽略的细节:extend() 会直接修改第一个对象参数。如果你的目标是生成一个新对象而不改动原始对象,记得把空对象 {} 作为第一个参数传进去,像这样:
var merged = $.extend(true, {}, obj1, obj2);
这样 obj1 和 obj2 都不会被改动,结果保存在 merged 里。这个习惯在函数式编程风格或需要保留原始数据的场景下很重要,值得从一开始就养成。