JS深拷贝与浅拷贝

本文介绍了JavaScript中的深拷贝和浅拷贝概念,重点讲述了深拷贝的实现方式,包括JSON.parse(JSON.stringify())、递归方法、jQuery的$.extend()以及lodash的_.cloneDeep(),并分析了各种方法的局限性和适用场景。同时,浅拷贝的实现方式如Object.assign()、Array.prototype.concat()、Array.prototype.slice()和引用复制也进行了探讨。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.基本概念

  • (1)深拷贝和浅拷贝针对的是引用类型。基本类型的名值存储在栈中,当复制时,栈内存会开辟一个栈内存。所以二者修改时,彼此不会影响

  • (2)浅拷贝复制的是指向对象的指针,并没有开辟新的栈内存,原对象和新对象还是共享同一块内存,修改新对象自然会影响原对象。

深拷贝会开辟新的栈内存,原对象和新对象不共享同一块内存,修改新对象不会影响到原对象。
在这里插入图片描述

2.实现方式

2.1深拷贝的实现方式
  • 2.1.1 JSON.parse(JSON.stringify())

说明:
1.用JSON.stringify将对象转成JSON字符串,再用**JSON.parse()**把字符串解析成对象。一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

2.这种方法虽然可以实现数组或对象的深拷贝,但不能处理函数。因为JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数

示例:

