在 JavaScript 中有不同的方法来复制对象。但主要还是这两种——深拷贝与浅拷贝。这也会是面试的高频考点。因此,本文将带你深入理解深拷贝与浅拷贝,一篇文章足以学透彻!
文末有我在前端面试多年的经验文章,分享给大家!!!
浅拷贝与深拷贝
浅拷贝
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址。所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
let obj1 = { name: '张三', details: { age: 25, address: '上海' } };
let obj2 = shallowClone(obj1);
obj2.details.age = 30;
console.log(obj1.details.age); // 30
console.log(obj2.details.age); // 30
function shallowClone(source) {
let target = {};
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
深拷贝
深拷贝是将一个对象从内存中完整地拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
let obj1 = { name: '张三', details: { age: 25, address: '上海' } };
let obj3 = deepClone(obj1);
obj3.details.age = 30;
console.log(obj1.details.age); // 25
console.log(obj3.details.age); // 30
function deepClone(obj) {
if (obj === null) return obj;
if (typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
借助以下的图片,帮我们更好地理解两者的含义:
总而言之,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。因此深拷贝的内存开销会比较大。
赋值和深/浅拷贝的区别
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象在栈中的地址,而不是堆中的数据。也就是说两个对象指向的是同一个存储空间,无论哪个对象发生改变,都是改变的存储空间的内容,因此,两个对象是联动的。
下面举几个示例代码来说明
对象赋值
let obj1 = { name: '张三', details: { age: 25, address: '上海' } };
let obj2 = obj1;
obj2.details.age = 30;
console.log(obj1.details.age); // 30
console.log(obj2.details.age); // 30
浅拷贝
let obj1 = { name: '张三', details: { age: 25, address: '上海' } };
let obj3 = shallowClone(obj1);
obj3.details.age = 30;
function shallowClone(source) {
let target = {};
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
console.log(obj1.details.age); // 30
console.log(obj3.details.age); // 30
深拷贝
let obj1 = { name: '张三', details: { age: 25, address: '上海' } };
let obj4 = deepClone(obj1);
obj4.details.age = 30;
function deepClone(obj) {
if (obj === null) return obj;
if (typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
console.log(obj1.details.age); // 25
console.log(obj4.details.age); // 30
上面例子中,obj1
是原始对象,obj2
是赋值操作得到的对象,obj3
是浅拷贝得到的对象,obj4
是深拷贝得到的对象。
我们可以发现 无论是对 obj2
还是 obj3
进行修改,原本关联的对象也都会本影响,而 obj4
才不会影响原本的数据。因此只要是对相互关联的其中一个对象修改,另外一个也会受到影响,而赋值和浅拷贝本质上也是关联。
实现方法
浅拷贝的实现方式
1. Object.assign()
let obj1 = { name: '张三', details: { age: 25, address: '上海' } };
let obj2 = Object.assign({}, obj1);
obj2.details.age = 30;
console.log(obj1.details.age); // 30
console.log(obj2.details.age); // 30
Object.assign()
方法可以将一个或多个源对象的属性复制到目标对象上。它执行浅拷贝,即仅复制对象的第一层属性。
2. Lodash 的 _.clone
var _ = require('lodash');
let obj1 = { name: '张三', details: { age: 25, address: '上海' } };
let obj2 = _.clone(obj1);
obj2.details.age = 30;
console.log(obj1.details.age); // 30
console.log(obj2.details.age); // 30
Lodash 是一个流行的 JavaScript 工具库,它提供了_.clone()
方法用于浅拷贝对象。_.clone()
进行浅拷贝,对于对象中嵌套的引用类型属性,它们仍然共享同一块内存。
3.展开运算符 ...
展开运算符是 ES6 引入的一个特性,提供了一种简洁的方式来执行浅拷贝。
let obj1 = { name: 'Kobe', address: { x: 100, y: 100 } };
let obj2 = { ...obj1 };
obj1.address.x = 200;
obj1.name = 'wade';
console.log(obj2); // { name: 'Kobe', address: { x: 200, y: 100 } }
展开运算符...
复制了obj1
的属性到obj2
。同样,由于address
是引用类型,obj1.address
和obj2.address
共享同一个对象。
4.Array.prototype.concat()
concat()
方法用于合并数组或复制数组。对于浅拷贝,通常用于数组的复制。
let arr = [1, 3, { username: 'kobe' }];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr); // [1, 3, { username: 'wade' }]
concat()
复制了数组的第一层元素,修改 arr2[2].username
也会影响 arr[2].username
,因为这两个数组共享相同的对象引用。
5. Array.prototype.slice()
slice()
方法可以用于创建数组的浅拷贝。
let arr = [1, 3, { username: 'kobe' }];
let arr3 = arr.slice();
arr3[2].username = 'wade';
console.log(arr); // [1, 3, { username: 'wade' }]
深拷贝
1.JSON.parse(JSON.stringify())
这是一个常见的深拷贝方法,通过将对象转换为 JSON 字符串,再将字符串解析为对象来实现深拷贝。
let arr = [1, 3, { username: 'kobe' }];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4); // arr: [1, 3, { username: 'kobe' }], arr4: [1, 3, { username: 'duncan' }]
注意JSON.parse(JSON.stringify())
实现了深拷贝,但不能处理函数、正则表达式和undefined
,这些数据在序列化过程中会丢失或被转换。
2 Lodash 的 _.cloneDeep()
Lodash 的 _.cloneDeep()
方法用于实现深拷贝,适用于更复杂的对象结构。
var _ = require('lodash');
var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] };
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false
_.cloneDeep()
递归地复制对象及其嵌套的属性,确保新旧对象完全独立。
3.jQuery.extend()方法
jQuery 提供的 $.extend()
方法可以用于深拷贝对象。第一个参数为 true
时,即为深拷贝。
var $ = require('jquery');
var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] };
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
$.extend(true, {}, obj1)
实现了深拷贝,递归地复制对象中的属性。
总结
浅拷贝和深拷贝是 JavaScript 中两种不同的对象复制方法,各有其适用场景和特点。浅拷贝仅复制对象的第一层属性,对于引用类型的属性只是复制了其引用地址,因此修改深层属性会影响原始对象。而深拷贝则通过递归复制整个对象,包括所有嵌套的属性,确保新旧对象之间完全独立。
在本文中,我们通过具体的代码示例展示了如何实现浅拷贝和深拷贝,并介绍了几种常见的实现方法,掌握这些方法可以帮助你在实际开发中更有效地处理对象复制问题,避免数据污染和潜在的错误。
接下来给大家推荐一篇我在前端面试多年的经验文章,希望大家看完以后都可以领取到心仪的offer哦!
文章:《聊聊前端面试那些事儿》
标签:obj1,obj,age,let,details,必须,拿下,拷贝 From: https://blog.csdn.net/2401_84489283/article/details/141021168