JavaScript中的浅拷贝和深拷贝-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

 浅拷贝

创建一个新对象这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型拷贝的就是基本类型的值如果属性是引用类型拷贝的就是内存地址 所以如果其中一个对象改变了这个地址就会影响到另一个对象。       

默认情况下引用类型object都是浅拷贝

简单理解对于对象来说就是对最外层数据的拷贝如果对象只有一层则相当于深拷贝如果对象有多层即某个属性为引用数据类型则拷贝的是该引用类型在堆中的地址不会在内存创建一个新的对象。

常见的对象浅拷贝

1. Object.assign

用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

2.展开运算符... 

// 对象的浅拷贝 
// 1.Object.assign()
let obj0={a:0}
let obj2 = Object.assign(obj0,obj1)
console.log(obj0===obj2) //true
console.log(obj1===obj2) // false
obj1.name = '张三1'  //obj2不变
obj1.hobby[0]='打篮球' //obj2变了
obj1.friend.name='李四1' //obj2变了
console.log(obj1)
console.log(obj2)

// 2.展开运算符
let obj3 = {...obj1}
obj1.hobby.push('摄影') //obj3会改变
console.log(obj3)

如果被拷贝对象的属性值为简单类型如NumberString通过Object.assign({},Obj)得到的新对象为深拷贝如果属性值为对象或其它引用类型则只拷贝了该引用类型的地址。 

常见的数组浅拷贝

1.Array.prototype.concat()

将alpha和numeric合并为一个新数组并返回

let arr1 = ['哈哈',18,{name:'哈利',age:10}]
let arr2 = ['嘿嘿',19,{name:'赫敏',age:11}]
// 1.concat
let arr3 = arr1.concat(arr2)
console.log(arr3===arr1) //false
console.log(arr3===arr2) // false
arr1[0]='haha' //arr3不会改
arr1[2].age=100 //arr3会改
console.log(arr3)

2. Array.prototype.slice()

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

// 1.slice截取数组
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

console.log(animals.slice(2));
// expected output: Array ["camel", "duck", "elephant"]

console.log(animals.slice(2, 4));
// expected output: Array ["camel", "duck"]

//2.slice浅拷贝
let arr1 = ['哈哈',18,{name:'哈利',age:10}]
let arr4 = arr1.slice() //不传参数相当于浅拷贝数组
console.log(arr4===arr1) //false
arr1[2].name = '哈利111' //arr4会变
console.log(arr4)

 3 扩展运算符 ...

// 3.展开运算符
let arr1 = ['哈哈',18,{name:'哈利',age:10}]
let arr5 = [...arr1]
arr1[2].age=5 //arr5会改变
console.log(arr5)

参考:https://github.com/YvetteLau/Step-By-Step/issues/17https://github.com/YvetteLau/Step-By-Step/issues/17

深拷贝

 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

根据拷贝的层级不同可以分为浅拷贝和深拷贝浅拷贝就是只进行一层拷贝深拷贝就是无限层级拷贝

1.深拷贝的简单实现 JSON.parse(JSON.stringify())

// JSON.parse(JSON.stringify(obj))可以处理 
// string,number,boolean,null,object,array
// 会忽略symbol,undefined,function,不能处理bigint,date,error,map,set
// 不能处理循环引用
let obj = {
  name: "哈哈",
  age: 18,
  bool: true,
  nu: null,
  un: undefined, //会忽略undefined类型的值
  symbol: Symbol(), //会忽略symbol类型的值
  // big: 15n, Do not know how to serialize a BigInt
  map: new Map(), // 不能处理
  set: new Set(),// 不能处理
  obj: {
    color: "blue",
    age: 13,
  },
  arr: [1, 2, 3, 4],
  date: new Date(),
  err: new Error(),
  fun: function add() {}, //会忽略function类型的值
};

let obj1 = JSON.parse(JSON.stringify(obj));
  • 可以处理基本数据类型string,number,boolean,null和引用数据类型的对象,数组
  • 不能处理函数undefinedsymbolfunctionmapset类型的值
  • 并且当对象中包含循环引用时会报错obj.info = obj

3.完整的深拷贝

封装一个函数来实现

// 可继续遍历的数据类型
const objectTag = "[object Object]";
const arrayTag = "[object Array]";
const mapTag = "[object Map]";
const setTag = "[object Set]";

// 不可继续遍历的数据类型
const stringTag = "[object String]";
const numberTag = "[object Number]";
const booleanTag = "[object Boolean]";
const symbolTag = "[object Symbol]";
const dateTag = "[object Date]";
const functionTag = "[object Function]";
const errorTag = "[object Error]";

// 需要深度遍历的数据类型
const deepTag = [setTag, mapTag, arrayTag, objectTag];

// 工具函数-遍历数组使用while提升性能
function forEach(arr, cb) {
  const len = arr.length;
  let i = -1;
  while (++i < length) {
    cb(arr[i], i);
  }
  return arr;
}

// 工具函数-判断是否是引用类型
function isObject(value) {
  const type = typeof value;
  // 这里忽略了function类型 function类型直接返回
  return value !== null && type === "object";
}

// 工具函数-获取数据实际类型
function getType(value) {
  return Object.prototype.toString.call(value);
}

//工具函数-初始化被克隆的对象
function getInit(value) {
  const cons = value.constructor;
  return new cons();
}
// 工具函数-克隆symbol
function cloneSymbol(value) {
  return Object(Symbol.prototype.valueOf.call(value));
}

// 工具函数-克隆不可遍历的类型(这里忽略了对函数和正则的处理)
function cloneOtherType(value) {
  const type = getType(value);
  const ctor = value.constructor;
  switch (type) {
    case stringTag:
    case numberTag:
    case booleanTag:
    case functionTag:
    case errorTag:
    case dateTag:
      return new ctor(value);
    case symbolTag:
      return cloneSymbol(value);
    default:
      return null;
  }
}
// 真正实现深拷贝的函数
function clone(value, map = new WeakMap()) {
  // 不是引用数据类型直接返回
  if (!isObject(value)) return value;

  // 根据数据类型做不同的处理
  let copyValue;
  const typeOfValue = getType(value);
  if (deepTag.includes(typeOfValue)) {
    copyValue = getInit(value);
  } else {
    return cloneOtherType(value);
  }

  // 处理循环引用
  if (map.has(value)) return map.get(value);
  map.set(value, copyValue);

  // 处理Map
  if (typeOfValue === mapTag) {
    value.forEach((item, key) => {
      copyValue.set(key, clone(item, map));
    });
    return copyValue;
  }
  // 处理set
  if (typeOfValue === setTag) {
    set.forEach((item) => {
      copyValue.add(clone(item, map));
    });
    return copyValue;
  }
  // 处理对象
  if (typeOfValue === objectTag) {
    const keys = value.keys();
    forEach(keys, (item) => {
      copyValue[item] = clone(value[item], map);
    });
    //获取属性值为symbol类型的keys
    const symbolKeys = Object.getOwnPropertySymbols(value);
    forEach(symbolKeys, (item) => {
      copyValue[item] = clone(value[item], map);
    });
    return copyValue;
  }
  // 处理数组
  if (typeOfValue === arrayTag) {
    forEach(value, (item, index) => {
      copyValue[index] = clone(item, map);
    });
    return copyValue;
  }
}

参考如何写出一个惊艳面试官的深拷贝 (qq.com)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: JavaScriptJava