揭开JS深拷贝的底裤:从递归到循环引用的终极解法

引言

"能手写一个深拷贝吗?"------这几乎成了前端面试中必问的保留节目。大多数人能写出基础的递归,一部分人能考虑到各种数据类型的兼容,但当面试官微笑着抛出"如果对象循环引用了怎么办?"时,场面往往瞬间安静。深拷贝的深水区,远比想象中更暗流涌动。今天,我们就来彻底揭开 JS 深拷贝的底裤,从递归的底层逻辑讲起,深入探讨各种棘手边界情况,最终破解循环引用的死局,帮你打造一个连面试官都无法挑剔的终极解法。

递归

在聊 JS 中拷贝前我们先来聊聊递归是啥?

递归: 递归就是程序自己调用自己,把大问题拆成相同的小问题,直到遇到终止条件再逐层返回结果。

知道递归是什么之后我们来用代码实现数学中如何求取一个值的阶乘如求取一个 5!。

第一种方法:

ini 复制代码
function mul(n) {
    let res = 1
    for(let i = n ; i > 0; i--){
        res = res * 5
    }
    return res
}

我们可以直接进行一个循环,得到 1 * 2 * 3 * 4 * 5 的结果,这样我们就可以算出 5!的值了这个是用循环的方法得到的,但我们要使用递归的方法。

在使用递归之前我们可以先把我们所需要的结果进行拆分一下,我们的目标是 5!,所以就是得到 mul(5)

scss 复制代码
mul(5)

mul(5)= 5 * mul(4)
mul(4)= 4 * mul(3)
mul(3)= 3 * mul(2)
mul(2)= 2 * mul(1)
mul(1)= 1

知道了这个过程之后我们就可以动动小手敲代码了

第二种方法:

scss 复制代码
function mul (n) {
  if(n == 1) return 1

  return  n * mul(n-1)
}

递归的过程 :

1. 找公式

2. 找出口

知道了这个过程之后我们就来运用到数学的斐波那契数列中即求第n位数是什么

scss 复制代码
// 1 1 2 3 5 8 13 ...  斐波那契数列

// 1.找公式: f(n)=f(n-1)+f(n-2)
// 2.找出口: n=2 fb=1  n=1 fb=1

function fb(n){
    if(n<3) return 1
    return n = fb(n-1)+fb(n-2)
}

拷贝

拷贝:克隆一份原对象,得到一份新对象

浅拷贝

浅拷贝: 克隆得到的新对象,其中的引用类型和原对象指向同一个内存地址。

说到浅拷贝,我们可以直接使用的有几个,如 Object.assign({},obj)\[\].slice(0)Array.from()

接下来我们自己通过写出一个代码来充分了解浅拷贝是什么。

scss 复制代码
let obj ={
    name:'猪猪侠',
    age:18,
    like:['洗脚','按摩']
}

function shallowCopy(obj) {
    let res = Array.isArray(obj) ? [] : {}

    for(let key in obj){
        if (obj.hasOwnProperty(key)){     
            res[key]=obj[key]
        }
    }
    return res
}

let oo = shallowCopy(obj)
obj.like[0]='品茶'
console.log(oo);  

我们直接看一下输出结果:

输出当中的结果中 like 里面第一个被更改为了 '品茶' ,其实这就是浅拷贝,它能将原始值直接拷贝,但是遇到引用类型的值时只会把引用类型的引用地址拷贝过去,和原对象用的都是同一个引用地址,当你要更改新对象里面包含的引用类型时,原对象里面的值也会被更改,这就是浅拷贝。

深拷贝

深拷贝: 将原对象中的所有的子对象,层层拷贝,引用地址全是全新的。

我们依旧通过代码来实现解释:

vbnet 复制代码
let obj ={
    name:'猪猪侠',
    age:18,
    like:{
        n:'打瓦',
        m:'三角洲',
        o:{
            a:'洗脚'
        }
    }
}

function deepCopy(obj){
    let res = {}

    for(let key in obj){
        if (obj.hasOwnProperty(key)){     
            if(typeof obj[key] == 'object' && obj[key] !== null ){
                res[key] = deepCopy(obj[key])
            }else{   //原始值
                res[key]=obj[key]
            }
        }
    }
    return res
}

let oo = deepCopy(obj)
obj.like.o.a='按摩'
console.log(oo);   

我们还是先看一下输出结果:

我们更改了原对象里面的引用类型里面的值,但是新对象还是跟原对象刚开始的值是一样的,这是因为新对象里面的引用地址全是新的不是和原对象的应用地址是同一个。

注意: 在创建这个函数的时候我们需要用到递归方法,因为这种都是一层套一层的。

在代码中我们用到了 for (key in obj) {} 这个方法是用来遍历引用类型里面的内容 。

最后在补充一下我们现在可以用到的深拷贝的方法:

  1. JSON.parse(JSON.stringify(obj)) --> 不能处理 BigInt undefined function NaN Infinity symbol
  2. structuredClone() --> 不能处理 function

总结

从一行简陋的递归,到能从容应对各种奇葩边界与循环引用的终极解法,我们一步步揭开了 JS 深拷贝的神秘底裤。深拷贝的演进过程,其实就是对 JS 数据类型和内存机制不断加深理解的过程。技术没有捷径,唯有死磕底层细节,才能写出真正无懈可击的代码。愿你在未来的开发中,不再被引用的坑绊倒,每一份拷贝都清清楚楚、明明白白。

相关推荐
创业之路&下一个五年2 小时前
mvvm中v和vm关系,vm中v和m的关系?
java·开发语言·javascript
一天 24h2 小时前
Vue3父子组件传值:从零到精通
前端·javascript·vue.js·pycharm·npm·学习方法
大家的林语冰2 小时前
CSS 新函数上市,一行代码让文本自动变色,无需 JS 也能符合 W3C 无障碍对比度标准
前端·javascript·css
爱勇宝2 小时前
前端工程师的下一站:不是失业,而是 AI Engineer
前端·javascript·架构
小雨下雨的雨2 小时前
电池电量检测工具 - 鸿蒙PC用Electron框架技术实现详解
前端·javascript·华为·electron·鸿蒙·鸿蒙系统
砍材农夫3 小时前
物联网实战:Spring Boot MQTT | 模拟器Paho客户端拆解核心点
java·javascript·网络·spring boot·后端·物联网
小李云雾3 小时前
Vue Router 从入门到精通:路由核心知识点全解析
前端·javascript·vue.js
weixin_539446783 小时前
使用Java HttpServletResponse和JavaScript Fetch下载文件
java·javascript·python
小雨下雨的雨3 小时前
近视度数模拟器鸿蒙PC Electron框架技术实现详解
前端·javascript·electron