目录

概念:

赋值与浅拷贝:

1.赋值

2.浅拷贝

浅拷贝的实现方式

Object.assign()

Array.prototype.slice()

Array.prototype.concat()

解构

深拷贝的实现方法

JSON.parse(JSON.stringify())

手写递归函数实现深拷贝

总结:


概念:

浅拷贝 :只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用一个内存块

深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象

赋值与浅拷贝:

1.赋值

当我们把一个对象赋值给一个变量的时候,赋值的其实是该对象的栈内存地址而不是堆内存数据,(此处看基本类型和引用类型,对象属于引用类型,值分为栈内存的地址和堆内存中的数据)。也就是赋值前的对象和赋值后的对象两个对象共用一个存储空间(赋值的是栈内存地址,而该地址指向了同一个堆内存空间),所以,无论哪个对象发生改变,改变的都是同一个堆堆内存空间。因此,无论修改哪个对象对另一个对象都是有影响的

var objA ={
        name:'张三',
        age:8,
        pal:['王五','王六','王七']
    }
    var objB = objA
    objB.name = '李四'
    objB.pal[0] = '王麻子'
    console.log('objA.name',objA.name) //李四
    console.log('objB.name',objB.name)//李四
    console.log('objB.pal',objB.pal)//['王麻子','王六','王七']
    console.log('objA.pal',objA.pal)//['王麻子','王六','王七']

总结:赋值后的对象objB改变,原对象objA的值也改变,因为赋值后的对象objB赋值的是原对象objA的栈内存地址,他们指向的是同一个堆内存数据,所以对赋值后的对象objB对数据进行操作会改变公共的堆内存中的数据,所以原对象的值也改变了。

2.浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原对象属性值的一份精准拷贝,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

有点抽象,来看个例子,该例子也是手写浅拷贝的方法

// hasOwnProperty(propertyName)方法 是用来检测属性是否为对象的自有属性,如果是,返回true,否者false; 参数propertyName指要检测的属性名;
// 用法:object.hasOwnProperty(propertyName) // true/false

var obj1 ={
     name:'张三',
     age:8,
     pal:['王五','王六','王七']
    }
    var obj3 = shallowCopy(obj1)
    function shallowCopy (src){
     var newObj = {};
     for(var prop in src ){
         console.log(prop)  // 属性名
         if(src.hasOwnProperty(prop)){
             newObj[prop] = src[prop]
         }
     }
     return newObj
    }
     obj3.name = '李四'
     obj3.pal[0] = '王麻子'
       
    console.log("obj1", obj1); //{age: 8, name: "张三", pal: ['王麻子', '王六', '王七']}
    console.log("obj3", obj3); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}
//obj3改变了基本类型的值name,并没有使原对象obj1的name改变,obj3改变了引用类型的值,导致原对象的值也改变了

总结:

赋值:就是对原对象的栈内存地址进行复制

浅拷贝:是对原对象的属性值进行精准复制,如果原对象的属性值是基本类型,那就是值的引用,所以浅拷贝后修改基本类型不会修改到原对象的,如果原对象属性值是引用类型,那么就是对引用类型属性值的栈内存的复制,所以修改引用类型属性值的时候会修改到原对象。

因此一般对无引用类型的属性的兑现拷贝的时候使用浅拷贝就行,对复杂对象包含引用类型属性的时候使用深拷贝

浅拷贝的实现方式

Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

Object.assign(target, ...sources) 参数: target--->目标对象       source--->源对象       返回值:target,即目标对象

原对象属性中包含引用类型:进行了浅拷贝,拷贝了原对象属性值,所以拷贝的对象改变的时候原对象的引用类型也改变

var obj1 ={
         name:'jack',
         age:25,
         hobby:{
             ball:'tennis'
         }
     }
     let obj2 = Object.assign({},obj1)
     obj2.hobby.ball = 'basketball'
     console.log('obj1',obj1.hobby.ball) //basketball
     console.log('obj2',obj2.hobby.ball) //basketball

原对象属性中不包含引用类型的时候等价于深拷贝,因为不包含引用类型的时候是对属性值的拷贝也就是对基本类的值的复制,也就是值引用,所以对拷贝的对象改变不会影响到原对象,也就等价于深拷贝

