文章目录
一、JS 的变量存储方式
在学习JS数组和对象的深拷贝方法前,我们必须了解JS的变量存储方式,这样才能更好的了解深浅拷贝!
JS 的变量存储方式: 栈(stack)和堆(heap)。
栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址(指针)。
堆:动态分配的内存,大小不定,也不会自动释放,里面存放引用类型的值
例如:
// 基本类型:赋值 相当于 赋了真正的值
let str_1 = 'abc';
let str_2 = str_1;
str_2 = 'def';
console.log(str_1); // abc
console.log(str_2); // def
// 引用类型:赋值 相当于 赋了引用类型的地址
let obj_1 = {
name:'张三'
}
let obj_2 = obj_1;
obj_2.name = '李四';
console.log(obj_1); // {name: '李四'}
console.log(obj_2); // {name: '李四'}
二、数组深拷贝
1.for循环
var arr_3 = [1,2,3];
var arr_4 = [];
for (let i = 0; i < arr_3.length; i++) {
// 循环追加元素
// 把每一项的元素追加到arr4的后面
arr_4.push(arr_3[i]);
}
arr_4.unshift(0);
console.log(arr_3); // [1, 2, 3]
console.log(arr_4); // [0, 1, 2, 3]
2.slice()
var arr_5 = [1,2,3];
var arr_6 = arr_5.slice(0);
// slice() 方法会返回一个新的数组对象
arr_6.shift();
console.log(arr_5); // [1, 2, 3]
console.log(arr_6); // [2, 3]
3.concat()
var arr_7 = [1,2,3];
var arr_8 = arr_7.concat();
// concat() 方法将一个或多个字符串与原字符串连接合并
// 它会形成一个新的字符串并返回。
arr_8.pop();
console.log(arr_7); // [1, 2, 3]
console.log(arr_8); // [1, 2]
4.filter()
filter 对数组元素进行判断满足条件的会组成一个新的数组
let clone = (arr) =>arr.filter((x)=>x)
5.使用 ES6 扩展运算符
扩展运算符用于展开数组或对象,可以将一个数组展开为另一个数组。对于数组的深拷贝,可以使用扩展运算符来创建一个新数组,并复制原数组中的元素。
var newArr = [...arr];
三、对象深拷贝
在 JavaScript 中,实现对象的深拷贝有多种方法。以下是几种常见的对象深拷贝方法:
1.for-in
通过遍历对象中的每一项,把每一项都赋给新对象实现深拷贝
var obj_1 = {
name:'张三',
age:80,
}
var obj_2 = {};
for (const key in obj_1) {
// 这里的key代表键名
obj_2[key] = obj_1[key];
}
obj_2.name = '李四';
console.log(obj_1); // {name: '张三', age: 80}
console.log(obj_2); // {name: '李四', age: 80}
2.JSON.stringify
把一个对象里面的内容转成JSON字符串,再用 JSON.parse() 的方法将JSON 字符串生成一个新的对象。
var obj_1 = { a: 1, b: [1, 2, 3] }
var str = JSON.stringify(obj_1);
var obj_2 = JSON.parse(str);
console.log(obj_2); //{a:1,b:[1,2,3]}
obj_1.a = 2;
obj_1.b.push(4);
console.log(obj_1); //{a:2,b:[1,2,3,4]}
console.log(obj_2); //{a:1,b:[1,2,3]}
3.Object.assign
该方法用于将所有可枚举属性从一个或多个源对象复制到目标对象中,可以用于浅拷贝,但在处理引用类型时仍然存在问题。要实现深拷贝,可以将目标对象设置为空对象,再使用 Object.assign() 复制属性。
let deepClone = Object.assign({},obj)
obj.str = 'abc';
obj.children.str = 'def'
console.log(obj,'obj');
console.log(deepClone ,'deepClone ');
4.使用递归实现循环引用处理
在进行对象深拷贝时,循环引用是一个常见的问题,可以使用递归来处理循环引用,通过记录已经拷贝的对象,遇到循环引用时返回已经拷贝的对象。
function deepCopyWithCircularRef(obj, cache = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj);
}
var copy = Array.isArray(obj) ? [] : {};
cache.set(obj, copy);
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
copy[key] = deepCopyWithCircularRef(obj[key], cache);
}
}
return copy;
}
5.使用第三方库
许多 JavaScript 库(如 Lodash、jQuery 等)提供了深拷贝函数,这些函数可以处理对象的各种情况,包括特殊类型和循环引用。例如,Lodash 的 cloneDeep() 方法可以深拷贝对象。
var newObj = _.cloneDeep(obj); // 使用 Lodash 深拷贝
需要注意的是,使用某种深拷贝方法时,要考虑对象中的特殊情况和是否需要保留特定对象的原型链。在实际开发中,根据具体的需求和性能要求,选择适合的深拷贝方法。