js 深拷贝和浅拷贝

深浅拷贝对比

深拷贝和浅拷贝是只针对Object和Array这样的对象数据类型的。

深拷贝和浅拷贝的示意图大致如下:

基本类型--名值存储在栈内存中,例如let a=1;

当b=a复制的时候,栈内存会新开辟一个内存,如下:

 当你修改a=2时,b并不会收到影响,因为他有了自己独立的内存空间,但这并不是深拷贝,深拷贝和浅拷贝是只针对Object和Array这样的对象数据类型的。

引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值

 当进行b=a拷贝时。其实是复制了a的引用地址,而并非是堆内存里面的值,

 

 当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。

那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,起步就达到深拷贝的效果了

 

 浅拷贝只是复制了指向某个对象的指针,并没有复制对象的值,新旧对象还是共享同一个内存空间,但深拷贝会重新创建一个相同的对象,新对象有自己的指针和内存空间。我们可以多次使用同样的数据,数据修改后也不会发生错乱。

浅拷贝的实现方式

1. 直接赋值

let obj = {username: 'kobe', age: 39, sex: {option1: '男', option2: '女'}};
  let obj1 = obj;
  obj1.sex.option1 = '不男不女'; // 修改复制的对象会影响原对象
  console.log(obj1, obj);

2.Object.assign()

let obj = {
    username: 'kobe'
    };
let obj2 = Object.assign(obj);
obj2.username = 'wade';
console.log(obj);//{username: "wade"}

3.Array.prototype.concat()

let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2=arr.concat();    
arr2[2].username = 'wade';
console.log(arr);

4.Array.prototype.slice()

let arr = [1, 3, {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);

关于Array的slice和concat方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

  • 如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变
  • 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
let arr = [1, 3,[5,8] {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[1] = 2
console.log(arr,arr3);

 对于这种浅层次的确实不受影响,但是深层次的呢?我们来看一看

let arr = [1, 3,[5,8], {
    username: ' kobe'
    }];
let arr4 = arr.concat();
arr4[1] = 2
arr4[2][0] = 9
console.log(arr,arr4);

 

 可以看到这种二级属性还是没能拷贝成功。所以slice和concat方法并不是真正的深拷贝方法。

深拷贝的实现方式

1、递归

递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

// 定义一个深拷贝函数  接收目标target参数
function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === 'object') {
    // 如果是一个数组的话
        if (Array.isArray(target)) {
            result = []; // 将result赋值为一个数组,并且执行遍历
            for (let i in target) {
                // 递归克隆数组中的每一项
                result.push(deepClone(target[i]))
            }
         // 判断如果当前的值是null的话;直接赋值为null
        } else if(target===null) {
            result = null;
         // 判断如果当前的值是一个RegExp对象的话,直接赋值    
        } else if(target.constructor===RegExp){
            result = target;
        }else {
         // 否则是普通对象,直接for in循环,递归赋值对象的所有值
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
     // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
        result = target;
    }
     // 返回最终结果
    return result;
}
let obj1 = {
        a: {
            c: /a/,
            d: undefined,
            b: null
        },
        b: function () {
            console.log(this.a)
        },
        c: [
            {
                a: 'c',
                b: /b/,
                c: undefined
            },
            'a',
            3
        ]
    }
    let obj2 = deepClone(obj1);
obj2.c[0].a='aaaa' console.log(obj1,obj2);

可以看到obj里面的全部属性都拷贝成功,修改obj2,obj1的数据并不会受影响

 2、JSON.parse(JSON.stringify())

let arr = [1, 2, {
    username: ' lihh'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'whhh'; 
console.log(arr, arr4)

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数

let arr = [1, 2, {
    username: ' lihh'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'whhh'; 
console.log(arr, arr4)

 因为JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数

 缺点:如果对象中包含时间格式就会转为字符串、如果包含正则就会成为 undefined、如果包含function、undefined就会丢失。

 

不积跬步无以至千里
原文地址:https://www.cnblogs.com/lyt0207/p/12082727.html