var iniArr = [1,2,{name:"peter"},function(){}];
var newArr = JSON.parse(JSON.stringify(iniArr));
newArr[1] = 8;
newArr[2].name = "lily";
console.log('JSON.parse(Json.stringfiy()):iniArr,newArr',iniArr,newArr

打印结果:
在这里插入图片描述

  • 2.1.2递归方法

原理:

遍历对象、数组,直到里边都是基本数据类型,然后再去复制,即可实现深度拷贝。

示例:

 var iniObj = {
      a: 1,
      b: [1, 2, 3],
      c: {
          d: {
              e: 4
          }
      },
      fun: function(){
          return 999;
      }
  };

  function deepClone(x) {
      if (x.constructor === Object) {
          var obj = {}
          for (var k in x) {
              obj[k] = deepClone(x[k])
          }
          return obj
      } else if (x.constructor === Array) {
          var arr = [];
          for (var i = 0; i < x.length; i++) {
              arr[i] = deepClone(x[i])
          }
          return arr
      } else {
          return x
      }
  }
  var newObj = deepClone(iniObj)

  newObj.b[0] = 6;
  newObj.c.d.e = 8;
  console.log('recursive deepClone,iniObj,newObj',iniObj,newObj);

打印结果:
在这里插入图片描述
该方法的局限性:

若属性值是函数undefinedsymbol时,会忽略掉

示例:

var iniObj = {
      name:'peter',
      age:undefined,
      school:function(){}
  }
  var newObj = JSON.parse(JSON.stringify(iniObj));
  console.log('newObj',newObj) 

打印结果:
在这里插入图片描述

尽管有上述局限,不过通常情况下,该函数还是可以解决大部分问题的。

  • 2.1.3jQuery的$.extend()

可以用jQuery的$.extend()实现深拷贝:
jQuery.extend([deep], target, object1, [objectN])

示例:

var iniObj = {
    a: 1,
    b: [1, 2, 3],
    c: {
           d: {
                  e: 4
           }
    },
};
var newObj = $.extend(true, {}, iniObj);
newObj.b[0] = 6;
newObj.c.d.e = 8;
console.log('$.extend deepClone,iniObj,newObj',iniObj,newObj);

打印结果:
在这里插入图片描述

  • 2.1.4 lodash的_.cloneDeep()

可以用js库lodash中的_.cloneDeep()实现深拷贝:

示例:

  var iniObj = {
      a: 1,
      b: [1, 2, 3],
      c: {
          d: {
              e: 4
          }
      }
  };
  var newObj = _.cloneDeep(iniObj);
  newObj.b[0] = 6;
  newObj.c.d.e = 8;
  console.log('lodash deepClone,iniObj,newObj',iniObj,newObj);

在这里插入图片描述
2.2浅拷贝的实现方式

  • 2.2.1 Object.assign()

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

Object.assign()只会拷贝所有的属性值到新的对象中,如果属性值是基本类型,则修改其中一个对象,不会影响另一个。而如果属性值是对象的话,拷贝的是对象的引用,而不是对象本身。所以该方法是浅拷贝。

分两种情况:

1.属性值是基本类型,修改其中一个对象,不会影响另一个对象的该属性:

var iniObj = {
    name: "peter",
 };
 var newObj = Object.assign({}, iniObj);
 newObj.name = "lily";
 console.log('iniObj.name', iniObj.name); //peter

2.属性值是引用类型,由于拷贝的是对象的引用,共享相同的地址:

var iniObj = {
    info: {
        name: "peter",
        age: 8
    }
};
var newObj = Object.assign({}, iniObj);
newObj.info.name = "lily";
console.log('iniObj.info.name', iniObj.info.name); //lily
  • 2.2.2 Array.prototype.concat()

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。注意这里分两种情况:

(1)对象引用(而不是实际对象):concat将对象引用复制到新数组中。原始数组和新数组都引用相同的对象。也就是说,如果引用的对象被修改,则更改对于新数组和原始数组都是可见的。这包括也是数组的数组参数的元素。

(2)数据类型如字符串,数字和布尔(不是String,Number 和 Boolean 对象):concat将字符串和数字的值复制到新数组中。

示例:

var iniArr = [1,2,{name:"peter"}];
var newArr = iniArr.concat();
newArr[1] = 8;
newArr[2].name = "lily";
console.log('iniArr',iniArr);

打印结果:
在这里插入图片描述

  • 2.2.3 Array.prototype.slice()

slice()方法返回一个新的数组对象,这一对象是一个由 begin和end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。

也分两种情况:
1.如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

2.对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

示例:

var iniArr = [1,2,{name:"peter"}];
var newArr = iniArr.slice();
newArr[1] = 8;
newArr[2].name = "lily";
console.log('iniArr',iniArr);

打印结果:
在这里插入图片描述
和上述concat()一样,修改新数组的对象会影响到原数组,修改新数组的基本类型值不会影响到原数组:

  • 2.2.4 引用复制
    示例:
  var iniObj = {
      gender: "male",
      info: {
          name: "peter",
          age: 8
      },
      test: [1, [2, 3]]
  };

  function shallowCopy(copyObj) {
      var obj = {};
      for (var i in copyObj) {
          obj[i] = copyObj[i];
      }
      return obj;
  }

  var newObj = shallowCopy(iniObj);
  newObj.gender = "female";
  newObj.info.name = "lily";
  newObj.test[1] = [4, 5];
  console.log('iniObj', iniObj);
  console.log('newObj', newObj)    

打印结果:
在这里插入图片描述

3.赋值

可以和上述2.2.4浅拷贝的示例结果做比较,示例:

 var iniObj = {
      gender: "male",
      info: {
          name: "peter",
          age: 8
      },
      test: [1, [2, 3]]
  };

  var newObj = iniObj;
  newObj.gender = "female";
  newObj.info.name = "lily";
  newObj.test[1] = [4, 5];
  console.log('iniObj', iniObj);
  console.log('newObj', newObj)   

打印结果:
在这里插入图片描述

说明把一个对象赋值给另一个变量时,并没有创建一个新对象,而是把原对象在栈中的地址(而非栈中的数据)赋给了新对象,即赋的是原对象在栈中的地址,原对象和新对象指向的是同一个地址。因此,两个对象的联动的,修改其中一个,另一个也会改变。包括里面所有的属性,不论是基本类型的数据,还是对象引用。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值