深浅拷贝的学习

浅拷贝

自己创建一个新的对象,来接受要重新赋值或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象

方案一:object.assign https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

Attention:

object.assign不会拷贝对象的继承属性;

object.assign不会拷贝对象的不可枚举的属性;

可以拷贝 Symbol 类型的属性。

方案二:扩展运算符 { …obj } https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax

var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4);

// arr2 此时变成 [1, 2, 3, 4]
// arr 不受影响

方案三:concat 拷贝数组 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/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 ]

方案四:slice 拷贝数组 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr);  //[ 1, 2, { val: 1000 } ]

深拷贝

将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

方案一: JSON.stringfy - JSON.parse()

let obj1 = { a:1, b:[1,2,3] }

let str = JSON.stringify(obj1)let obj2 = JSON.parse(str);

console.log(obj2);   //{a:1,b:[1,2,3]} 

obj1.a = 2;

obj1.b.push(4);

console.log(obj1);   //{a:2,b:[1,2,3,4]}

console.log(obj2);   //{a:1,b:[1,2,3]}
function Obj() { 
  this.func = function () { alert(1) }; 

  this.obj = {a:1};

  this.arr = [1,2,3];

  this.und = undefined; 

  this.reg = /123/; 

  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);

console.log('obj2',obj2);

Attention:

  1. 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
  2. 拷贝 Date 引用类型会变成字符串;
  3. 无法拷贝不可枚举的属性;
  4. 无法拷贝对象的原型链;
  5. 拷贝 RegExp 引用类型会变成空对象;
  6. 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
  7. 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。

方案二: 递归

let obj1 = {

  a:{

    b:1

  }

}

function deepClone(obj) { 

  let cloneObj = {}

  for(let key in obj) {                 //遍历

    if(typeof obj[key] ==='object') { 

      cloneObj[key] = deepClone(obj[key])  //是对象就再次调用该函数递归

    } else {

      cloneObj[key] = obj[key]  //基本类型的话直接复制值

    }

  }

  return cloneObj

}

let obj2 = deepClone(obj1);

obj1.a.b = 2;

console.log(obj2);   //  {a:{b:1}}

Attention:

  1. 这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
  2. 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
  3. 对象的属性里面成环,即循环引用没有解决。

方案三: 增强版递归

  1. 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法;
  2. 当参数为 Date、RegExp 类型,则直接生成一个新的实例返回;
  3. 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链;
  4. 利用 WeakMap https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 Map 和 weakMap 的关键区别,这里要用 weakMap),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。
const isComplexDataType = obj => (typeof obj === "object" || typeof obj === "function") && obj !== null;

const deepClone = function (obj, hash = new WeakMap()) {
  if (obj.constructor === Date) return new Date(obj); // 日期对象直接返回一个新的日期对象
  if (obj.constructor === RegExp) return new RegExp(obj); //正则对象直接返回一个新的正则对象

  //如果循环引用了就用 weakMap 来解决
  if (hash.has(obj)) return hash.get(obj);
  let allDesc = Object.getOwnPropertyDescriptors(obj);

  //遍历传入参数所有键的特性
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);

  //继承原型链
  hash.set(obj, cloneObj);

  for (let key of Reflect.ownKeys(obj)) {
    cloneObj[key] = isComplexDataType(obj[key]) && typeof obj[key] !== "function" ? deepClone(obj[key], hash) : obj[key];
  }

  return cloneObj;
};

// 下面是验证代码
let obj = {
  num: 0,
  str: "",
  boolean: true,
  unf: undefined,
  nul: null,
  obj: { name: "我是一个对象", id: 1 },
  arr: [0, 1, 2],
  func: function () {
    console.log("我是一个函数");
  },
  date: new Date(0),
  reg: new RegExp("/我是一个正则/ig"),
  [Symbol("1")]: 1,
};

Object.defineProperty(obj, "innumerable", {
  enumerable: false,
  value: "不可枚举属性",
});

obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj));
obj.loop = obj; // 设置loop成循环引用的属性
let cloneObj = deepClone(obj);
cloneObj.arr.push(4);
console.log("obj", obj);
console.log("cloneObj", cloneObj);

相关习题

/* 对象的拷贝 */
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
obj.a = 2
console.log(obj)  //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2
console.log(obj)  //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}}
/* 数组的拷贝 */
let arr = [1, 2, 3];
let newArr = [...arr]; //跟arr.slice()是一样的效果
// 请自行实现一个浅拷贝  1. 对基础类型做一个最基本的一个拷贝 2. 对引用类型开辟一个新的存储,并且拷贝一层对象属性
const shallowClone = (target) => {
  if (typeof target === 'object' && target !== null) {
    const cloneTarget = Array.isArray(target) ? [] : {};
    for (let prop in target) {
      if (target.hasOwnProperty(prop)) {
        cloneTarget[prop] = target[prop];
      }
    }
    return cloneTarget;
  }
  return target;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值