深拷贝与浅拷贝深入总结分析

栏目: JavaScript · 发布时间: 5年前

内容简介:ECMAScript 中数据类型可分为:不同类型的存储方式:不同类型的复制方式:

ECMAScript 中数据类型可分为:

  • 基本类型:String、Number、Boolean、Symbol、null、undefined
  • 引用类型:Object、Array、Date、RegExp、Function等

不同类型的存储方式:

  • 基本类型:基本类型值在内存中占据固定大小,保存在栈内存中
  • 引用类型:引用类型的值是对象,保存在堆内存中,而栈内存保存的对象的变量标识符和对象存储在堆内存中的存储地址

不同类型的复制方式:

  • 基本类型:从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量
let foo = 1;
let bar = foo;
console.log(foo === bar); // -> true

// 修改foo变量的值并不会影响bar变量的值
let foo = 233;
console.log(foo); // -> 233
console.log(bar); // -> 1
复制代码
  • 引用类型:从一个变量向另一个新变量复制引用类型的值,其实复制的是指针,最终两个变量最终都指向同一个对象
let foo = {
  name: 'leeper',
  age: 20
}
let bar = foo;
console.log(foo === bar); // -> true

// 改变foo变量的值会影响bar变量的值
foo.age = 19;
console.log(foo); // -> {name: 'leeper', age: 19}
console.log(bar); // -> {name: 'leeper', age: 19}
复制代码

二、拷贝

  • 浅拷贝(一层):仅仅是复制了引用,彼此之间的操作会互相影响
  • 深拷贝(多层):在堆中重新分配内存,不同的地址,相同的值,互不影响

首先深复制和浅复制只针对像 Object, Array 这样的复杂对象的。简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

2.1 浅拷贝

2.1.1 Object.assign

// 使用Object.assign解决
// 使用Object.assign(),你就可以没有继承就能获得另一个对象的所有属性,快捷好用。 
// Object.assign 方法只复制源对象中可枚举的属性和对象自身的属性。
let obj = { a:1, arr:[2,3]};
let res = Object.assign({}, obj)

console.log(res.arr === obj.arr); // true,指向同一个引用
console.log(res === obj); // false
复制代码

2.1.2 扩展运算符

// 使用扩展运算符(…)来解决
let obj = { a:1, arr:[2,3]};
let res = {...obj};

console.log(res.arr === obj.arr); // true,指向同一个引用
console.log(res === obj); // false
复制代码

2.1.3 浅拷贝原生实现

const shallowCopy = (sourceObj) => {
  if (typeof sourceObj !== 'object') return;
  let newObj = sourceObj instanceof Array ? [] : {};
  
  for(let key in sourceObj){ 
    if(sourceObj.hasOwnProperty(key)) {
     //只复制元素自身的属性,不复制原型链上的
      if(!(key in newObj)){
        newObj[key] = sourceObj[key];
      }
    }
  }
  return newObj;
}

let obj = { a:1, arr:[2,3]};
let res = shallowCopy(obj);
console.log(res.arr === obj.arr); // true,指向同一个引用
console.log(res.a === obj.a); // false
复制代码

因为浅复制只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript 存储对象都是存地址的,所以浅复制会导致 obj.arr 和 shallowObj.arr 指向同一块内存地址,大概的示意图如下。

深拷贝与浅拷贝深入总结分析

2.2 深拷贝

2.2.1 JSON 序列化

  • JSON.stringify():把一个 js 对象序列化为一个 JSON 字符串
  • JSON.parse():把 JSON 字符串反序列化为一个 js 对象
// 可以通过 JSON.parse(JSON.stringify(object)) 来解决
let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
复制代码

但是该方法也是有局限性的:

  1. 会忽略 undefined
  2. 不能序列化函数(会忽略函数)
  3. 不能解决循环引用的对象

并且该函数是内置函数中处理深拷贝性能最快的。当然如果你的数据中含有以上三种情况下,可以使用 lodash 的深拷贝函数。

2.2.2 深拷贝的原生实现

const deepCopy = (sourceObj) => {
  if(typeof sourceObj !== 'object') return;
  let newObj = sourceObj instanceof Array ? [] : {};
  
  for(let key in sourceObj){
    if(sourceObj.hasOwnProperty(key)) {
     //只复制元素自身的属性,不复制原型链上的
      newObj[key] = (typeof sourceObj[key] === 'object' ? deepCopy(sourceObj[key]) : sourceObj[key]);
     }
   }
   return newObj;
}

let obj = { a:1, arr:[2,3]};
let res = deepCopy(obj);
console.log(res.arr === obj.arr); // false,指向不同的引用
console.log(res === obj); // false
复制代码

而深复制则不同,它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。这就不会存在上面 obj 和 shallowObj 的 arr 属性指向同一个对象的问题。

2.3 Array 中的拷贝

2.3.1 Array.prototype.slice()

let a = [1, 2, 3, 4];
let b = a.slice();
console.log(a === b); // -> false(当引用类型时需要满足值相等和引用相等才为 true)

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
复制代码

2.3.2 Array.prototype.concat()

let a = [1, 2, 3, 4];
let b = a.concat();
console.log(a === b); // -> false

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
复制代码

2.3.3 综上

看起来 Array 的 slice(), concat() 似乎是 深拷贝 ,再接着看就知道它们究竟是深拷贝还是浅拷贝:

let a = [[1, 2], 3, 4];
let b = a.slice();
console.log(a === b); // -> false

a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
复制代码

同样,对于concat()也进行验证:

![](https://user-gold-cdn.xitu.io/2019/2/22/169156d156f7c222?w=720&h=270&f=jpeg&s=15463)
let a = [[1, 2], 3, 4];
let b = a.concat();
console.log(a === b); // -> false

a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
复制代码

综上, Array 的 slice 和 concat 方法并不是真正的深拷贝,对于 Array 的第一层的元素是深拷贝,而 Array 的第二层 slice 和 concat 方法是复制引用。所以,Array 的 slice 和 concat 方法都是浅拷贝。

深拷贝与浅拷贝深入总结分析

以上所述就是小编给大家介绍的《深拷贝与浅拷贝深入总结分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

单元测试之道Java版

单元测试之道Java版

David Thomas、Andrew Hunt / 陈伟柱、陶文 / 电子工业 / 2005-1 / 25.00元

程序员修炼三部曲丛书包含了四本书,介绍了每个注重实效的程序员和成功团队所必备的一些工具。 注重实效的程序员都会利用反馈来指导开发,并驱动个人的开发流程。编码的时候,最有用的反馈来自于“单元测试”。 为了测试一座桥梁,不会只在晴朗的天气,开一辆汽车从桥中间穿过,就认为已经完成了对桥梁的测试。然而许多程序员却正在使用这种测试方法——把这种一次顺利通过称为“测试”。事实上,注重实效的程序员应......一起来看看 《单元测试之道Java版》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具