JavaScript对象拷贝与Object.assign用法实例分析
本文详细解析JavaScript中的对象拷贝机制,区分基本类型与引用类型的复制差异。重点讲解Object.assign方法的浅拷贝特性、属性覆盖规则、继承属性处理及原始类型包装行为,并对比JSON序列化等深拷贝方案,帮助开发者掌握对象合并与复制的最佳实践。
JavaScript对象拷贝与Object.assign用法实例分析
本文通过具体实例解析JavaScript对象拷贝机制及Object.assign方法的使用,供开发者参考。
深拷贝与浅拷贝的概念区分
深拷贝与浅拷贝的概念区分
对于JavaScript中的基本数据类型(包括undefined、null、boolean、number、string),变量直接存储值本身,复制操作即值的复制,不涉及深浅拷贝的概念。然而,C系语言(如C/C++、Java等)在存储引用类型(对象)时,变量中保存的是内存地址。JavaScript作为兼容C系语言特性的语言,其复杂数据类型(object)也存在浅拷贝与深拷贝的区别:浅拷贝指多个变量指向同一内存地址,即共享同一对象;深拷贝则重新分配内存空间,生成与原对象完全独立的新对象,两者互不影响。
注:部分资料将浅拷贝定义为仅复制对象第一层属性而不递归处理子对象的情况,本文将其视为深浅拷贝之间的中间状态。本文以“是否分配新内存”为标准划分深浅拷贝,以保持与C/C++、C#、Java等语言概念的一致性。
JavaScript中浅拷贝的实现方式
JavaScript中浅拷贝的实现方式
在JavaScript中实现浅拷贝十分简单,示例如下:
let objA = {
name: '对象A',
content: '我是A'
};
let copyA = objA;
console.log(objA.name); // ==> "对象A"
console.log(copyA.name); // ==> "对象A"
通过上述方式得到objA的浅拷贝copyA。由于两者指向同一内存对象,修改objA会同步影响copyA,反之亦然。
Object.assign方法详解
Object.assign方法详解
Object.assign(target, ...sources) 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。根据MDN文档,String类型和Symbol类型的属性会被拷贝,且该方法会忽略值为null或undefined的源对象。示例如下:
var o1 = { a: 1 };
var o2 = { [Symbol('foo')]: 2 };
var obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]
Object.assign方法的深拷贝与浅拷贝特性
Object.assign方法的深拷贝与浅拷贝特性
需注意前文提到的“可枚举属性”概念,该方法处于完全深拷贝与完全浅拷贝之间。若将第一个参数target设为空对象{},且确保后续源对象sources中的属性值均非引用类型,则返回值是与源对象内容相同但内存地址不同的新对象,此时实现深拷贝。但若源对象属性中包含引用类型(即属性值指向另一个对象),如以下示例所示:
var obj = {
name: 'obj name',
content: {
a: 1,
b: 2
}
};
此时使用 Object.assign({}, obj) 返回的目标对象中,content属性与源对象obj中的content属性指向同一内存区域,即对obj的content属性执行了浅拷贝。若需实现真正的深拷贝,需采用其他方法,如手动实现递归拷贝函数或使用 JSON.parse(JSON.stringify(obj)),因为 Object.assign() 仅拷贝属性值而非深层结构。
Object.assign的属性覆盖机制
Object.assign的属性覆盖机制
若目标对象与源对象存在同名键,源对象属性将覆盖目标对象属性。后出现的源对象属性会覆盖先前的属性,这一特性常用于合并对象,例如在React应用中通过reducers更新状态。
var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };
var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
关于对象的继承属性与不可枚举属性
关于对象的继承属性与不可枚举属性
前文指出,Object.assign 仅拷贝对象的可枚举属性。该方法利用源对象的 [[Get]] 和目标对象的 [[Set]],因此会触发相关的 getter 和 setter。这表明它执行的是属性分配而非单纯的复制。若源对象包含getter,直接合并可能不适合将新属性添加到原型中。若需将属性定义(包括可枚举性)复制到原型,应使用 Object.getOwnPropertyDescriptor() 和 Object.defineProperty()。因此,Object.assign 无法拷贝对象的继承属性,示例如下:
var obj = Object.create({foo: 1}, { // foo 是个继承属性。
bar: {
value: 2 // bar 是个不可枚举属性。
},
baz: {
value: 3,
enumerable: true // baz 是个自身可枚举属性。
}
});
var copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }
当源对象为原始数据类型时的行为
当源对象为原始数据类型时的行为
ECMAScript定义五种原始类型(primitive type):Undefined、Null、Boolean、Number 和 String。当 Object.assign 的源对象为原始类型时,源对象会被包装为对象,对应的键为源对象中的索引值:
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo")
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
拷贝过程中发生异常的情况
拷贝过程中发生异常的情况
若遇到错误(如属性不可写),会抛出TypeError,并中断后续拷贝任务。若在错误发生前已添加部分属性,target对象将处于部分修改状态。
var target = Object.defineProperty({}, "foo", {
value: 1,
writable: false
}); // target 的 foo 属性是个只读属性。
Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
// TypeError: "foo" is read-only
// 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。
console.log(target.bar); // 2,说明第一个源对象拷贝成功了。
console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。
console.log(target.foo); // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
