当前位置: 首页 > article >正文

【前端面经】JS-深浅拷贝

理解深浅拷贝

深浅拷贝问题的出现是由于JavaScript对不同类型的存储方式而引发的。
对于原始数据类型,它们的值是直接存储在栈内存中;
而复杂数据类型,则在栈内存中记录它的指针,而指针指向堆内存中真正的值。
所以对于原始数据类型,就没有深浅拷贝一说;而对于复杂数据类型,浅拷贝就是仅复制指针,但被复制对象改变时,新复制的对象也会跟着改变;深拷贝则是连同堆内存中的数据完全拷贝一份,新旧对象的变化互不影响。

深浅拷贝的定义

浅拷贝:

拷贝复杂类型时,仅拷贝对象的指针;当原对象改变时,拷贝对象也会跟着改变。

深拷贝:

拷贝复制类型时,拷贝对象的值;当原对象改变时,拷贝对象不会跟着改变。

深浅拷贝的实现方案

浅拷贝方案:

  1. 展开运算符(ES6)
  2. Object.assign()
  3. concat

深拷贝:

  1. 手写递归
  2. JSON.parse(JSON.stringfy())
    • 先转换成字符串,再解析回对象
    • 缺点是不能处理正则表达式和函数等特殊数据类型
  3. 一些库封装的深拷贝方法,例如Lodash的_.cloneDeep()

手写实现

// 先写一个浅拷贝
function _deepClone(target) {
    let cloneRes = {};
    for(const key in target) {
        cloneRes[key] = target[key]
    }
    return cloneRes
    // 函数出口
    // if (typeof target !== 'obejct') return target
}

// 2. 普通深拷贝(只考虑对象/非对象)
function _deepClone(target) {
    if (typeof target === 'object'){
        // 如果是对象,进行处理
        let cloneRes = {};
        for(const key in target) {
            cloneRes[key] = _deepClone(target[key])
        }
        return cloneRes
    } else {
        // 如果不是对象,直接返回
        return target
    }
}

// 3.考虑数组的深拷贝
function _deepClone(target) {
    if (typeof target === 'object') {
        let cloneRes = Array.isArray(target) ? [] : {}; // 核心
        for (const key in target) {
            cloneRes[key] = _deepClone(target[key])
        }
        return cloneRes
    } else {
        return target
    }
}

// 4. 考虑循环引用
// Q:为什么要用WeakMap替代Map?
// A:强引用和弱引用的区别,强引用只能手动释放,弱引用能够被垃圾回收机制释放,使用Map会造成内存额外消耗。
//    WeakMap中的键是弱引用,Map中的键是强引用
function _deepClone(target, map=new WeakMap()) {
    if (typeof target === 'object') {
        let cloneRes = Array.isArray(target) ? [] : {}; // 核心
        // 防止循环调用导致的栈内存溢出
        if (map.get(target)) {
            return map.get(target)
        }
        map.set(target, cloneRes)
        for (const key in target) {
            cloneRes[key] = _deepClone(target[key], map)
        }
        return cloneRes
    } else {
        return target
    }
}

// 5. 考虑其他数据类型
function _deepClone (target, map = new WeakMap()) {
    if (target === null) return null
    if (target instanceof Date) return new Date(target)
    if (target instanceof RegExp) return new RegExp(target);
    if (typeof target !== 'object') return target
    if (map.get(target)) return map.get(target);

    let cloneRes = new target.constructor()
    map.set(target, cloneRes)
    for (const key in target) {
        if (target.hasOwnProperty(key)) {
            cloneRes[key] = _deepClone(target[key], map);
        }
    }
    return cloneRes
}
// 6. 性能优化
// 把for..in改为普通的for循环,性能会有所提高

// 测试案例
const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
const newTarget = _deepClone(target)
newTarget.field4[1] = "Child2"
console.log(target);
console.log(newTarget);
console.log(target === newTarget);
// 输出:
// {
//   field1: 1,
//   field2: undefined,
//   field3: { child: 'child' },
//   field4: [ 2, 4, 8 ]
// }
// {
//   field1: 1,
//   field2: undefined,
//   field3: { child: 'child' },
//   field4: [ 2, 'Child2', 8 ]
// }
// false

参考链接&&相关文章

前端早读课【第2810期】JavaScript 深拷贝性能分析


http://www.kler.cn/news/17095.html

相关文章:

  • 4. 嵌入式基础
  • 唱作音乐人朱卫明新歌全网首发,当初恋遇到《龙仙街》
  • 留守儿童爱心网站
  • JSP+SQL基于JSP的学生信息管理系统(源代码+论文+答辩PPT)
  • 操作系统原理 —— 调度的概念、层次(十一)
  • Git 使用教程:最详细、最正宗手把手教学(万字长文)
  • springboot实用配置
  • Effective 建造者模式、私有化、依赖注入
  • 五一欢乐赛!题解
  • 希尔排序(C++)
  • 安卓开发_广播机制_广播的最佳实践:实现强制下线功能
  • PyQt5桌面应用开发(5):对话框
  • Java 基础进阶篇(二)—— static 静态关键字与单例模式
  • kafka 学习,笔记
  • Spring Boot参考指南-Spring Boot安装(Maven安装、Gradle安装)
  • Docker compose 常用指令
  • c++ 11标准模板(STL) std::vector (二)
  • 天气预报查询 API 提供个性化的天气服务的设计思路
  • 贪心刷题~
  • AI 时代,提示词便是生产力
  • ChatGPT AI使用成本
  • 【每日随笔】操控人性 ③ ( 懂领导的心思 | 办事的套路 | 管理学与权谋 | 人事谱系 )
  • HDU5552 Bus Routes(分治NTT)
  • 每天一道算法练习题--Day16 第一章 --算法专题 --- ----------哈夫曼编码和游程编码
  • SpringCloud:ElasticSearch之数据同步
  • 【实例展示通俗易懂】SQL中的内外连接、左右连接
  • Vue3+Element Plus环境搭建和一键切换明暗主题的配置
  • 【Latex】有关于Latex tabularray的一些很不错的教程、模板
  • LeetCode周赛复盘(第343场周赛)
  • isNotBlank 和isNotEmpty的区别