JavaScript 的深浅拷贝

在我们开始了解拷贝之前,我们先来了解下v8的数据是怎么存储的:

  1. 栈内存: 栈内存主要用来存储一些基本类型的数据( number、string、boolean、null、undefined、symbol、bigInt )

  2. 堆内存: 存储引用数据类型。( object、 array、 function、 date、math、error、json等)

拷贝:

为什么要存在拷贝?

当我们把一个对象复制给另一个对象时,其实只是将该对象的引用地址赋给了新对象(因为引用类型是存在堆内存里面的),所以他们共享同一块内存空间,当我们修改某一个对象时,另外一个对象也会跟着改变。所以为了解决这种情况,我们就需要拷贝一个完全一样的引用类型来实现当我修改其中某个引用类型变量时不会导致另一个也发生变化,让他们互不影响,这就是拷贝的意义。

注意:基本类型没有深浅拷贝的概念,在我们对基本类型进行赋值和传参时,本质是值的拷贝,是不涉及深或浅的。

拷贝的种类:
  • 浅拷贝:只拷贝对象的最外层,原对象的属性值修改会影响新对象

  • 深拷贝:层层拷贝,并且新旧对象互不影响

浅拷贝:
  1. [].concat(arr)

concat 是数组的一种方法,可以用来合并一个或是多个数组,再返回一个新的数组,不会去改变原来的数组

ini 复制代码
let arr = [1, 2, 3]
let arr2 = [4, 5]
console.log(arr.concat(arr2));
console.log(arr2);

let arr3 = [].concat(arr)
console.log(arr3);
  1. 数组解构 [...arr]

数组结构也是再创建的一个新数组也是不会改变原来数组的。

ini 复制代码
let arr = [1, 2, 3]
const [x, y, z] = arr
console.log(x, y, z);
console.log(...arr);

let arr2 = [...arr]
console.log(arr2);

3. arr.slice(0, arr.length)

javascript 复制代码
let arr = ['a', 'b', 'c', 'd', 'e']
arr.splice(1, 0, 'o')  // 会影响原数组
let arr2 = arr.slice(0, arr.length) // 不会影响原数组

console.log(arr2);

4. Object.assign({}, obj)

以下我们可以看到这个方法在不影响之前的数组内参数的值

ini 复制代码
let obj = {
  name: '康少',
  age: 18
}
let girl = {
  nickname: '章若楠'
}
let newObj = Object.assign({}, obj)
console.log(obj);
console.log(newObj);
newObj.name = '康少2'
console.log(newObj);

5. arr.toReversed().reverse()【.toReversed()近来新增的方法,有些浏览器读不懂这方法,并且这两个方法都是将数组翻转一次】

此方法也是不会影响原数组的,属于浅拷贝

ini 复制代码
let arr = [1, 2, 3]

let newArr = arr.reverse()
console.log(newArr, arr);
ini 复制代码
let arr = [1, 2, 3]

// let newArr = arr.reverse()
// console.log(newArr, arr);

let newArr = arr.toReversed().reverse()
console.log(newArr, arr);

newArr[0] = 100
console.log(newArr, arr);

提示: 浅拷贝无法拷贝有嵌套对象的引用类型,因为"拷贝"出的两个引用类型依旧会互相影响。如下:

ini 复制代码
let arr = [{a:1},{b:2}]

let newArr = []

newArr = arr.toReversed().reverse()

console.log(newArr);

newArr[0].a = 3

console.log(arr);

console.log(newArr);

手搓浅拷贝:

vbnet 复制代码
function shallowCopy(obj) {
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) { 
            newObj[key] = obj[key]
        }
    } return newObj
}

需要用hasOwnProperty()方法去排除原型链上继承的属性,只复制 obj 自身拥有的对象,否则拷贝时会把原型链上的属性也复制至新对象中,会导致浅拷贝包含不属于该对象的自身的属性。

深拷贝:
  1. JSON.parse(JSON.stringify(obj))

    • 无法识别 bigInt 类型,无法处理 undefined, function,symbol,如果加入至以下 like 对象里面会出现报错
    • 无法处理循环引用
ini 复制代码
let obj = {
    name:'kangshao',
    age:18,
    like :{
        a:'pi',
        b:'ki',
        c:'ni'
    }
}

let newObj = JSON.parse(JSON.stringify(obj))
console.log(obj);
console.log(newObj);
obj.like.a = 'ni'
console.log(obj);
console.log(newObj);
newObj.like.a = 'hello'
console.log(obj);
console.log(newObj);
  1. structuredClone() (官方打造的函数)

    • 无法拷:function,Symbol
javascript 复制代码
let obj = {
  name: '康少',
  age: 18,
  like: {
    a: '唱',
    b: '跳',
    c: 'rap',
  },
  a: undefined,
  b: null,
  e: {}
}
let newObj = structuredClone(obj)
obj.like.a = '篮球'
console.log(newObj);

手搓深拷贝: (重要)★★★

普通版:

vbnet 复制代码
function deepCopy(obj) {
  let newObj = {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === 'object' && obj[key] !== null) {
        newObj[key] = deepCopy(obj[key])
      } else {
        newObj[key] = obj[key]
      }
    }
  }
  return newObj
}

进阶版:

javascript 复制代码
function deepCopy(obj, map = new WeakMap()) {
  // 如果不是对象(包括 null),直接返回
  if (typeof obj !== 'object' || obj === null) return obj;

  // 如果是 Date
  if (obj instanceof Date) return new Date(obj);

  // 如果是 RegExp
  if (obj instanceof RegExp) return new RegExp(obj);

  // 如果对象已拷贝过(处理循环引用)
  if (map.has(obj)) return map.get(obj);

  // 创建新的拷贝对象,保持原对象的构造函数
  const newObj = Array.isArray(obj) ? [] : {};

  // 缓存当前对象
  map.set(obj, newObj);

  // 遍历对象属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepCopy(obj[key], map);
    }
  }

  return newObj;
}
相关推荐
goldenocean22 分钟前
React之旅-06 Ref
前端·react.js·前端框架
小赖同学啊23 分钟前
将Blender、Three.js与Cesium集成构建物联网3D可视化系统
javascript·物联网·blender
子林super24 分钟前
【非标】es屏蔽中心扩容协调节点
前端
前端拿破轮26 分钟前
刷了这么久LeetCode了,挑战一道hard。。。
前端·javascript·面试
代码小学僧26 分钟前
「双端 + 响应式」企业官网开发经验分享
前端·css·响应式设计
土豆骑士33 分钟前
简单理解Typescript 装饰器
前端·typescript
Java水解34 分钟前
【web应用】若依框架前端报表制作与导出全攻略(ECharts + html2canvas + jsPDF)
前端
多啦C梦a34 分钟前
《设计模式?》前端单例模式保姆级教程:用 Class + 闭包各封装一个 LocalStorage 单例,一次学会!
前端·javascript·面试
ttod_qzstudio37 分钟前
彻底移除 HTML 元素:element.remove() 的本质与最佳实践
前端·javascript·typescript·html
WebGirl41 分钟前
Unicode转义字符&html实体符号
javascript