深拷贝和浅拷贝的区别

在讲深拷贝和浅拷贝之前要先了解一下不同数据类型的存储方式

数据的存储方式

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和引用数据类型(Object, Array)
基本数据类型存储:数据直接存储在栈中
引用数据类型的存储:真实的数据存放在堆内存里,在栈中存储的是对象的指针

浅拷贝与深拷贝定义区别

浅拷贝与深拷贝是针对引用类型值的拷贝
浅拷贝:仅仅复制对象的引用,而不是对象本身,新旧对象还是共享同一块内存,
深拷贝:将对象完整地拷贝出来,开辟新的内存来存放新对象,新对象跟原对象完全独立,不共享内存
区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制,深拷贝相比于浅拷贝速度较慢并且花销较大。

浅拷贝的实现

1.“=” 赋值
最基础的赋值方法,只是将对象的引用赋值。
2. Object.assign 拷贝的是对象的属性的引用,而不是对象本身
它不会拷贝对象的继承属性
它不会拷贝对象的不可枚举属性

let target = {};
let source = { a: { b: 2 }, c: 2, sym: Symbol(1) };
Object.defineProperty(source, 'innumerable' ,{
    value: '不可枚举属性',
    enumerable: false
});
Object.assign(target, source);
console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }
source.a.b = 10;
source.c = 10;
console.log(source);             // { a: { b: 10 }, c: 10, sym: Symbol(1), innumerable: '不可枚举属性' }
console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }

  1. Object.create(obj1) 只拷贝一层属性
  2. 扩展运算符
let source = { a: { b: 2 }, c: 2, sym: Symbol(1) };
Object.defineProperty(source, 'innumerable' ,{
    value: '不可枚举属性',
    enumerable: false
});
let target = {...source};
console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }
source.a.b = 10;
source.c = 10;
console.log(source);             // { a: { b: 10 }, c: 10, sym: Symbol(1), innumerable: '不可枚举属性' }
console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }
  1. slice和concat
    slice也只能是处理一层的深度拷贝。如果存在对象的嵌套,它也无能为力
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr);            // [ 1, 2, { val: 1000 } ]

concat

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);              // [ 1, 2, 3 ]
console.log(newArr);           // [ 1, 100, 3 ]
  1. 手动浅拷贝(只拷贝第一层属性)
const shallowCopy = target => {
  if (typeof target === 'object' && target !== null) {
  	const cloneTarget = Array.isArray(target) ? [] : {};
    for (let key in target) {
      if (target.hasOwnProperty(key)) {
        cloneTarget[key] = target[key];
      }
    }
    return cloneTarget;
  } else {
  	return target;
  }
};

深拷贝的实现

  1. 使用JSON.stringify和JSON.parse实现深拷贝
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);                     // { body: { a: 10 } } <-- 沒被改到
console.log(obj2);                     // { body: { a: 20 } }
console.log(obj1 === obj2);            // false
console.log(obj1.body === obj2.body);  // false

缺陷:
1. 它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;
2. 只能处理的对象只有 Number, String, Boolean, Array,扁平对象,无法转换函数、undefined、symbol数据类型拷贝
3. 对象中的值为Date时,会变成字符串。
4. 无法拷贝不可枚举的属性。
5. 拷贝的对象中的值为RegExp时,会变成空对象。
6. 拷贝的对象中的值为NaN、Infinity或者-Infinity时,会变成null
7. 无法拷贝对象的循环应用,即对象成环(obj[key] = obj);

  1. 递归深拷贝
function deepCopy(obj1) {
//  创建一个空的对象或数组
     var obj2 = Array.isArray(obj1) ? [] : {};
     if (obj1 && typeof obj1 === "object") {
     for (var i in obj1) {
	let prop = obj1[i]; 
	// 避免相互引用造成死循环,如obj1.a=obj
          if (prop == obj1) {
         continue;
       }
         if (obj1.hasOwnProperty(i)) {
         // 如果子属性为引用数据类型,递归复制
         if (prop && typeof prop === "object") {
           obj2[i] = deepCopy(prop);
         } else {
           // 如果是基本数据类型,只是简单的复制
           obj2[i] = prop;
         }
       }
     }
   }
   return obj2;
 }

缺陷:
当遇到两个互相引用的对象,会出现死循环的情况,
这个深拷贝方法不可拷贝不可枚举的属性
这个方法对于Date、RegExp不能够正确的拷贝
无法拷贝对象的循环应用,即对象成环(obj[key] = obj);

JSON.stringify和递归深拷贝结果对比

function Obj() {
  this.func = function() { alert(1); };
  this.obj = { a: 1 };
  this.arr = [1, 2, 3];
  this.reg = /123/;
  this.und = undefined;
  this.date = new Date(0);
  this.NaN = NaN;
  this.Infinity = Infinity;
  this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{ 
  enumerable: false,
  value: 'innumerable'
});
console.log('obj1', obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
let obj3 = deepClone(obj1);
console.log('obj2', obj2);
console.log('obj3', obj3);

  1. 改进版深度拷贝

要更好的实现一个深拷贝的方法,那么必须要了解以下四点:
对于不可枚举的属性,我们采用Reflect.ownKeys方法。
当参数为Date、RegExp类型时,则直接生成一个新的实例返回。
利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性。
顺便结合Object.create创建对象,并继承传入原对象的原型链。
利用WeakMap类型作为哈希表,作为检测循环引用。如果存在循环,则引用直接返回WeakMap存储的值。

const isComplexDataType = obj => (typeof obj === "object" || typeof obj === "function") && obj !== null;
const deepClone = function(origin, hash = new WeakMap()) {
  if (origin.constructor === Date) return new Date(origin);
  if (origin.constructor === RegExp) return new RegExp(origin);
  // 如果循环引用就用WeakMap解决
  if (hash.has(origin)) return hash.get(origin);
  // 遍历传入参数所有键的特性
  let allDesc = Object.getOwnPropertyDescriptors(origin);
  // 继承原型链
  let cloneObj = Object.create(Object.getPrototypeOf(origin), allDesc);
  hash.set(origin, cloneObj);
  for (let key of Reflect.ownKeys(origin)) {
    cloneObj[key] = (isComplexDataType(origin[key]) && typeof origin[key] !== "function" ? deepClone(origin[key], hash) : origin[key];
  }
  return cloneObj;
}
  1. jquery 提供一个$.extend可以用来做深拷贝
var obj2 = $.extend(true, {}, obj1);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值