JS的深浅拷贝

61 阅读3分钟

JS数据存储机制

js中基本数据类型,比如number,string,boolean,null,undefind等简单的数据类型是存储在栈中,

引用数据类型则存储在堆中,在栈中则存储着指向该引用数据地址的指针

image.png

基本类型的数据会随着当前执行环境的结束而销毁,引用类型的数据则是引用它的变量不存在是才会被垃圾回收机制回收

let obj1 ={
    name:'dd',
    age:18
}

let obj2 = obj1

obj1.age = 20


当我们把一个对象赋值给另一个对象,再更改第一个对象值,我们期望是

//obj1
{
    name:'dd',
    age:20
}

//obj2
{
    name:'dd',
    age:18
}

实际是

    //obj1
{
    name:'dd',
    age:20
}

//obj2
{
    name:'dd',
    age:20
}

这是因为我们赋值的行为是将这个obj1在内存中的地址赋值给obj2,obj2存储的地址和obj1存储的地址指向同一块内存,所以即使改变对象中某一个属性,另一个也会变化

这个问题怎么解决,方式1:就是浅拷贝

第一种方法就是for...in循环,将对象每一个值和属性依次赋给新对象

let obj1 ={
    name:'dd',
    age:18
}

let obj2 = {}

for(let key in obj1){
    obj2[key] = obj1[key]
}

第二种方法,Object.assign(),这是js对象提供一个合并对象的方法,也可以实现浅拷贝

  let obj1 ={
        name:'dd',
        age:18
    }
let obj2 = Object.assign({},obj1)
//这样也可以实现浅拷贝

第三种,数组的浅拷贝,数组也可以用for...in循环进行拷贝,也可以循环数组,数组其实也是对象,还有就是ES6新增的扩展运算符,还有concat,slice也可以实现浅拷贝

let arr1 = [1,2,3,4]
let arr2 = [...arr1]

但是如浅拷贝这样的一个对象,那就会出现问题

 let obj1 ={
        name:'dd',
        age:18,
        val:{
            num:10
        }
    }

    let obj2 = {}

    for(let key in obj1){
        obj2[key] = obj1[key]
    }
    obj1.val.num=15
    //obj1
    {
        name:'dd',
        age:18,
        val:{
            num:15
        }
    }
    //obj2
    {
        name:'dd',
        age:18,
        val:{
            num:15
        }
    }

其实浅拷贝只拷贝了第一层,如果对象里面放着另一个引用类型的数据,浅拷贝拷贝的还是引用地址

针对这类情况,另一种解决方案就是深拷贝

2,深拷贝

第一种:JSON.stringify,将对象转换为JOSN字符串,再用JSON.parse解析给新对象,直接看代码

let obj1 = {
    a:1,
    b:{
        c:3
    }
}

let obj2 = JSON.stringify(obj1) //{"a":1,"b":{"c":3}}
obj2 = JSON.parse(obj2) 
//{
    a:1,
    b:{
        c:3
    }
}

这看起来简单又方便,但是又有特殊情况,如果我的对象是这样的

const a = {
    b: 1,
    c: {
        val: 0,
        arr: [1, 2, 3, { ff: 0, aad() { return 0} }]
    },
    gg() {
        console.log(33)
    },
    un: undefined,
    sy:Symbol('a')
}
console.log(JSON.stringify(a)); //{"b":1,"c":{"val":0,"arr":[1,2,15,{"ff":0}]}}

这是因为JSON.stringify转换会忽略会忽略 undefined、symbol 和函数,所以衍生出第二种深拷贝方法,手写递归

手写递归

完全实现深拷贝功能

//几个月没写这个,一次写对
const a = {
    b: 1,
    c: {
        val: 0,
        arr: [1, 2, 3, { ff: 0, aad() { return 0} }]
    },
    gg() {
        console.log(33)
    },
    un: undefined,
    sy:Symbol('a')
}

function deep(obj) { 
    let dd = Array.isArray(obj) ? [] : {}  //判断创建了对象是数组还是对象
    for (let key in obj) { 
        if (typeof obj[key] != 'object') { //如果值为基本数据类型,直接赋值
            dd[key] = obj[key]
        } else { 
            dd[key] = deep(obj[key])  //如果不是,递归调用这个函数,处理这个引用类型的数据,并将结果返回
        }
    }
    return dd
}

let ss = deep(a)
a.c.arr[2]=15


console.log(ss);