JavaScript中的浅拷贝和深拷贝

浅拷贝

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

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

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

常见的对象浅拷贝

1. Object.assign

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

2.展开运算符...

javascript 复制代码
// 对象的浅拷贝 
// 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)

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

常见的数组浅拷贝

1.Array.prototype.concat()

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

javascript 复制代码
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() 方法返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝 (包括 begin,不包括end)。原始数组不会被改变。

javascript 复制代码
// 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 扩展运算符 ...

javascript 复制代码
// 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())

javascript 复制代码
// 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),和引用数据类型的(对象,数组)
  • 不能处理函数,undefined,symbol,function,map,set类型的值
  • 并且当对象中包含循环引用时会报错obj.info = obj)

3.完整的深拷贝

封装一个函数来实现:

javascript 复制代码
// 可继续遍历的数据类型
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)

相关推荐
ac-er8888几秒前
PHP网络爬虫常见的反爬策略
开发语言·爬虫·php
理想不理想v1 分钟前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
爱吃喵的鲤鱼10 分钟前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
DARLING Zero two♡36 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
Gu Gu Study38 分钟前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
栈老师不回家1 小时前
Vue 计算属性和监听器
前端·javascript·vue.js
芊寻(嵌入式)1 小时前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
前端啊龙1 小时前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_1 小时前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法