新手学前端JS浅拷贝和深拷贝:对象复制竟然是个“替身文学”?

前言

写 JS 谁没栽在拷贝上?兴冲冲赋值以为复制好了对象,改新变量原数据跟着原地叛变;只改内层属性老对象悄悄躺枪,排查 BUG 半天才发现踩了引用的大坑。浅拷贝只能扒外层皮,嵌套对象依旧共用一套内存,深拷贝稍有不慎又遇上正则、日期、函数翻车。 本文用接地气的大白话,掰开深浅拷贝底层原理,从扩展运算符、Object.assign 到递归手写深拷贝、JSON 暴力拷贝逐个拆坑,理清适用场景与边界缺陷,看完再也不会被引用数据偷偷背刺。

递归

在说关于js拷贝之前,我们先来了解一下递归这个概念,因为深拷贝就是利用了递归思想。创造递归有两个条件:

  1. 找公式:即找递归的公式
  2. 找出口:即找递归的出口,下面举个斐波那契数列的例子加以深刻了解
js 复制代码
function feibo(n){
    if(n ==1 || n ==2) return 1
    return feibo(n - 1) + feibo(n - 2)
}
console.log(feibo(10))

可以看到feibo(n - 1) + feibo(n - 2)就是该递归函数的公式,if(n ==1 || n ==2) return 1就是递归出口,没有这个出口,递归将会一直执行,直到调用栈报错,还有一点值得注意,并不是所有的递归都需要出口。

拷贝

拷贝顾名思义就是克隆一份原对象,得到一份新对象,用一段代码举例:

js 复制代码
let a = 1
let b = a
a = 2
console.log(b);

这时我们知道js经过预编译之后,已经将a的值赋给了b,也就可以理解为b将a的数据拷贝了一份传给自己保存,从而使得a的值改变之后b的值不变。这就是拷贝,那对象等引用类型的拷贝也一样吗,显然不是的,那就要引出我们的浅拷贝和深拷贝的概念了。

1. 浅拷贝

浅拷贝通俗来讲,就是如果原obj对象里面是基本类型,直接给新对象赋值原始类型,如果是引用类型,则给新对象赋值的是原对象的引用地址,只复制第一层,里面嵌套对象不复制,这就好像复刻一个房子,但是家具还共用,下面举个例子加以说明:

js 复制代码
let obj ={
    age:18
}
let oo =obj
obj.age = 19
console.log(oo.age);

就比如这里,新手很容易以为这和原始类型一样,将对象里的age重新复制后,拷贝的新对象oo里的age也不会变,但其实就是在预编译时两份对象存储的都是同一份引用地址,指向的都是同一个堆空间,所以obj.age = 19执行之后,等于修改同一个对象,age都会变成18。就像下图所示:

那么js有什么方法实现浅拷贝呢?

-Object.assign 实现浅拷贝

js提供了一个专门用来浅拷贝的函数Object.assign(),传入参数为Object.assign({},obj),我们可以试着手搓一个丐版的浅拷贝:

js 复制代码
let obj ={
    age:18,
    name: '南哥',
    hobby:['瓦','州']
}
// let obj = ['a','b','c']
function shallowCopy(obj){
   let newObj = Array.isArray(obj)? [] : {}
   for(let key in obj){
    if(obj.hasOwnProperty(key)){
           newObj[key] = obj[key]
    }
   }
   return newObj
}
let oo = shallowCopy(obj)
 obj.hobby[0] = '原始'
console.log(oo);

执行结果为:

当我加入 obj.hobby[0] = '原始'的执行后,结果为:

可以看到拷贝过来的的对象hobby数组变了,因为拷贝的是引用地址,而地址对应的堆中hobby数组变了。所以

Object.assign({},obj),\[\].slice() 原obj对象里面如果是基本类型,直接给新对象赋值,如果是引用类型,则给新对象赋值的是原对象的引用地址 \[\].slice()

深拷贝

好了,你已经知道浅拷贝原理,那么类似的深拷贝就是从第一层复制到最后一层,但是所有引用地址全部重新创建,还是举个例子结合递归带你了解丐版底层深拷贝原理:

js 复制代码
let obj ={
    age:18,
    name: '南哥',
    hobby:{
        n:'1',
        m:'2',
        o:{
            a:'洗脚'
        }
    }
}

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
}
const oo = deepCopy(obj)
obj.hobby.o.a = '按摩'
console.log(oo);

结果为:

可以看到,修改obj.hobby.o.a 之后,新对象里的不会变,这时因为新对象也创建了一个新的引用地址,不受影响。为什么递归能实现深拷贝,递归会不断进入: 直到:不是对象,停止递归。然后逐层返回。 因此:

复制代码
所有子对象
都获得新的引用地址

js还有两种自带的深拷贝

JSON.parse(JSON.stringify(obj)) //不能拷贝bigint类型,undefined类型,NaN类型,Infinity类型,函数类型,symbol类型
structuredClone(obj) //不能拷贝函数类型

相关推荐
艾莉丝努力练剑2 小时前
【AI大模型接入SDK】Provider分析与实现
c++·人工智能·学习·面试·大模型·llm
单线程_018 小时前
从案例分析 Vue3 Tokenizer 源码一
前端·javascript·vue.js
大模型码小白9 小时前
AI 对话流性能调优:万级消息的虚拟滚动落地
java·大数据·前端·javascript·人工智能·算法·机器学习
m0_547486669 小时前
《JavaScript核心原理 》全套PPT课件2026
开发语言·javascript·ecmascript
数智启示录9 小时前
Flink CDC 机制精讲(二):Checkpoint Barrier 如何形成一致快照 【面试宝典】
大数据·经验分享·面试·flink
xm_xm_xm_110 小时前
react17版本以前类组件常用操作
前端·javascript·react.js
lzhdim11 小时前
13、JavaScript事件循环机制 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
liangshanbo121511 小时前
Vue 3 <script setup> 的本质原理是什么?它与普通 setup()有何区别?
前端·javascript·vue.js
七牛开发者13 小时前
实测推荐 3 个 Skill,轻松上手 Codex 网页设计与交付流程
前端·javascript·后端
七牛开发者13 小时前
拆解 dsh 系列:从源码和版本变化看 DeepSeek Harness 的设计取舍
前端·javascript·后端