地址与地基:在 JavaScript 的堆栈迷宫里,重新理解“复制”的哲学

前言

js 复制代码
let a = 1
let b = a
let obj = {
  age: 18
}
let obj2 = obj

把 a 的值赋值给 b ,这个是一种拷贝,但是把 obj 的值赋给另一个对象 obj2 ,它就不是拷贝,这是为啥呢,今天我们来深入探讨 js 中的 拷贝 --- 分为浅拷贝和深拷贝,由于原始类型在任何语言层面操作都是值复制,没有共享问题,也就没有"深浅"维度;所以在这里,拷贝只针对引用类型,基于原对象,拷贝得到一个新对象,这也解释了为什么上面对象赋值不算拷贝。


浅拷贝

1、[].slice(0) ---截取数组

[].slice(0) 就是一种浅拷贝,我们来看看一下浅拷贝都有些什么特征

js 复制代码
const arr = ['a', 'b', 'c', 'd', {age: 18}]    
const arr2 = arr.slice(0)
arr2.splice(2, 1)
console.log(arr);   

看输出结果,它符合基于原对象,拷贝得到一个新对象,新对象改动不影响旧对象,但是这样也解释不了为啥叫浅拷贝呀,别急,我们再来看一段代码:

js 复制代码
const arr = ['a', 'b', 'c', 'd', {age: 18}]
const arr2 = arr.slice(0)
arr[4].age = 20
console.log(arr2);

我们可以看到,旧对象里面的子对象在拷贝过后改动会影响新对象里面的子对象,只是因为所有的引用类型都会在堆里放一个引用地址,我们拷贝时新对象里面的子对象是 copy 了一个引用地址,后续子对象里面的值更改过后,新对象也会跟着变,如下图所示:

所以我们可以知道 浅拷贝 --- 新对象受原对象的影响(只拷贝了原对象的第一层,里面的子对象拷贝的还是引用地址)。实现浅拷贝的方法有五种,分别是 [].slice(0)[...arr]arr1.concat(arr2)arr.toReversed().reverse()Object.assign({}, obj)


2、 [...arr] ---解构数组

js 复制代码
const a = [1, 2, {age: 18}]
let c = [...a]
c.splice(1, 1)
a[2].age = 19
console.log(a);
console.log(c);

3、 arr1.concat(arr2) --- 合并数组

js 复制代码
const a = [1, 2, 3, {age: 18}]
const b = [4, 5]
const c = a.concat(b)
console.log(c, a);
const d = [].concat(a)
a[3].age = 20
console.log(d);

4、 arr.toReversed().reverse() --- 反转数组

.toReversed() 得到一个反转的新数组,.reverse()方法会反转原数组,配合起来就能实现浅拷贝:

js 复制代码
const arr = [1, 2, 3, {age: 18}]
const arr2 = arr.toReversed().reverse()
arr2.splice(1, 1)
arr[3].age = 20
console.log(arr2);
console.log(arr);

5、 Object.assign({}, obj) --- 合并对象

它和合并数组不一样,它把后面对象里的值放入前面的对象里面,覆盖前面的对象里的值,返回一个新对象,后面对象不变,所以在这里实现拷贝效果都是在前面放一个空对象

js 复制代码
const obj = {
  name: '俊杰',
  like: ['泡脚']
}
const newObj = Object.assign({}, obj)
obj.like[0] = '台球'
console.log(newObj);

手搓浅拷贝

js 复制代码
const obj = {
  name: '俊杰',
  age: 18,
  like: {
    n: '洗脚',
    m: '台球'
  }
}
function shallowCopy(obj) {
  let o = {}
  // 遍历原对象,将原对象中的 key,value 都存到新对象中一份
  for (let key in obj) {   // key 为形参
    if (obj.hasOwnProperty(key)) {  // 判断当前的 key 是否是 obj 显示属性
      o[key] = obj[key]    // o.name = obj[name] = '俊杰'
    }
  }
  return o
}
const newObj = shallowCopy(obj)
obj.like.m = '篮球'
console.log(newObj);

深拷贝

顾名思义:层层拷贝,新对象不受原对象修改的影响,深拷贝有两种方法 structuredClone()JSON.parse(JSON.stringify(obj))

1、structuredClone()

js 复制代码
const obj = {
  name: '俊杰',
  age: 18,
  like: {
    n: '洗脚',
    m: '台球'
  },
  a: 123n,
}
const newObj = structuredClone(obj)
obj.like.m = '蓝球'
console.log(newObj);

它可以把子对象里面的东西全给 copy 出来,但是他有一个缺陷,就是无法拷贝函数和 Symbol。


2、 JSON.parse(JSON.stringify(obj))

js 复制代码
const obj = {
  name: '俊杰',
  age: 18,
  like: {
    n: '洗脚',
    m: '台球'
  }
 }
const oo = JSON.parse(JSON.stringify(obj))
obj.like.m = '篮球'
console.log(oo);


JSON.stringify(obj) 这是将里面的对象变为字符串,JSON.parse(str) 则是将里面的字符串变为对象,所以合起来实现了深拷贝,同样的他也有缺陷,无法处理 bigint, undefined, NaN, Infinity, function,Symbol。

手搓深拷贝

js 复制代码
const obj = {
  name: '俊杰',
  age: 18,
  like: {
    n: '洗脚',
    m: '台球'
  }
}
function deepClone(obj) {
  let o = {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
    // 判断里面是否有引用类型例如对象,数组(函数除外),null也要除外
      if (typeof(obj[key]) == 'object' && obj[key] !== null) {
      // 如果是引用类型就再执行一次拷贝
        const childObj = deepClone(obj[key])
        o[key] = childObj                      
      } else {
        o[key] = obj[key]
      }
    }
  }
  return o
}
const newObj = deepClone(obj)
obj.like.m = '篮球'
console.log(newObj);

运用递归思想,进行层层拷贝。


结语

拷贝,看似只是"复制"二字,实则藏着 JS 内存模型的全部秘密。

浅拷贝,把引用地址递出去,像递名片------改的是同一份底稿;

深拷贝,把整座房子连地基都搬过来,从此山水不相逢。

下次再写 let obj2 = obj 时,不妨先问一句:

"我只是想要一把钥匙,还是想要一栋真正属于自己的房子?"

弄懂深浅,才能让你的数据不再"藕断丝连",也让你的代码少一句 console.log 的叹息。

相关推荐
神探小白牙11 分钟前
eCharts 多系列柱状图增加背景图
javascript·ecmascript·echarts
女生也可以敲代码16 分钟前
AI时代下的50道前端开发面试题:从基础到大模型应用
前端·面试
ZhengEnCi23 分钟前
M5-markconv自定义CSS样式指南 📝
前端·css·python
IT_陈寒41 分钟前
SpringBoot自动配置的坑差点让我加班到天亮
前端·人工智能·后端
xingpanvip42 分钟前
星盘接口开发文档:星相日历接口指南
android·开发语言·前端·css·php·lua
@PHARAOH1 小时前
WHAT - GitLens supercharged 插件
前端
TT模板1 小时前
苹果cms整合西瓜播放器XGplayer插件支持跳过片头尾
前端·html5
Wect2 小时前
React 性能优化精讲
前端·react.js·性能优化
Cosolar2 小时前
告别无脑循环:深入解析 ReWOO 与 Plan-and-Execute Agent 架构
人工智能·面试·全栈
追风筝的人er2 小时前
SpringBoot+Vue3 企业考勤如何处理法定假期?节假日方案、调休补班与工作日判断链路拆解
前端·vue.js·后端