【前端面试题】深拷贝的终极实现

简介: 【前端面试题】深拷贝的终极实现

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

引子

通过本文可以学习到深拷贝的三种写法的实现思路与性能差异

首先,我们要理解什么是深拷贝,以及为什么要实现深拷贝

深拷贝是什么

通俗来讲,深拷贝就是深层的拷贝一个变量值。

为什么要实现深拷贝

因为在拷贝引用值时,由于复制一个变量只是将其指向要复制变量的引用内存地址,他们并没有完全的断开,而使用就可以实现深拷贝将其完全拷贝为两个单独的存在,指向不同的内存地址。

如何实现深拷贝

一行实现

let deepClone = JSON.parse(JSON.stringify(obj))
复制代码

这种是最简单的实现方法,但缺点是无法拷贝 Date()或是RegExp()。  

简单实现

function deepClone(obj) {
    // 判断是否是对象
    if (typeof obj !== 'object') return obj
    // 判断是否是数组 如果是数组就返回一个新数组 否则返回一个新对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj
    for (var key in obj) {
        // 将key值拷贝,再层层递进拷贝对象的值
        newObj[key] = deepClone(obj[key]);
    }
    // 返回最终拷贝完的值
    return newObj;
}
复制代码

对于普通的值(如数值、字符串、布尔值)和常见的引用类型(如对象和数组),这个写法完全够用。

但因为少了对 Date()  和  RegExp() 这些引用类型的特殊处理,这个写法一样不够完备。

普通版

function deepClone(origin, target) {
  // 判断target是否传入,如果未传入则创建一个{}
  let tar = target || {}; 
  // 遍历origin对象
  for (var key in origin) {
    // 判断是否origin的自有属性
    if (origin.hasOwnProperty(key)) {  
      // 如果值是对象并且不为null,递归调用
      if (typeof origin[key] === 'object' && origin[key] !== null) {  
        // 根据值是否为数组创建初始化对象或数组
        tar[key] = Array.isArray(origin[key]) ? [] : {};
        // 递归调用 
        deepClone(origin[key], tar[key]);
      } else {  
        // 如果是简单类型,直接复制值
        tar[key] = origin[key];
      }
    }
  }
  // 返回最终拷贝完的值
  return tar;
}
复制代码

这个深拷贝方法通过判断属性的值类型,实现了对 对象数组 以及 DateRegExp 等引用类型对象的递归拷贝,同时也考虑了拷贝基本类型值的情况,能够满足大多数场景的要求。

最终版

为什么还有最终版?

上面的案例,可以应对一般场景。

但是对于有两个对象相互拷贝的场景,会导致循环的无限递归,造成死循环!

Uncaught RangeError: Maximum call stack size exceeded

场景:

如何解决无限递归的问题?

首先我们要了解 WeakMap: WeakMap 的键名所引用的对象都是弱引用,不会被垃圾回收机制考虑,所以当对象只被WeakMap引用时,其所占用的内存会被垃圾回收。

而通过 WeakMap 记录已经拷贝过的对象,能防止循环引用导致的无限递归。

代码

实现简述:利用 WeakMap() 在属性遍历完绑定,并在每次循环时获取当前键名,如果存在则返回数据,不存在则拷贝。

function deepClone(origin, hashMap = new WeakMap()) {
    // 判断是否是对象
    if (origin == undefined || typeof origin !== 'object') return origin;
    // 判断是否是Date/RegExp类型
    if (origin instanceof Date) return new Date(origin);
    if (origin instanceof RegExp) return new RegExp(origin);
    // 判断是否已经克隆过此对象, 如果是直接返回
    const hashKey = hashMap.get(origin);
    if (hashKey) return hashKey;
    // *利用原型构造器获取新的对象 如: [], {}
    const target = new origin.constructor();
    // 将对象存入map
    hashMap.set(origin, target);
    // 循环遍历当前层数据
    for (let k in origin) {
        // 判断当前属性是否为引用类型
        if (origin.hasOwnProperty(k)) {
            target[k] = deepClone(origin[k], hashMap);
        }
    }
    return target;
}
复制代码

我们再来看一下使用最新版后的两个对象互相拷贝:

可以看到,通过使用 WeakMap 记录已经拷贝的对象,有效防止循环引用导致的栈溢出错误,是功能最完备的深拷贝实现。

总结

深拷贝可以完全拷贝一个对象,生成两个独立的且相互不影响的对象。

明白各种深拷贝实现的思路和性能差异,可以在不同场景选用最优的方案。

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

相关文章
|
24天前
|
存储 前端开发 JavaScript
前端面试题23-34
通过对 Promise 和 ECMAScript6 的深入理解,可以更好地应对现代 JavaScript 开发中的复杂异步操作和新特性,提升代码质量和开发效率。
19 2
|
19天前
|
存储 缓存 监控
2024春招小红书前端面试题分享
2024春招小红书前端面试题分享
46 3
|
24天前
|
前端开发 JavaScript 虚拟化
前端面试题12-22
ES6(ECMAScript 2015)是 JavaScript 的重要版本,引入了许多新特性和语法,提升了语言的功能和可用性。ES6 的主要特性包括箭头函数、类、模板字符串、解构赋值、默认参数、Promise、模块化、Generator 函数、async 函数、Proxy 和 Reflect 等。这些特性不仅简化了代码的编写和维护,还为开发者提供了更多的编程范式和工具。了解和掌握 ES6 的特性是现代 JavaScript 开发的必备技能。
11 1
|
24天前
|
JSON 前端开发 JavaScript
前端面试题01-11
Map是ES6引入的一种新的键值对集合数据结构,类似于对象,但键的范围不限于字符串,还可以是任何类型的值。Map保持键值对的插入顺序,提供更灵活的键值对操作方法,如`set()`、`get()`、`delete()`、`has()`等。
15 1
|
3天前
|
前端开发 JavaScript
前端深拷贝、浅拷贝,一起手撕深拷贝
【7月更文挑战第2天】JavaScript中的深拷贝和浅拷贝关乎对象复制的独立性。浅拷贝(如`Object.assign()`、扩展运算符)创建新对象,但共享引用类型属性的内存地址,导致修改新对象会影响原始对象。深拷贝(如递归复制)创建完全独立的对象副本,不受原始对象变动影响。`JSON.parse(JSON.stringify(obj))`是简单的深拷贝方法,但无法处理函数、undefined、Symbol及循环引用。手动实现深拷贝需递归遍历并处理循环引用问题,以确保复制的完整性。理解这两者差异对编写健壮的代码至关重要。
6 0
|
4天前
|
JavaScript 前端开发
JS进阶篇(前端面试题整合)(三)
JS进阶篇(前端面试题整合)(三)
12 0
|
8天前
|
存储 缓存 前端开发
谈谈前端面试中遇到的问题(一)
谈谈前端面试中遇到的问题(一)
|
9天前
|
Web App开发 存储 前端开发
技术心得记录:前端面试题汇总
技术心得记录:前端面试题汇总
|
9天前
|
缓存 前端开发 JavaScript
中高级前端面试秘籍,助你直通大厂(一)
中高级前端面试秘籍,助你直通大厂(一)
|
19天前
|
算法 前端开发 安全
面试官:前端加密怎么做?这,这,这不是后端的活儿吗?
前端加密技术概述: 前端加密主要用来保护数据在传输过程中的安全,但因浏览器环境开放性,仅能提供有限的安全性,真正安全策略需结合服务器端加密和安全协议。