js对象拷贝

js的内存结构

原始类型与引用类型

六大原始数据类型:String、Number、Boolean、Null、Undefined、Symbol。对这些简单数据类型(原始值)的访问是按值访问的,因此我们操作的就是存储在变量中的实际值。

引用数据类型:object。保存引用值得变量时按引用访问的,不能直接操作对象所在内存空间,在操作对象时,实际上操作的时对该对象的引用,而非实际的对象本身。

可以认为原始类型,是以键值对的形式存储在栈中。而引用类型的只是将地址存储在栈中,对象本身存储到堆内存中了,我们无法直接访问。

 

  • 对对象复制(浅拷贝)

当我们使用对象拷贝时,如果属性是对象或数组时,这时候我们传递的也只是一个地址。因此子对象在访问该属性时,会根据地址回溯到父对象指向的堆内存中,即父子对象发生了关联,两者的属性值会指向同一内存空间。

var oldobj = {
            name: '小二上酒',
            age: 22,
            arr: [1, 2, 3, { key: '123' }],  //数组测试    
        };
        let newobj = oldobj
        newobj.name = '不喝酒';
        console.log(oldobj);
        console.log(newobj);

 可以看到,浅拷贝只不过是将oldobj地址复制了一份传给newobj,他们实际上指的还是同一个内存中的对象。操作newobj指向的对象时,即也在操作lodobj指向的对象,总而言之对象还是那个对象。

  • 深拷贝

我们不希望父子对象之间产生关联,那么这时候可以用到深拷贝。深拷贝则是,完完全全将栈内存中存放的地址和堆内存中的对象都拷贝(创建)一份,生成一个新的对象。既然属性值类型是数组和或象时只会传地址址,那么我们就用递归来解决这个问题,把父对象中所有属于对象的属性类型都遍历赋给子对象即可

 1 function deepCopy(target={}){
 2             if(typeof target !=='object' || target == null){
 3                 return target
 4             }
 5             let result
 6             if(target instanceof Array){
 7                 result =[]
 8             }else{
 9                 result={}
10             }
11             for(let key in target){
12                 result[key] = deepCopy(target[key])
13             }
14             return result
15         }
16 
17         var oldobj = {
18             name: '小二上酒',
19             age: 22,
20             arr: [1, 2, 3, { key: '123' }],  //数组测试    
21         };
22         let newobj = deepCopy(oldobj)
23         newobj.name = '不喝酒';
24         console.log(oldobj);
25         console.log(newobj);  //

 可以看到这样一来对复制后得到的newobj对象进行修改,就不会影响到oldobj了。

但是这样对一些特殊的情况并不适用。我们还需要解决以下几个问题

  • 对象内属性循环引用自身

  • 相同的引用

  • 其余类型

解决前俩点的完善写法

 1         
 2         function deepCopy(target) {
 3             let copyed_objs = [];//此数组解决了循环引用和相同引用的问题,它存放已经递归到的目标对象 
 4 
 5             function _deepCopy(target) {
 6                 if ((typeof target !== 'object') || !target) {
 7                     return target;
 8                 }
 9                 // 如果当前目标对象和 copyed_objs 中的某个对象相等,那么不对其递归。
10                 for (let i = 0; i < copyed_objs.length; i++) {
11                     if (copyed_objs[i].target === target) {
12                         return copyed_objs[i].copyTarget;
13                     }
14                 }
15                 //处理target是数组的情况 
16                 let result;
17                 if (target instanceof Array) {
18                     result = [];
19                 } else {
20                     result = {}
21                 }
22                 copyed_objs.push({ target: target, copyTarget: result })
23 
24                 // 递归实现深层次拷贝
25                 for(let key in target){
26                     result[key] = _deepCopy(target[key])
27                 }
28                 return result;
29             }
30             return _deepCopy(target);
31         }
32 
33 
34         var oldobj = {
35             name: '小二上酒',
36             age: 22,
37             arr: [1, 2, 3, { key: '123' }],  //数组测试    
38         };
39 
40         oldobj.self = oldobj //循环引用测试
41         oldobj.likes = { book: '剑来' }
42         oldobj.hobby = oldobj.likes //相同引用测试
43         let newobj = deepCopy(oldobj)
44         console.log(oldobj);
45         console.log(newobj);

可以看出,尽管对oldobj添加循环引用自身的属性,以及相同引用的属性,依然可以完成深拷贝。

ps:JSON.sringify 和 JSON.parse 

1         var oldobj = {
2             name: '小二上酒',
3             age: 22,
4             arr: [1, 2, 3, { key: '123' }],  //数组测试    
5         };
6         let newobj = JSON.parse(JSON.stringify(oldobj))
7         console.log(oldobj);
8         console.log(newobj);

这是JS实现深拷贝最简单的方法了,原理就是先将对象转换为字符串,再通过JSON.parse重新建立一个对象。适合项目中快捷使用。 但是这种方法的局限也很多:

  • 不能复制function、正则、Symbol
  • 循环引用报错
  • 相同的引用会被重复复制

学习于:https://juejin.cn/post/6844903620190666759

https://www.cnblogs.com/web1/p/6518482.html

原文地址:https://www.cnblogs.com/zxf906/p/15328239.html