JavaScript必学之数据深浅拷贝

js 对象赋值之浅拷贝深拷贝在实际中应用

我们有时候有这样的需求,需要将一个值赋值给另外一个变量。例如:

var a = 10;
var b = a;

在这里我们首先需要知道基本类型和引用类型的区别。js 有 6 种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol。引用类型有 Array、Date、Function、(Boolean、String…),他们都是复杂数据类型 Object 的实例对象。基本数据类型是直接存放于栈内存中,可直接访问。引用类型存放在堆内存中保存的是一个指针,当直接赋值时,会指向同一个内存空间。

首先我们看基本数据类型赋值前后操作:

var a = 10;
var b = a;

b = 5;
console.log(b); // 5
console.log(a); // 10

从这里可以看出虽然把 a 的值赋值给了 b,但是对 b 修改时并不会影响到 a。也就是说,他们保存在单独的空间里。

再看引用类型赋值前后操作:

var obj = {
  a: "111",
  b: "222"
};
var newObj = obj;

newObj.a = "aaa";

console.log(obj); // { a: 'aaa', b: '222' }
console.log(newObj); // { a: 'aaa', b: '222' }

从这里可以看出,修改赋值后的 newObj,原来的 obj 也发生了改变。可见他们指向的是同一片内存地址。

在实际应用中经常会有赋值的操作,如果不想影响到之前的数据就需要使用浅拷贝或者深拷贝。

1.浅拷贝
除了上面的直接赋值,还可以使用 Object.assign()进行浅拷贝。
例如:

var obj = {
  a: "111",
  b: "222"
};
var assObj = Object.assign({}, obj);

assObj.a = "aaa";

console.log(obj); // { a: '111', b: '222' }
console.log(assObj); // { a: 'aaa', b: '222' }

对 Object.assign()拷贝后的数据进行更改,不会改变原来对象里的值。那么为什么它是浅拷贝而不是深拷贝呢,因为 Object.assign()只能深拷贝第一层数据。
例如:

var obj = {
  aaa: "123",
  bbb: "456",
  ccc: {
    c: "666",
    oo: {
      sss: "000"
    }
  }
};

var assObj = Object.assign({}, obj);

assObj.bbb = "bbbb";
assObj.ccc.c = "cccc";
assObj.ccc.oo.sss = "sss";
console.log(obj); // { aaa: '123', bbb: '456', ccc: { c: 'cccc', oo: { sss: 'sss' } } }
console.log(assObj); // { aaa: '123',bbb: 'bbbb',ccc: { c: 'cccc', oo: { sss: 'sss' } } }

上例可以看出对拷贝后的数据进行修改,虽然第一层数据源对象没有被修改,但是一层以外的数据被修改了。所以 Object.assign()只能深度复制一层对象数据。

使用 JSON.parse(JSON.stringify(obj))进行深拷贝。

var obj = {
  aaa: "123",
  bbb: "456",
  ccc: {
    c: "666",
    oo: {
      sss: "000"
    }
  }
};
var jsonObj = JSON.parse(JSON.stringify(obj));
jsonObj.bbb = "bbbb";
jsonObj.ccc.c = "ccc";
jsonObj.ccc.oo.sss = "sss";
console.log(obj); //{ aaa: '123', bbb: '456', ccc: { c: '666', oo: { sss: '000' } } }
console.log(jsonObj); // { aaa: '123',bbb: 'bbbb',ccc: { c: 'ccc', oo: { sss: 'sss' } } }

上例可以看到使用 JSON.parse(JSON.stringify())对进行拷贝的对象修改,所有层级的对象都没有影响到源对象。所以可以说 JSON.parse(JSON.stringify())是深拷贝方法。

为什么我描述为“可以说”,因为对象里的值可以为任意引用类型。比如:Array、Date、Function、Boolean、String…

var obj = {
  aaa: "123",
  bbb: "456",
  ccc: {
    c: "666",
    oo: {
      sss: "000"
    }
  },
  foo: function() {
    console.log("foo");
  },
  date: new Date(),
  boolean: false,
  arr: [1, 2, 3, 4, 5, 6, 7],
  exp: new RegExp("e")
};

var jsonObj = JSON.parse(JSON.stringify(obj));

jsonObj.bbb = "bbbb";
jsonObj.ccc.c = "ccc";
jsonObj.ccc.oo.sss = "sss";
console.log(obj);
/*  { aaa: '123',
    bbb: '456',
    ccc: { c: '666', oo: { sss: '000' } },
    foo: [Function: foo],
    date: 2019-05-28T08:42:05.454Z,
    boolean: false,
    arr: [ 1, 2, 3, 4, 5, 6, 7 ],
    exp: /e/
    }
*/
console.log(jsonObj);
/* { aaa: '123',
    bbb: 'bbbb',
    ccc: { c: 'ccc', oo: { sss: 'sss' } },
    date: '2019-05-28T08:42:05.454Z',
    boolean: false,
    arr: [ 1, 2, 3, 4, 5, 6, 7 ],
    exp: {}
    }
*/

从上例可以看出,虽然对拷贝后的值改变对象属性值源对象内值没变,但是源对象里的 Function 类型和正则表达式数据却没有拷贝过来。

要达到真正意义上的深拷贝,需要手写递归函数。

function deepClone(obj) {
  if (!obj && typeof obj !== "object") {
    throw new Error("error arguments");
  }

  var objClone = Array.isArray(obj)
    ? []
    : Object.prototype.toString.call(obj) === "[object Object]"
    ? {}
    : obj.valueOf();

  if (obj && typeof obj === "object") {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] && typeof obj[key] === "object") {
          objClone[key] = deepClone(obj[key]);
        } else {
          objClone[key] = obj[key];
        }
      }
    }
  }
  return objClone;
}

var deep = deepClone1(obj);
deep.bbb = "bbbb";
deep.ccc.c = "ccc";
deep.date = new Date("2018-10-09");

console.log(obj);
/*
{ aaa: '123',
  bbb: '456',
  ccc: { c: '666', oo: { sss: '000' } },
  foo: [Function: foo],
  date: 2019-05-29T02:43:12.332Z,
  boolean: false,
  arr: [ 1, 2, 3, 4, 5, 6, 7 ],
  exp: /e/ }
*/
console.log(deep);
/*
{ aaa: '123',
  bbb: 'bbbb',
  ccc: { c: 'ccc', oo: { sss: '000' } },
  foo: [Function: foo],
  date: 2018-10-09T00:00:00.000Z,
  boolean: false,
  arr: [ 1, 2, 3, 4, 5, 6, 7 ],
  exp: /e/ }
*/

总结:实际应用中会有很多地方需要对对象进行拷贝。这时候就要看情况使用浅拷贝还是深拷贝。在上文中,能够了解到的知识点有浅拷贝深拷贝以及手写深拷贝方法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值