深浅拷贝问题
·
Yin灏
深拷贝和浅拷贝的区别
-
浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;
-
深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。
浅拷贝的实现
浅拷贝的意思就是只复制引用,而未复制真正的值,有时候我们只是想备份数组,但是只是简单让它赋给一个变量,改变其中一个,另外一个就紧跟着改变,但很多时候这不是我们想要的。
// 对象的浅拷贝举例
var obj = {
name:'Hanna Ding',
age: 22
}
var obj2 = obj; // 直接复制的是一个对象,只复制了对象的引用,是浅拷贝
obj2['c'] = 5;
console.log(obj); //Object {name: "Hanna Ding", age: 22, c: 5}
console.log(obj2); //Object {name: "Hanna Ding", age: 22, c: 5}
// 数组的浅拷贝举例
var arr = [1, 2, 3, '4'];
var arr2 = arr;
arr2[1] = "test"; // 修改数组项
console.log(arr); // [1, "test", 3, "4"]
console.log(arr2); // [1, "test", 3, "4"]
arr[0]="fisrt"; // 修改数组项
console.log(arr); // ["fisrt", "test", 3, "4"]
console.log(arr2); // ["fisrt", "test", 3, "4"]
深拷贝的实现
数组的深拷贝
对于数组我们可以使用 slice()
和 concat()
方法来解决上面的问题,但是 slice()
和 concat()
对数组的深拷贝是有局限性的。
- slice()
var arr = ['a', 'b', 'c'];
var arrCopy = arr.slice(0);
arrCopy[0] = 'test'
console.log(arr); // ["a", "b", "c"]
console.log(arrCopy); // ["test", "b", "c"]
- concat()
var arr = ['a', 'b', 'c'];
var arrCopy = arr.concat();
arrCopy[0] = 'test'
console.log(arr); // ["a", "b", "c"]
console.log(arrCopy); // ["test", "b", "c"]
针对上面说的 slice() 和 concat() 对局限性,我们可以继续看下面的例子:
var arr1 = [{"name":"Roubin"},{"name":"RouSe"}];//原数组
var arr2 = [].concat(arr1);//拷贝数组
arr1[1].name="Tom";
console.log(arr1);//[{"name":"Roubin"},{"name":"Tom"}]
console.log(arr2);//[{"name":"Roubin"},{"name":"Tom"}]
var arr1 = [{"name":"weifeng"},{"name":"boy"}]; //原数组
var arr2 = arr1.slice(0) ;//拷贝数组
arr1[1].name="girl";
console.log(arr1); // [{"name":"weifeng"},{"name":"girl"}]
console.log(arr2); //[{"name":"weifeng"},{"name":"girl"}
slice 和 concat 对对象、数组的拷贝,还是浅拷贝
,拷贝之后数组各个值的指针还是指向相同的存储地址。
因此,slice 和 concat 这两个方法,仅适用于对不包含引用对象的一维数组的深拷贝。
- ES6 扩展运算符实现数组的深拷贝 (
前提是一维
)
var arr = [1, 2, 3, 4, 5]
var arr2 = [...arr]; // 针对一维的数据,扩展运算符是深拷贝
arr2[2] = 'test';
console.log(arr2); // [1, 'test', 3, 4, 5]
console.log(arr); // [1, 2, 3, 4, 5]
对象的深拷贝
- 利用递归来实现每一层都重新创建对象并赋值
- 利用 JSON 对象中的 parse 和 stringify
- ES6 扩展运算符实现对象的深拷贝
// 利用 ES6 扩展运算符实现对象的深拷贝
var obj = {
name: 'FungLeo',
sex: 'man',
old: '18'
}
obj2 = { ...obj };
obj.old = '22'
console.log(obj) ///{ name: 'FungLeo', sex: 'man', old: '22'}
console.log(obj2) ///{ name: 'FungLeo', sex: 'man', old: '18'}
var obj = {
name:'xiao ming',
age: 22
}
var obj2 = new Object();
obj2.name = obj.name;
obj2.age = obj.age
obj.name = 'xiao Ding';
console.log(obj); //Object {name: "xiao Ding", age: 22}
console.log(obj2); //Object {name: "xiao ming", age: 22}
/*
obj2是在堆中开辟的一个新内存块,将 obj1 的属性赋值给 obj2 时,obj2 是同直接访问对应的内存地址。
*/
// 递归实现对象的深拷贝
/*
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作
*/
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
var obj = {
name: 'Hanna',
age: 22
}
var objCopy = deepClone(obj)
obj.name = 'ding';
console.log(obj);//Object {name: "ding", age: 22}
console.log(objCopy);//Object {name: "Hanna", age: 22}
// JSON.stringify/parse的方法
// 未封装
const originArray = [1, 2, 3, 4, 5];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false
const originObj = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } };
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
cloneObj.a = 'aa';
cloneObj.c = [1, 1, 1];
cloneObj.d.dd = 'tt';
console.log(cloneObj); // { a: 'a', b: 'b', c: [1, 1, 1], d: { dd: 'dd' } }
console.log(originObj); // { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } }
// JSON.stringify/parse的方法
/****************封装层**************/
function deepClone(origin) {
var clone = {};
try {
clone = JSON.parse(JSON.stringify(origin));
} catch (e) {
}
return clone;
}
const originObj = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } };
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false
//改变值
cloneObj.a = 'aa';
cloneObj.c = [4, 5, 6];
cloneObj.d.dd = 'tt';
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'tt'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
虽然上面的深拷贝很方便(请使用封装函数进行项目开发以便于维护),但是,只适合一些简单的情景(Number, String, Boolean, Array, Object),扁平对象,那些能够被 json 直接表示的数据结构。function对象
,RegExp对象
是无法通过这种方式深拷贝。
const originObj = {
name:'pipi',
sayHello:function(){
console.log('Hello pipi');
}
}
console.log(originObj); // {name: "pipi", sayHello: ƒ}
const cloneObj = deepClone(originObj);
console.log(cloneObj); // {name: "pipip"}
/*
undefined、function、symbol 会在转换过程中被忽略。
*/