深浅拷贝
浅拷贝
浅拷贝是指创建一个新的对象,把这个对象的原始属性精确拷贝一份,如果是基本类型就拷贝基本类似的值,如果是引用类型,拷贝的就是内存地址,如果其中一个引用类型改变了值,那么就会影响另一个对象
深拷贝
深拷贝是将一个对象从内存中完成拷贝出来,在内存中开辟一个新的区域存放对象,并且两者不会相互影响
看图理解


赋值和浅拷贝的区别
在这篇文章之前,我一直认为,赋值就是浅拷贝,现在发现真的是大错特错,
赋值对于基本类型来说可以认为是浅拷贝,(基本类型没有深浅拷贝),下面我们来看一段代码
const obj = {
name: "12",
message: ["帅气",['丑'] "英俊"],
};
const obj2 = obj;
obj2.name = "14";
obj2.message[1] = ['真丑']
console.log(obj); //{ name: '14', message: [ '帅气',['真丑'], '英俊' ] }
console.log(obj2); // { name: '14', message: [ '帅气',['真丑'], '英俊' ] }
从代码中可以看出 name 的修改会相互影响。下面我们来看浅拷贝有哪些方法,以及使用浅拷贝之后 name 应该是什么样子的
浅拷贝方法
- Object.assign() 对象浅拷贝
const obj = {
name: "12",
message: ["帅气", "英俊"],
};
let obj3 = Object.assign({}, obj);
obj3.name = "15";
console.log(obj, obj3);
// 可以看到浅拷贝修改之后name的变更并不会相互影响
{ name: '14', message: [ '帅气', '英俊' ] } { name: '15', message: [ '帅气', '英俊' ] }
- ... 展开运算符 对象数组浅拷贝
const obj = {
name: "12",
message: ["帅气", "英俊"],
};
let obj4 = { ...obj };
obj4.name = "obj4";
console.log(obj, obj4);
// { name: '14', message: [ '帅气', '英俊' ] } { name: 'obj4', message: [ '帅气', '英俊' ] }
- concat 数组浅拷贝
let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr); //[ 1, 2, 3 ]
console.log(newArr); // [ 1, 100, 3 ]
- slice 数组浅拷贝
let arr = [1, 2, { val: 4 }];
let newArr = arr.slice();
newArr[2].val = 1000;
newArr[1] = 10;
console.log(arr); //[ 1, 2, { val: 1000 } ]
console.log(newArr); // [ 1, 10, { val: 1000 } ]
- 手动实现浅拷贝
function clone(source) {
if (typeof source === "object") {
let target = Array.isArray(source) ? [] : {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
// source.hasOwnProperty(i)意思是_proto_上面的属性我们不拷贝
target[i] = source[i];
}
}
return target;
} else {
return target;
}
}
以上就是一些浅拷贝的方法
深拷贝
- 简单的方法 JSON.parse(JSON.stringify())
只适用于固定的数据结构
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则
const obj = [{ name: "1" }, { name: "2" }];
const obj2 = JSON.parse(JSON.stringify(obj));
obj2[0].name = 3;
console.log(obj, obj2);
// [ { name: '1' }, { name: '2' } ] [ { name: 3 }, { name: '2' } ]
- lodash 库的_.cloneDeep
封装好的库可以直接使用,简单方便
let _ = require("lodash");
let obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3],
};
let obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false
- 手写 递归方法
深拷贝,递归+浅拷贝就是深拷贝
看一段代码
function copy1(source) {
let target = {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === "object") {
target[i] = copy1(source[i]);
} else {
target[i] = source[i];
}
}
}
return target;
}
const a = {
name: "1",
message: {
name: "2",
},
age: null,
};
const b = copy1(a);
b.message.name = "3";
console.log(a, b);
// { name: '1', message: { name: '2' }, age: null } { name: '1', message: { name: '3' }, age: {} }
在浅拷贝的基础上添加了一个判断是否是对象,如果是对象进入递归,实现了简单的深拷贝
再看一段代码
// 判断是否是对象
function isObject(obj) {
return typeof obj === "object" && obj !== null;
}
function copy2(source) {
if (!isObject(source)) return source;
let target = Array.isArray(source) ? [] : {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
if (isObject(source[i])) {
target[i] = copy2(source[i]);
} else {
target[i] = source[i];
}
}
}
return target;
}
const a = {
name: "1",
message: {
name: "2",
},
age: null,
arr: [1, 2, 3, [4, 5]],
};
let b = copy2(a);
b.arr[3][1] = 9;
console.log(a);
// { name: '1',
// message: { name: '2' },
// age: null,
// arr: [ 1, 2, 3, [ 4, 5 ] ] }
console.log(b);
// { name: '1',
// message: { name: '2' },
// age: null,
// arr: [ 1, 2, 3, [ 4, 9 ] ] }
实现了对数组的兼容
解决循环引用,再看一段代码
function copy3(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
var target = Array.isArray(source) ? [] : {};
hash.set(source, target); // 新增代码,哈希表设值
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], hash); // 新增代码,传入哈希表
} else {
target[key] = source[key];
}
}
}
return target;
}
// es5版本
function cloneDeep3(source, uniqueList) {
if (!isObject(source)) return source;
if (!uniqueList) uniqueList = []; // 新增代码,初始化数组
var target = Array.isArray(source) ? [] : {};
// ============= 新增代码
// 数据已经存在,返回保存的数据
var uniqueData = find(uniqueList, source);
if (uniqueData) {
return uniqueData.target;
}
// 数据不存在,保存源数据,以及对应的引用
uniqueList.push({
source: source,
target: target,
});
// =============
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], uniqueList); // 新增代码,传入数组
} else {
target[key] = source[key];
}
}
}
return target;
}
// 新增方法,用于查找
function find(arr, item) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
}
主要的思路就是我们存储已经拷贝的对象,当我们检测道这个对象已经拷贝之后,我们就不再递归直接取出改值使用
参考文献
深拷贝 主要参考了 https://muyiy.cn/blog/4/4.3.htm (其中还有关于拷贝 Symbol 递归爆栈的解法)
网友评论