js的深浅拷贝

浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝

如果能完全明白上面这句话表达的意思,可以直接跳过下面的内容,因为此文主要就是讲上面这句话分开来分析,如果不是很明白,可细细品味,挺好理解。
js数据类型

数据类型 Value 存放位置
基本数据类型 String、Number、Boolean、Undefined、Null、Symbol 栈内存
引用数据类型 Object、Array、Function 堆内存

js的深浅拷贝_第1张图片

  • 基本数据类型是一些简单的数据段,它们是存储在栈内存中。如果进行复制,系统会自动为新的变量在栈内存中分配一个新值。
  • 引用数据类型是保存在堆内存中的,然后在栈内存中保存一个对堆内存中实际对象的引用。所以,JavaScript中对引用数据类型的操作都是操作对象的引用而不是实际的对象。简单来说就是在栈内存中有个名字(地址),通过名字(地址)去堆内存中对应的值。如果进行复制,系统也会自动为新的变量在栈内存中分配一个值,但这个值仅仅是一个地址。也就是说,复制出来的变量和原有的变量具有相同的地址值,指向堆内存中的同一个对象。
    举个简单的例子:
    js的深浅拷贝_第2张图片
    如果所示,执行了var objCopy=obj之后,obj和objCopy具有相同的地址值,执行堆内存中的同一个实际对象。也就是说当修改obj或objCopy时,都会引起另一个变量的改变。这就是浅拷贝。

为什么基本数据类型存在栈中,而引用数据类型存在堆中呢?

  • 堆比栈大,栈比对速度快。
  • 基本数据类型比较稳定,而且相对来说占用的内存小。
  • 引用数据类型大小是动态的,而且是无限的。
  • 堆内存是无序存储,可以根据引用直接获取。

浅拷贝

浅拷贝只是复制栈内存中的引用,而没有复制堆内存中的值。

var arr=[1,2,3,4,5,6]
        var arr1=arr;
        arr1.push(7)
        // console.log(arr) // [1, 2, 3, 4, 5, 6, 7]
        // console.log(arr1)// [1, 2, 3, 4, 5, 6, 7]

        var obj={
            name:'小明',
            age:24
        }
        var obj1=obj;
        obj1.sex='男'
        console.log(obj === obj1); // true
        console.log(obj) //{name: "小明", age: 24, sex: "男"}
        console.log(obj1)//{name: "小明", age: 24, sex: "男"}

可以看出,当复制数据发生改变的时候,原有的数据也会跟着改变,这就是浅拷贝。

深拷贝

深拷贝是对目标的完全拷贝,不只是复制了一层引用,值也复制了。

1.利用 JSON 对象中的 parse 和 stringify(拷贝对象里面不能有函数(function)

  • JSON.stringify():可以把 JavaScript 对象转换为字符串。
  • JSON.parse():可以把JSON规则的字符串转换为JSONObject(JSON对象)
var obj={
            name:'小明',
            age:24,
            arr:[1,2,3,4,5,6],
            child:{
                name:'小红',
                age:12
            },
            way:function(){
                console.log('拷贝到了吗?')
            }
        }
        var obj1=JSON.parse(JSON.stringify(obj));
        obj1.sex='男'
        console.log(obj === obj1); // false
        console.log(obj) //{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12}}
        console.log(obj1)//{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},sex:"男"}

可以看出,当复制的数据改变时,原有的数据不会发生改变,实现了深拷贝,没有问题,但是当对象里面包含函数(function)的时候,会是什么样呢?一起来看一下

var obj={
            name:'小明',
            age:24,
            arr:[1,2,3,4,5,6],
            child:{
                name:'小红',
                age:12
            },
            way:function(){
                console.log('拷贝到了吗?')
            }
        }
        var obj1=JSON.parse(JSON.stringify(obj));
        obj1.sex='男'
        console.log(obj) //{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},way:ƒ()}
        console.log(obj1)//{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},sex:"男"}

可以看出,当对象里面有函数的时候,是拷贝不到的,因为在JSON.stringify()在转换的过程中undefined、function这些会被忽略掉,所以当对象中存在function的时候,就不能用JSON.parse(JSON.stringify(obj))这个方法来进行深拷贝

2. 利用递归来实现重新创建对象进行复制(真正的完全拷贝)

function deepObj(obj) {
            let newObj = typeof(obj) === Array ? [] : {}
            for (let key in obj) { 
                if (obj[key] && typeof obj[key] ==='object'){
                    newObj[key]=deepObj(obj[key])
                }else{
                    newObj[key]=obj[key]
                }
            }
            return newObj
        }
        var obj = {
            name: '小明',
            age: 24,
            arr: [1, 2, 3, 4, 5, 6],
            child: {
                name: '小红',
                age: 12
            },
            way: function () {
                console.log('拷贝到了吗?')
            }
        }
        let obj1=deepObj(obj)
        obj1.name='我不叫小明'
        obj1.hobby='打篮球'
        obj1.child.name='我也不叫小红'
        console.log(obj === obj1); // false
        console.log(obj)//{name: "小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'小红',age:12},way:ƒ()}
        console.log(obj1)//{name: "我不叫小明", age: 24,arr:[1,2,3,4,5,6],child:{name:'我也不叫小红',age:12},hobby: "打篮球",way:ƒ()}

可以看出,采用递归的方法可以实现真正的完全拷贝,哪怕是对象里面有方法也可以进行拷贝

3. slice和concat对数组进行深拷贝(一层数组是深拷贝,二层以后的还是浅拷贝)

//slice方法
		//单层数组
        var arr=[1,2,3,4,5,6]
        var newArr=arr.slice()
        newArr.push(7)
        console.log(arr==newArr) //false
        console.log(arr)    //[1, 2, 3, 4, 5, 6]
        console.log(newArr) //[1, 2, 3, 4, 5, 6,7]
        
        //多层数组
        var arr=[1,[1,2,3],3,4,5,6]
        var newArr=arr.slice()
        newArr.push(7)
        newArr[1][1]=6
        console.log(arr==newArr) //false
        console.log(arr)    //[1, [1,6,3], 3, 4, 5, 6]
        console.log(newArr) //[1, [1,6,3], 3, 4, 5, 6,7]
        
//concat方法:
		//单层数组
        var arr=[1,2,3,4,5,6]
        var newArr=arr.concat()
        newArr.push(7)
        console.log(arr==newArr) //false
        console.log(arr)    //[1, 2, 3, 4, 5, 6]
        console.log(newArr) //[1, 2, 3, 4, 5, 6,7]
        //多层数组
        var arr=[1,[1,2,3],3,4,5,6]
        var newArr=arr.concat()
        newArr.push(7)
        newArr[1][1]=6
        console.log(arr==newArr) //false
        console.log(arr)    //[1, [1,6,3], 3, 4, 5, 6]
        console.log(newArr) //[1, [1,6,3], 3, 4, 5, 6,7

可以看出,slice方法和concat方法在单层数组进行拷贝的时候没问题,都是深拷贝,但是在第二层的时候却都是浅拷贝,由此说明slcie方法和concat方法只是对数组的第一层进行深拷贝

总结:

  • 赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
  • slice方法跟concat方法都是“首层浅拷贝”;
  • JSON.parse 和JSON.stringify 实现的是深拷贝,但是对目标对象有要求;
  • 若想真正意义上的深拷贝,请使用递归方法。

你可能感兴趣的:(js相关,js)