js对象拷贝or Object.assign来实现浅拷贝和深拷贝

631 阅读3分钟

       要理解深浅克隆首先要知道js的数据类型:字符串(String)、数字(Number)、布尔(Boolean)、数组(Array)、对象(Object)、空(Null)、未定义(Undefined)以及es6新增数据类型symbol。

js将这些数据类型分为两类:**基本类型和引用类型。**其中基本数据类型指的是简单的数据段,包括:**字符串(String)、数字(Number)、布尔(Boolean)****、空(Null)、未定义(Undefined)。**引用据类型指的是有多个值构成的对象,包括:**数组(Array)、对象(Object)。js为什么会把他们分为两种类型,根本上是因为基本数据类型保存在栈内存,**保存在栈内存的必须是大小固定的数据,而引用类型保存在堆内存中,引用类型的大小不固定,只能保存在堆内存中,然后把它的地址放在栈中访问。

举个例子:

var age = 27;
var worker = {
      name:"张三"
    } 

所以操作的是堆还是栈就出现了深浅拷贝的问题

深浅拷贝

例:
var age = 27;
var newAge = age;
console.log(age)//------27
console.log(newAge)//---27
newAge = 72;
console.log(age)//------27
console.log(newAge)//---72

当操作的是基本类型时newAge并没有对age产生影响

例:
var worker = {
    name:"yyx"
  }
  var newWorker = worker
  console.log(worker)//-------{name:"yyx"}
  console.log(newWorker)//----{name:"yyx"}
  newWorker.name = "yu"
  console.log(worker)//-------{name:"yu"}
  console.log(newWorker)//----{name:"yu"}
当操作的是引用类型时,改变newWorker的值时worker的值也发生变化了

因为上面两个例子克隆的都只是栈,而克隆的引用类型还指向同一个堆所以改变其中一个的数据,别外的也会发生改变,这就是浅克隆,如图:

1.递归实现深拷贝

而想要完成深克隆,即worker和newWorker互不影响,最常用的就是方法就是递归:

        function deepClone(obj) {
            if (typeof obj != "object") {
                return obj
            }
            let newObj = {};
            for (var i in obj) {
                newObj[i] = deepClone(obj[i]);
            }
            return newObj
        }
        let people = {
            name: "张三",
            age: "27"
        }
        let copyPeople = deepClone(people)
        people.name = "李四"
        console.log(people)      //{name:"李四", age:"27"}
        console.log(copyPeople)  //{name:"张三", age:"27"}

此时newWorker指向了新的堆,更改时不会对worker产生影响

如图:

2.通过JSON方法实现深拷贝

通过 JSON.parse(JSON.stringify())来实现对象的深拷贝

        let people = {
            name: "张三",
            age: "27",
            sayName(){
                console.log(this.name)
            }
        }
        let copyPeople = JSON.parse(JSON.stringify(people))
        people.name = "李四"
        console.log(people)      //{name: "李四", age: "27", sayName: ƒ}
        console.log(copyPeople)  //{name:"张三", age:"27"}

该方法存在一个问题是:只能拷贝对象的属性,对象方法会丢失

3.Object.assign实现深拷贝

ES6新增方法assign():将源对象的所有可枚举属性复制到目标对象

assign(target,...obj)方法接受多个参数,第一个参数为拷贝目标,剩余参数是拷贝源。此方法可以将...obj中的属性复制到target中,名字相同的属性会覆盖。assign实现的对象的浅拷贝 。
        let peopleA = {
            id: "a001",
            name: "a",
            age: "15"
        }
        let peopleB = {
            id: "b002",
            name: "b",
            sex:"1"
        }
        let peopleC = Object.assign(peopleA, peopleB)
        //此时创建了一个对象 peopleC 值为 {id: "b002", name: "b", age: "15", sex: "1"}

        //1.首先修改 peopleA里面值
        peopleA.id = "c003"
        //此时打印peopleC发现变为了 {id: "c003", name: "b", age: "15", sex: "1"}

        //2.再来修改 peopleB的值
        peopleB.name = "c"
        //此时再次打印peopleC,peopleC的值不变,仍为{id: "c003", name: "b", age: "15", sex: "1"}

那assign()如何实现一个深拷贝呢,此时对目标对象 peopleA来说如果我们传入的拷贝源是空对象的话,peopleC就是peopleA的浅拷贝,然后我们换一个角度思考,把pelpleB当成我们想要拷贝的对象,目标对象传入一个空数组就可以实现对拷贝源的深拷贝

        let copyA = Object.assign(peopleA,{})
        peopleA.name = "copya"
        console.log(copyA)            //{id: "a001", name: "copya", age: "15"} 浅拷贝
        let copyB = Object.assign({},peopleB)
        peopleB.name = "copyb"
        console.log(copyB)            //{id: "b002", name: "b", sex: "1"} 深拷贝

但是这种方式也存在一个问题,那就是如果源对象的层级超过1层,那么对超过的层级来说,assign实现的也是浅拷贝