灏天阁

深浅拷贝问题

· 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 会在转换过程中被忽略。
*/

- Book Lists -