JavaScript 对象复制完全指南(速查手册)
大约 8 分钟
JavaScript 对象复制完全指南(速查手册)
📚 目录
浅拷贝 vs 深拷贝
概念对比
// 原始对象
const original = {
name: '张三',
age: 25,
address: {
city: '北京',
district: '朝阳区'
}
};
// 浅拷贝:只复制第一层
const shallow = { ...original };
shallow.name = '李四'; // ✅ 不影响原对象
shallow.address.city = '上海'; // ❌ 会影响原对象!
console.log(original.name); // '张三' ✅
console.log(original.address.city); // '上海' ❌ 被修改了!
// 深拷贝:递归复制所有层级
const deep = JSON.parse(JSON.stringify(original));
deep.name = '王五'; // ✅ 不影响原对象
deep.address.city = '广州'; // ✅ 不影响原对象
console.log(original.name); // '张三' ✅
console.log(original.address.city); // '北京' ✅ 没有被修改
可视化对比
原始对象:
┌─────────────┐
│ name: '张三' │
│ age: 25 │
│ address: ───┼──→ ┌──────────────┐
└─────────────┘ │ city: '北京' │
│ district: ... │
└──────────────┘
浅拷贝:
┌─────────────┐
│ name: '李四' │ (新的)
│ age: 25 │ (新的)
│ address: ───┼──→ ┌──────────────┐
└─────────────┘ │ city: '北京' │ (共享!)
│ district: ... │
└──────────────┘
深拷贝:
┌─────────────┐
│ name: '王五' │ (新的)
│ age: 25 │ (新的)
│ address: ───┼──→ ┌──────────────┐
└─────────────┘ │ city: '广州' │ (新的!)
│ district: ... │
└──────────────┘
对象浅拷贝
方法1:展开运算符 ... (推荐⭐⭐⭐⭐⭐)
// 基本用法
const original = { name: '张三', age: 25 };
const copy = { ...original };
// 同时修改属性
const copy2 = { ...original, age: 30 };
// 合并多个对象
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
// 结果: { a: 1, b: 2, c: 3, d: 4 }
// 覆盖属性
const base = { name: '张三', age: 25, city: '北京' };
const updated = { ...base, age: 30 };
// 结果: { name: '张三', age: 30, city: '北京' }
方法2:Object.assign()
// 基本用法
const original = { name: '张三', age: 25 };
const copy = Object.assign({}, original);
// 合并多个对象
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const obj3 = { c: 3 };
const merged = Object.assign({}, obj1, obj2, obj3);
// 结果: { a: 1, b: 2, c: 3 }
// 修改目标对象(不推荐)
const target = { a: 1 };
Object.assign(target, { b: 2 }); // target 被修改了
方法3:手动复制
// 简单对象
const original = { name: '张三', age: 25 };
const copy = {};
for (let key in original) {
copy[key] = original[key];
}
对象深拷贝
方法1:JSON 序列化 (最简单⭐⭐⭐⭐⭐)
// 基本用法
const original = {
name: '张三',
age: 25,
address: {
city: '北京',
district: '朝阳区'
},
hobbies: ['读书', '旅游']
};
const deepCopy = JSON.parse(JSON.stringify(original));
// 修改深层属性
deepCopy.address.city = '上海';
console.log(original.address.city); // '北京' ✅ 不受影响
⚠️ 注意:JSON 方法的局限性
const obj = {
date: new Date(), // ❌ 会变成字符串
func: () => {}, // ❌ 会丢失
undefined: undefined, // ❌ 会丢失
symbol: Symbol('test'), // ❌ 会丢失
regex: /test/, // ❌ 会变成空对象
nan: NaN, // ❌ 会变成 null
infinity: Infinity // ❌ 会变成 null
};
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy);
// { date: "2024-01-01T00:00:00.000Z", regex: {}, nan: null, infinity: null }
方法2:递归深拷贝 (通用⭐⭐⭐⭐⭐)
/**
* 深拷贝函数(支持对象、数组、Date、RegExp等)
* @param {*} obj - 要拷贝的对象
* @returns {*} 拷贝后的对象
*/
function deepClone(obj) {
// 处理 null 和基本类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理 Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// 处理 RegExp
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理数组
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
// 处理对象
const clonedObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
// 使用示例
const original = {
name: '张三',
date: new Date(),
regex: /test/g,
nested: {
value: 100,
array: [1, 2, 3]
}
};
const copy = deepClone(original);
copy.nested.value = 200;
console.log(original.nested.value); // 100 ✅
方法3:lodash 库 (生产环境推荐⭐⭐⭐⭐⭐)
// 安装: npm install lodash
import _ from 'lodash';
const original = {
name: '张三',
address: {
city: '北京'
}
};
const deepCopy = _.cloneDeep(original);
方法4:structuredClone (现代浏览器⭐⭐⭐⭐)
// 浏览器原生方法(Chrome 98+, Firefox 94+)
const original = {
name: '张三',
date: new Date(),
nested: {
value: 100
}
};
const deepCopy = structuredClone(original);
数组浅拷贝
方法1:展开运算符 ... (推荐⭐⭐⭐⭐⭐)
// 基本用法
const original = [1, 2, 3, 4, 5];
const copy = [...original];
// 合并数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2];
// 结果: [1, 2, 3, 4, 5, 6]
// 在开头添加元素
const arr = [2, 3, 4];
const newArr = [1, ...arr];
// 结果: [1, 2, 3, 4]
// 在末尾添加元素
const arr = [1, 2, 3];
const newArr = [...arr, 4];
// 结果: [1, 2, 3, 4]
// 在中间插入元素
const arr = [1, 2, 5];
const newArr = [...arr.slice(0, 2), 3, 4, ...arr.slice(2)];
// 结果: [1, 2, 3, 4, 5]
方法2:slice()
// 基本用法
const original = [1, 2, 3, 4, 5];
const copy = original.slice();
// 复制部分数组
const partial = original.slice(1, 3);
// 结果: [2, 3]
方法3:concat()
// 基本用法
const original = [1, 2, 3];
const copy = [].concat(original);
// 合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = arr1.concat(arr2);
// 结果: [1, 2, 3, 4]
方法4:Array.from()
// 基本用法
const original = [1, 2, 3];
const copy = Array.from(original);
// 转换类数组对象
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const arr = Array.from(arrayLike);
// 结果: ['a', 'b', 'c']
数组深拷贝
方法1:JSON 序列化
// 基本用法
const original = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
];
const deepCopy = JSON.parse(JSON.stringify(original));
// 修改深层属性
deepCopy[0].name = '王五';
console.log(original[0].name); // '张三' ✅
方法2:递归深拷贝
// 使用前面定义的 deepClone 函数
const original = [
{ id: 1, name: '张三', tags: ['tag1', 'tag2'] },
{ id: 2, name: '李四', tags: ['tag3', 'tag4'] }
];
const deepCopy = deepClone(original);
方法3:map + 展开运算符(一层嵌套)
// 适用于数组元素是对象的情况
const original = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
];
const deepCopy = original.map(item => ({ ...item }));
// 修改属性
deepCopy[0].name = '王五';
console.log(original[0].name); // '张三' ✅
Vue 中的特殊情况
问题:Vue 的响应式对象
// Vue 2
const original = {
name: '张三',
age: 25,
__ob__: Observer // Vue 添加的响应式标记
};
// ❌ 错误:会复制 __ob__
const copy1 = { ...original };
// ✅ 正确:使用 JSON(会去除 __ob__)
const copy2 = JSON.parse(JSON.stringify(original));
// ✅ 正确:手动过滤
const copy3 = Object.keys(original).reduce((acc, key) => {
if (key !== '__ob__') {
acc[key] = original[key];
}
return acc;
}, {});
解决方案:Vue 专用复制函数
/**
* 复制 Vue 响应式对象(去除 __ob__)
* @param {Object} obj - Vue 响应式对象
* @returns {Object} 纯净的对象
*/
function cloneVueObject(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => cloneVueObject(item));
}
const cloned = {};
for (let key in obj) {
// 跳过 Vue 的内部属性
if (key === '__ob__' || key === '__v_skip') {
continue;
}
if (obj.hasOwnProperty(key)) {
cloned[key] = cloneVueObject(obj[key]);
}
}
return cloned;
}
// 使用示例
export default {
data() {
return {
form: {
name: '张三',
age: 25
}
}
},
methods: {
copyForm() {
// ✅ 正确
const copy = cloneVueObject(this.form);
// 或者使用 JSON
const copy2 = JSON.parse(JSON.stringify(this.form));
}
}
}
实战场景
场景1:表单重置
export default {
data() {
return {
// 原始表单数据
defaultForm: {
name: '',
age: null,
address: {
city: '',
district: ''
}
},
// 当前表单数据
form: {}
}
},
created() {
// 初始化时深拷贝
this.resetForm();
},
methods: {
resetForm() {
// ✅ 深拷贝,避免修改 defaultForm
this.form = JSON.parse(JSON.stringify(this.defaultForm));
},
submitForm() {
// 提交表单...
// 提交成功后重置
this.resetForm();
}
}
}
场景2:编辑对话框
export default {
data() {
return {
dialogVisible: false,
editForm: {},
originalData: null
}
},
methods: {
// 打开编辑对话框
handleEdit(row) {
// ✅ 深拷贝,避免直接修改表格数据
this.originalData = row;
this.editForm = JSON.parse(JSON.stringify(row));
this.dialogVisible = true;
},
// 取消编辑
handleCancel() {
this.dialogVisible = false;
this.editForm = {};
this.originalData = null;
},
// 保存编辑
handleSave() {
// 更新原始数据
Object.assign(this.originalData, this.editForm);
this.dialogVisible = false;
}
}
}
场景3:数组操作(不修改原数组)
export default {
data() {
return {
list: [
{ id: 1, name: '张三', selected: false },
{ id: 2, name: '李四', selected: false },
{ id: 3, name: '王五', selected: false }
]
}
},
methods: {
// ❌ 错误:直接修改原数组
toggleSelectWrong(id) {
const item = this.list.find(item => item.id === id);
item.selected = !item.selected;
},
// ✅ 正确:创建新数组
toggleSelectRight(id) {
this.list = this.list.map(item => {
if (item.id === id) {
return { ...item, selected: !item.selected };
}
return item;
});
},
// 添加项目
addItem(newItem) {
// ✅ 不修改原数组
this.list = [...this.list, newItem];
},
// 删除项目
removeItem(id) {
// ✅ 不修改原数组
this.list = this.list.filter(item => item.id !== id);
},
// 更新项目
updateItem(id, updates) {
// ✅ 不修改原数组
this.list = this.list.map(item => {
if (item.id === id) {
return { ...item, ...updates };
}
return item;
});
}
}
}
场景4:保存历史记录(撤销/重做)
export default {
data() {
return {
form: { name: '', age: null },
history: [],
historyIndex: -1
}
},
methods: {
// 保存当前状态到历史
saveHistory() {
// 删除当前位置之后的历史
this.history = this.history.slice(0, this.historyIndex + 1);
// 添加新的历史记录(深拷贝)
this.history.push(JSON.parse(JSON.stringify(this.form)));
this.historyIndex++;
// 限制历史记录数量
if (this.history.length > 50) {
this.history.shift();
this.historyIndex--;
}
},
// 撤销
undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.form = JSON.parse(JSON.stringify(this.history[this.historyIndex]));
}
},
// 重做
redo() {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.form = JSON.parse(JSON.stringify(this.history[this.historyIndex]));
}
}
}
}
场景5:对比数据变化
export default {
data() {
return {
originalData: {},
currentData: {}
}
},
methods: {
// 加载数据
loadData(data) {
// 保存原始数据
this.originalData = JSON.parse(JSON.stringify(data));
// 当前数据
this.currentData = JSON.parse(JSON.stringify(data));
},
// 检查是否有修改
hasChanges() {
return JSON.stringify(this.originalData) !== JSON.stringify(this.currentData);
},
// 获取变化的字段
getChangedFields() {
const changes = {};
for (let key in this.currentData) {
if (JSON.stringify(this.originalData[key]) !== JSON.stringify(this.currentData[key])) {
changes[key] = {
old: this.originalData[key],
new: this.currentData[key]
};
}
}
return changes;
},
// 重置修改
resetChanges() {
this.currentData = JSON.parse(JSON.stringify(this.originalData));
}
}
}
快速参考表
| 场景 | 推荐方法 | 代码 |
|---|---|---|
| 对象浅拷贝 | 展开运算符 | { ...obj } |
| 对象深拷贝 | JSON | JSON.parse(JSON.stringify(obj)) |
| 数组浅拷贝 | 展开运算符 | [...arr] |
| 数组深拷贝 | JSON | JSON.parse(JSON.stringify(arr)) |
| 合并对象 | 展开运算符 | { ...obj1, ...obj2 } |
| 合并数组 | 展开运算符 | [...arr1, ...arr2] |
| 更新对象属性 | 展开运算符 | { ...obj, key: newValue } |
| 更新数组元素 | map | arr.map(item => item.id === id ? { ...item, ...updates } : item) |
| Vue 表单重置 | JSON | this.form = JSON.parse(JSON.stringify(this.defaultForm)) |
| 复杂对象深拷贝 | lodash | _.cloneDeep(obj) |
总结
记忆口诀
浅拷贝:展开运算符 {...obj} [...arr]
深拷贝:JSON.parse(JSON.stringify())
Vue 对象:去除 __ob__
不修改:总是创建新对象/数组
最佳实践
- ✅ 默认使用浅拷贝(性能更好)
- ✅ 需要深层修改时使用深拷贝
- ✅ Vue 中避免直接修改 props 和 data
- ✅ 数组操作使用 map/filter/concat 等不可变方法
- ✅ 表单重置使用深拷贝
常见错误
// ❌ 错误1:直接赋值(不是拷贝)
const copy = original;
// ❌ 错误2:浅拷贝嵌套对象
const copy = { ...original };
copy.nested.value = 100; // 会影响原对象!
// ❌ 错误3:直接修改数组
this.list.push(item); // 可能导致 Vue 响应式问题
// ✅ 正确
const copy = JSON.parse(JSON.stringify(original));
this.list = [...this.list, item];