javascript中的深拷贝和浅拷贝<整理>

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_37064409/article/details/79229194

在javascript中对数据的clone分为赋值、深拷贝和浅拷贝。
在经过克隆之后,我们希望clone的对象操作之后与数据源互不干扰。

在分析深拷贝和浅拷贝之前还有一个概念要清楚,就是不同数据类型的clone方式和效果是不同的。

javascript中数据类型

类型 内容
基本数据类型 string、number、boolean、null、undefined
引用类型 array、object、
  1. 基本数据类型保存在 中,在进行浅拷贝操作后,会在内存中开辟一块新的空间,然后把赋过来的值保存于这块新开辟的空间中,存放在栈中的数据互不影响
  2. 引用数据类型保存在 中,在进行浅拷贝操作后,也会开辟一块新的空间,不同的是新的空间保存的是赋值对象现在的地址,由于数据对地址的引用是相同的,所以会联动变化
  3. 所以深拷贝和浅拷贝是针对与引用类型讨论的
  4. 浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

赋值、深拷贝、浅拷贝区别

  • 赋值 : 两个对象经过拷贝之后,有相同的属性,对象也相同
    var obj = 12;
    var newobj = obj;
    obj === newobj  //true
  • 浅拷贝 : 经过拷贝之后虽然有相同属性,但是对象却不同,其内部引用类型数据指向相同内存地址
    var arr = [1,2,3];
    var newarr = arr.slice(0);
    arr === newobj;   //false
    arr.push(12);
    newarr  // [1,2,3,12]
  • 深拷贝 : 经过拷贝之后虽然有相同的属性,但是对象却不同,而且其内部引用类型地址也不相同
    var arr = [1,2,3];
    var newarr = JSON.parse(JSON.stringify(arr));
    arr === newobj;   //false
    arr.push(12);
    newarr  // [1,2,3]

赋值

  • 如果只是想要对数据的复制,那么直接赋值就可以了,这是对数据进行clone的一种方式:
var data = {
    a : "hello",
    b : 12,
    c : [1,2,3],
    d : function(){
        return this.a;
    } 
}

var shallowcopy  = data;
console.log(shallowcopy);   //{a: "hello", b: 12, c: Array(3), d: ƒ};
  • 但是这样会存在一个问题!
data.a += "world";
shallowcopy.a; //"hello"  
data.c.push(12);
shallowcopy.c  // [1, 2, 3, 12]  
  • ① 当data.a变化时,shallowcopy.a不会跟着变化,这是我们想要的结果
  • ② 当data.c变化时, shallowcopy.cc也会跟着联动变化,和我们的需求不太一样。

所以有了浅拷贝的需求,使得拷贝出来的数据更新时和源数据互不干扰。

浅拷贝

方式一: splice()

  • slice() 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。原始数组不会被修改。
var arr = [1,2,3];
var arr1 = arr.slice(0);
arr.push(12);
arr //[1,2,3,12]
arr1 //[1,2,3]
  • 由于slice()是数组的方法,所以这种方式只能对引用类型中的数组使用

方式二:Object.assign()

  • 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
    • Object.assign(target, …sources)
var arr = [1,2,3,4];
var arr2 =Object.assign([],arr); 
arr.a.push(12) 
arr2  //  Object { a: Array [1, 2, 3, 4, 12] }

// 这种浅拷贝方式,可操作所有引用类型的对象。且新对象与原对象互不干扰。但是新的问题出现了:

var obj = {
    a : [1,2,3,4],
    b : [5,6,7,8]   
}

var newobj = Object.assign({},obj);
obj.a.push(12)
newobj.a // [1, 2, 3, 4, 12]

//当对象多嵌套一层数组的时候,对数据进行修改,还是会相互影响,这种情况下只有深拷贝才能解决这种问题了。

深拷贝

方式一: JSON.parse(JSON.stringify(oldobj))

var obj = {
    a : 0,
    b : {c : 1}
}

var newobj = JSON.parse(JSON.stringify(obj));
 obj.a = 4;
 obj.b.c = 4;
 newobj = { a: 0, b: { c: 0}};  //互不干扰
  • 然而这种使用JSON序列化的方法有使用限制
    • 对象里有函数,函数无法被拷贝下来
    • 对象原型链上的属性和方法无法被拷贝下来
var obj = { 
    a: 0, 
    b: function(){
        return this.a
    }
};

var newobj = JSON.parse(JSON.stringify(obj));
newobj // {a: 0}  -- 函数未被拷贝

方式二: 函数方式递归浅拷贝
- 先看函数方式的浅拷贝方法:

    function shallowCopy(source, target = {}) {
        var key;
        for (key in source) {
            if (source.hasOwnProperty(key)) {        // 意思就是__proto__上面的属性,我不拷贝
                target[key] = source[key];
            }
        }
        return target;
    }

    var obj = { a: "hello", b: [1,2,3]};
    var newobj = shallowCopy(obj,{});
    obj.b.push(12);
    console.log(obj !== newobj); //true 不管深拷贝还是浅拷贝obj都是两个不同对象,他们所占内存地址不同
    console.log(obj.b == newobj.b); //true 由于是浅拷贝,所以引用类型所占内存地址是相同的
    console.log(newobj.b);  //[1,2,3,12]  // 相互干扰
  • 先看函数方式的深拷贝方法:
    • 遍历被拷贝对象 (for in)
    • 判断被拷贝对象的数据类型
    • 如果不是对象类型则直接赋值,如果是对象类型则再次调用deepcopy() ③
function deepCopy(source, target) {
    var target = target || {};
    for (var key in source) {
        if (source.hasOwnProperty(key)) { //判断对象自身属性中是否具有指定的属性,过滤__proto__原型上属性
            if (typeof source[key] === 'object') { // ③
                target[key] = (source[key].constructor === Array) ? [] : {}; //判断target[key] 类型是[] / {}
                deepCopy(source[key], target[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

var obj = {
    a: "hello",
    b: [1, 2, 3]
};

var newobj = deepCopy(obj, {});
obj.b.push(12);
console.log(newobj); // Object {a: "hello", b: Array(3)}  互不干扰

jquery - $.extend() 方式进行深、浅拷贝

  • $.extend( [deep ], target, object1 [, objectN ] )
  • jQuery.extend() 函数用于将一个或多个对象的内容合并到目标对象。

第一个参数为是否深度拷贝,默认是浅拷贝
第二个参数是拷贝目标
第三个参数是拷贝的源目标

     $(function () {
         var object1 = {
             apple: 0,
             banana: {
                 weight: 52,
                 price: 100
             },
             cherry: 97
         };
         var newobj = {}
         $.extend(true,newobj, object1);

         object1.banana.price = 120;
         console.log(object1.banana);
         console.log(newobj.banana);
     })

大概总结了
0、 js中值类型和引用类型的存储方式
1、 赋值、深拷贝、浅拷贝的区别
2、 深拷贝的3种方式
3、 浅拷贝的3种方式
以上。

猜你喜欢

转载自blog.csdn.net/weixin_37064409/article/details/79229194