浅复制(浅拷贝)和深复制(深拷贝)

js存储机制

JS中对象分为基本类型和复合(引用)类型,基本类型存放在栈内存,复合(引用)类型存放在堆内存。
堆内存用于存放由new创建的对象,栈内存存放一些基本类型的变量和对象的引用变量。

1.基本类型
如 变量,在创建时会直接复制一份。

var a = 'hello men'
var b = a
b = "good"
console.log(a) // hello men
console.log(b) // good

当b改变时,不会影响初始变量a

2.复合类型
如数组、对象等,js就会做引用,原对象和拷贝对象会做关联

var arr1 = [1, 2, 3, 4]
var arr2 = arr1
arr2[4] = 9
console.log(arr1)  // [1,2,3,4,9]
console.log(arr2)  // [1,2,3,4,9]

arr2改变时arr1也随之改变,这里举了数组的例子,对象也类似。

浅与深的区别

为了让拷贝对象 和 原对象完全脱离关系,我们需要用到浅拷贝、深拷贝这两种方法。

浅拷贝只能复制根属性,做不到真正的全拷贝。
深拷贝能实现真正的全拷贝,与原对象分清界限。

浅拷贝

我们先从简单的入手,浅浅的拷贝一个数组。

  • 方法1
var array1 = ['a', 'b', 'c']
var array2 = array1.slice() // 单个复制数组元素
array2[3] = 'h'
console.log(array1) // ['a','b','c']
console.log(array2) // ['a','b','c', 'h']
  • 方法2
var arr1 = [1, 2, 3]
var arr2 = []
var copy = (arr1, arr2) => {
    arr1.forEach((element, index) => {
        arr2[index] = element
    })
    return arr2
}
arr2 = copy(arr1, arr2)
arr2[3] = 6
console.log(arr2) // [ 1, 2, 3, 6 ]
console.log(arr1) // [ 1, 2, 3 ]

array2只改变自己,不影响他人,是个好同志。
现在难度升级,我们在数组里加入了对象。

var array1 = ['a', 'b', 'c', {
    "name": 'leo'
}]
var array2 = array1.slice()
array2[3].name = 'mark'
console.log(array1) // ['a','b','c',{name: 'mark'}]
console.log(array2) // ['a','b','c',{name: 'mark'}]

这时候改变array2,array1也跟着变了,哦NO!

深拷贝

现在是该大英雄出场了,“深拷贝!深拷贝!”
不啰嗦,直接上代码。

// 创建了 一个带数组和对象的元素 (这还是比较简单的对象结构)
var array1 = ['a', 'b', 'c', { "name": 'leo'}, [8, 9]]
var array2 = []
// 创建拷贝方法 
var copy = (obj1, obj2) => {
    obj1.forEach((item, index) => {
               // 判断该索引值是否为数组
        if (obj1[index].constructor === Array) {
            obj2[index] = []
            obj1[index].forEach((subItem, subIndex) => {
                obj2[index][subIndex] = subItem
            })
                // 判断该索引值是否为对象
        } else if (obj1[index] && typeof obj1[index] === 'object') {
            obj2[index] = {}
            for (let element in obj1[index]) {
                obj2[index][element] = obj1[index][element]
            }
                // 不是对象,说明是属性,直接赋值
        } else {
            obj2[index] = item
        }
    })
    return array2
}

var array2 = copy(array1, array2)
// 改变对象的属性值
array2[3].name = 'mark'
// 改变数组的值
array2[4][1] = '5'
console.log(array2) // [ 'a', 'b', 'c', { name: 'mark' }, [ 8, '5' ] ] 
console.log(array1) // [ 'a', 'b', 'c', { name: 'leo' }, [ 8, 9 ] ] 

恭喜你,我们向要的都实现了!

深不可测的深拷贝 (递归)

有些对象是后台传给我们的,拿到之前不知道里面是什么结构。这种数据着么去拷贝呢?
是时候展示真正的技术了, 那就是递归
递,层层递进。归,归去来兮。
总结下来就是...还是不解释了。
看代码:

// 创建一个深层嵌套的对象
var json1 = {
    "name": "leo",
    "age": 20,
    "child": [{
            "eye": "blue",
            "child": [{
                "photo": "nice"
            }, {
                "photo": "beautiful"
            }]
        },
        {
            "eye": "red",
            "child": []
        }
    ]
}

var json2 = {}
function copy(obj1, obj2) {
    for (var name in obj1) {
        if (typeof obj1[name] === "object") {
            obj2[name] = (obj1[name].constructor === Array) ? [] : {}
           // 递归时改变当前参数位置,
           // 举例:当前name为child时,copy中的参数被替换为 (obj1.child, obj2.child)
            copy(obj1[name], obj2[name])
        } else {
            obj2[name] = obj1[name]
        }
    }
    return obj2
}

json2 = copy(json1, json2)
json2.child[0].eye = 'green'
console.log(json1) // child: [ { eye: 'blue', child: [Object] }
console.log(json2) // child: [ { eye: 'green', child: [Object] }

最简单的深拷贝

先把对象使用JSON.stringify()转为字符串,再赋值给另外一个变量,然后使用JSON.parse()转回来即可。

let a = {
  a1: 1,
  a2: '2',
  a3: [1, 2, 3, 4, 5, 6],
  a4: {
    deep1: 1,
    deep2: 2
  }
}

let b = JSON.parse(JSON.stringify(a))
console.log(b)
a.a4.deep1 = 99
console.log(a)  // a的属性值变动了
console.log(b) // b没变

new

顺带提下,用new 创建的对象,都是引用原对象的。new常常和构造函数一起出现,用于创建对象的继承关系。
我们应尽量避免引用类型的直接拷贝,这样会改变原对象的属性,在协同工作时会产生不可预期的错误。
可以用新的方法Object.create()来创建,或者定义一个空的 F(){} 构造函数做衔接。

参考: https://www.jianshu.com/p/0d7bd31ccf43

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容