var obj1 ={
         name:'jack',
         age:25,
     }
     let obj2 = Object.assign({},obj1)
     obj2.name = 'rose'
     console.log('obj1',obj1.name) //jack
     console.log('obj2',obj2.name) //rose

Array.prototype.slice()

var arr = ['jack',25,{hobby:'tennise'}];
        let arr1 = arr.slice()
        arr1[2].hobby='rose'
        arr1[0]='rose'
        console.log( arr[2].hobby) //rose
        console.log( arr[0]) //jack

Array.prototype.concat()

var arr = ['jack',25,{hobby:'tennise'}];
        let arr1 = arr.concat()
        arr1[2].hobby='rose'
        arr1[0]='rose'
        console.log( arr[2].hobby) //rose
        console.log( arr[0]) //jack

解构

let aa = {
    age: 18,
    name: 'aaa',
    address: {
        city: 'shanghai'
    }
}
 
let bb = {...aa};
bb.address.city = 'shenzhen';
 
console.log(aa.address.city);  // shenzhen

深拷贝的实现方法

JSON.parse(JSON.stringify())

var arr = ['jack',25,{hobby:'tennise'}];
        let arr1 = JSON.parse(JSON.stringify(arr))
        arr1[2].hobby='rose'
        arr1[0]='rose'
        console.log( arr[2].hobby) //tennise
        console.log( arr[0]) //jack

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

缺点:当对象里面有函数的话,深拷贝后,函数会消失

手写递归函数实现深拷贝

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

let deepCopy = function (data) {
        if (typeof (data) != 'object') {
            return data
        } else {
            // 判断是否为数组的方式
            // if (data instanceof Array) {
            //     let result = []
            //     console.log('是数组')
            // }
            // if (data.constructor == Array) {
            //     let result = []
            //     console.log('是数组')
            // }
            // if (Array.isArray(data)){
            //     let result = []
            //     console.log('是数组')
            // }
            let result = Array.isArray(data) ? [] : {}
            // 如果是数组,返回一个数组,如果哦是一个对象,返回的是一个对象
            for (key in data) {
                // if (typeof (data[key]) != 'object') {
                //     // 如果条件成立,那么就是一个普通数据类型
                //     result[key] = data[key]
                // } else {
                //     // 否则就是复杂数据类型
                //     result[key] = deepCopy(data[key])
                // }
                result[key] = typeof (data[key]) != 'object' ? data[key] : deepCopy(data[key])
            }
            return result
        }
    }
    window.deepCopyfn = deepCopy

案例:

var obj = {   //原数据,包含字符串、对象、函数、数组等不同的类型
        name: "test",
        main: {
            a: 1,
            b: 2
        },
        fn: function () {

        },
        friends: [1, 2, 3, [22, 33]]
    }

    function copy(obj) {
        let newobj = null;   //声明一个变量用来储存拷贝之后的内容

        //判断数据类型是否是复杂类型,如果是则调用自己,再次循环,如果不是,直接赋值即可,
        //由于null不可以循环但类型又是object,所以这个需要对null进行判断
        if (typeof (obj) == 'object' && obj !== null) {

            //声明一个变量用以储存拷贝出来的值,根据参数的具体数据类型声明不同的类型来储存
            newobj = obj instanceof Array ? [] : {};

            //循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数
            for (var i in obj) {
                newobj[i] = copy(obj[i])
            }
        } else {
            newobj = obj
        }
        console.log('77', newobj)
        return newobj;    //函数必须有返回值,否则结构为undefined
    }
    var obj2 = copy(obj)
    obj2.name = '修改成功'
    obj2.main.a = 100
    console.log(obj)
    console.log(obj2)

什么时候使用深拷贝?

我们在希望改变新的数组(对象)的时候,不改变原数组(对象)

使用深拷贝的注意事项?

如果对象比较大,层级也比较多,深拷贝会带来性能上的问题。所以在遇到需要使用深拷贝,考虑有没有其他的方案,实际应用中主要还是以浅拷贝为主

总结:

浅拷贝将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用(拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响)

深拷贝是将原对象的各个属性的“值”逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深拷贝的方法递归复制到新对象上(注意拷贝的“值”而不是“引用”)