黑马程序员技术交流社区

标题: 【上海校区】JavaScript中的浅拷贝 VS 深拷贝 [打印本页]

作者: 天树123    时间: 2020-3-19 14:20
标题: 【上海校区】JavaScript中的浅拷贝 VS 深拷贝
一、前言
在前端的数据处理当中,有时候往往需要对原有的数据进行克隆拷贝一份,然后进行操作,但是又不能影响原来的数据
比较典型的应用场景就是:点击一信息列表,然后编辑该列的信息。在弹出的编辑弹出框中,需要复制原有的列表信息内容,如果是浅拷贝的话,那么改变现有的数据,会影响原来的数据,这样显然是不符合需求的
在真实的业务场景中,对数据进行深拷贝是一个非常常见的操作。

二、浅拷贝
定义: 对一个对象进行拷贝时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。
其中一个对象的改变都会影响到另一个对象。
简单点:假设B复制了A对象,改变其中的任何一个对象,都会影响到另一个对象,那么这就是浅拷贝,如果互相不影响,那么就是深拷贝。

三、深拷贝
主要是针对复杂的数据类型
定义:指拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。
简单点:源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响,其实就是打破隔离对象引用之间的关系。
1、那么用代码具体怎么操作实现呢? 先来看一个问题:
var obj =  { x:1, y:2 }
var ob2 =  obj;
obj2.x = 100;
console.log(obj1, obj2);
2、当你改变obj2的值,会影响到obj1的值,显然有时候,这不符合我们的需求,那怎么解决?
1)第一种解决办法: 通过for-in
var obj1 = {x: 1, y: 2};
var obj2 = {};   // 定义一个空对象
for(var key in obj1) {
      obj2[key] = obj1[key]
}
obj2.x = 100;
console.log(obj1, obj2);
2)第二种解决办法: 通过object.assign()方法,这种方式在实际开发中很常用
var obj1 = { x: 1, y: 2}
var obj2 = Object.assign({}, obj1);
ob2.x = 100;
console.log(obj1, obj2);
3)第三种解决办法: 通过Es6中的拓展运算符...
var obj1 = {x: 1, y: 2}
var obj2 = {...obj1};
obj2.x = 100;
console.log(obj1, obj2);
4)第四种解决办法: 通过JSON.parse(JSON.stringify())
var obj1 = { x: 1, y: 2}
var obj2 = JSON.parse(JSON.stringify(obj1));
ob2.x = 100;
console.log(obj1, obj2);
3、上面四种方法都可以解决,改变obj2对象的值,不会影响到obj1对象的值,但是需要格外注意的是:除了第四种深拷贝JSON.parse(JSON.stringify()),其他方式只能做一层拷贝
如果考虑深层的嵌套关系,那么就需要用深拷贝了的,比如类似下面这样的结构
var obj1 = {x: 1, y: 2, z: { a: 3, b: 4}}
var obj2 = {};
for(var key in obj1) {
      obj2[key] = obj1[key]
}
obj2.z.a =  100;
console.log(obj1, obj2);  // 这样的话,改变obj2的变量会影响obj1的变量对象
4、怎么实现深拷贝:利用的是递归
function deepCopy(obj2){
    /*
    * 把一个对象递归拷贝给另外一个对象
    * 源对象与拷贝后的对象没有引用关系,实现克隆
    *
    */
    var obj =  isArray(obj2)?[]:{}; // 判断被拷贝对象是数组还是对象,如果不考虑数组的话,会出现一些问题,如果被拷贝对象里面有数组对象的话,会把数组变为对象,如果传进来是一个数组的话,就让它是一个数组的,是一个对象就是一个对象的
    // 取出obj2的所有属性,如果当前拷贝的数据还是一个对象的话,那么继续调用,deepCopy进行二次拷贝,递归
    for(var property in obj2){
        if(isObject(obj2[property])){
            obj[property] = deepCopy(obj2[property])
        }else{
            obj[property] =  obj2[property]
        }
    }
    return obj;
}

// 判断一个值是不是数组
function isArray(val){
    return Object.prototype.toString.call(val) === '[object Array]';
    // 这里直接用Es6中的Array.isArray()方法判断是不是数组也是可以的,但是上面那种是一些标准库的常见用法
}
// 判断是不是对象
function isObject(val){
    return typeof val ==='object' && val !== null;
}
var obj1 = {x: 1, y: 2, z: { a:3, b: 4}}
var obj2 = deepCopy(obj1);
obj2.x = 100;
obj2.z.a = 200;
console.log(obj1, obj2);

四、总结
本篇主要介绍了JS中的浅拷贝与深拷贝,其中深拷贝,主要是针对复杂的数据类型而言的,也就是对象,对于基本数据类型的拷贝称作为赋值,而复杂数据类型的拷贝为赋止,不用深究概念,改变一个对象,影响了另一个对象,那这就是浅拷贝,要是不影响,那就是深拷贝。
而实现数据的拷贝有四种方式,for-in,以及Object.assign(),拓展运算符...,还有JSON.parse(JSON.stringify()),其中前面三者只能实现数据的一级拷贝,如果想要更深沉次的拷贝的话,那么可以用递归实现,如上面代码所示,当然还有第三方库loadsh中的cloneDeep方法也是一个很常用的方式。
当然最后也提到了一个开发中常见的问题:往一个数组中push对象,前面的值总是会被最后一次的值覆盖的问题,具体解决办法是,把目标对象放在for-in里面就可以了的。





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2