js对象深拷贝与浅拷贝

91 阅读2分钟

一、浅拷贝

对象和数组的浅拷贝代码如下:

var obj1 = {
name: 'shen'
}
var obj2 = obj1
obj2.name = 'shenzhiyong'

console.log('obj1:', obj1)   // obj1: {name: "shenzhiyong"}
console.log('obj2:', obj2)   // obj2: {name: "shenzhiyong"}

var arr1 = [1,2,3]
var arr2 = arr1
arr2.push(4)
console.log('arr1:', arr1)   // arr1: [1,2,3,4]
console.log('arr2:', arr2)   // arr2: [1,2,3,4]

浅拷贝的意思就是只复制引用,没有复制真正的值。有时候我们只是想保留对象的数据,单纯想改变obj2和arr2的值,但是原对象的数据也发生了改变。很多时候这种情况都不是我们想要的。为了解决这个问题: 深拷贝它来了!

二、深拷贝

1、JSON方法

var obj1 = {
    name: 'shen'
}
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.name = 'shenzhiyong'

console.log('obj1:', obj1)   // obj1: {name: "shen"}
console.log('obj2:', obj2)   // obj2: {name: "shenzhiyong"}

优点:简单明了,方便记忆

缺点:看下面代码。当对象里面出现函数的时候就不适用了。

var obj1 = {
    name: 'shen',
    show: function (argument) {
        console.log(1)
    }
}
var obj2 = JSON.parse(JSON.stringify(obj1))
console.log('obj1:', obj1)  // obj1: {name: "shen", show: ƒ}
console.log('obj2:', obj2)  // obj2: {name: "shen"}

2、手写递归方法

function deepCopy(obj) {
  var newobj = obj.constructor === Array ? [] : {};
  if (typeof obj !== 'object') {
    return obj;
  } else {
  for (var i in obj) {
    if (typeof obj[i] === 'object'){ //判断对象的这条属性是否为对象
      newobj[i] = deepCopy(obj[i]);  //若是对象进行嵌套调用
    }else{
        newobj[i] = obj[i];
        }
    }
    }
    return newobj; //返回深度克隆后的对象
}

var obj1 = {
    name: 'shen',
    show: function (argument) {
        console.log(1)
    }
}
var obj2 = deepCopy(obj1)
console.log('obj1:', obj1)  // obj1: {name: "shen", show: ƒ}
console.log('obj2:', obj2)  // obj2: {name: "shen"}

优点:能够实现对象和数组的深拷贝

缺点:如果拷贝的对象嵌套过深的话,会对性能有一定的消耗

3、es6解析结构 和 Object.assign()

let obj1 = {
    a:1,
    b:2,
    c:{c1:1,c2:2}
}

// let obj2 = Object.assign({},obj1);
let obj2 = {...obj1};

obj2.a = 10;

obj2.c.c1 = 10;

console.log(obj1);
console.log(obj2);
// 运行结果
// {a: 1, b: 2, c: {}}
// a: 1
// b: 2
// c: {c1: 10, c2: 2}
// __proto__: Object

// {a: 10, b: 2, c: {}}
// a: 10
// b: 2
// c: {c1: 10, c2: 2}
// __proto__: Object
// 可以看到改变基本类型的值是没有变化的,但是当改变嵌套的引用类型时,它们会一起改变

优点:简单方便,适用于简单数据类型

缺点:只能拷贝基本类型的值,如果是引用类型那么拷贝出来的这个值还是属于潜拷贝

4、数组中的slice() & concat()

var arr1 = [1,2,3]
var arr2 = arr1.slice()  // 方法一
// var arr2 = arr1.concat()  //方法二
arr2.push(4)
console.log('arr1:', arr1)  // arr1: [1, 2, 3]
console.log('arr2:', arr2)  // arr1: [1, 2, 3, 4]

5、完全深度拷贝的方案

function cloneFun(obj) {
    // 创建容器
    let newobj = obj.constructor === Array?[]:{};
    // 判断是否是对象类型
    if( typeof obj !== 'object' ){
        return obj;
    }else{
        for (const key in obj) {
            if ( typeof obj[key] === 'object' ) {
                newobj[key] = cloneFun(obj[key])
            }else{
                newobj[key] = obj[key];
            }
        }
    }
    return newobj;
}

let obj2 = cloneFun(obj1);

obj2.c.c1 = 10;

console.log(obj1);
console.log(obj2);
// 运行结果
// {a: 1, b: 2, c: {…}, d: ƒ}
// a: 1
// b: 2
// c: {c1: 1, c2: 2}
// d: ƒ ()
// __proto__: Object
// {a: 1, b: 2, c: {…}, d: ƒ}
// a: 1
// b: 2
// c: {c1: 10, c2: 2}
// d: ƒ ()
// __proto__: Object
// 改变任何值都不影响原始对象上的值

总结:在工作中看需求来选择解决方法,比如拷贝基本类型就用 json方法 、扩展运算符、或者Object.assign,如果有引用那么就用 函数递归拷贝