JavaScript的深拷贝实现

简介:   在实际开发当中,我们经常会遇到要对对象进行深拷贝的情况。而且深拷贝这个问题在面试过程中也经常会遇到,下面就对本人在学习过程中的收获,做以简单的总结。  关于浅拷贝的概念,我在网上看到一种说法,直接上代码。

  在实际开发当中,我们经常会遇到要对对象进行深拷贝的情况。而且深拷贝这个问题在面试过程中也经常会遇到,下面就对本人在学习过程中的收获,做以简单的总结。

  关于浅拷贝的概念,我在网上看到一种说法,直接上代码。

  var person={name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};var person1=person; //他们认为这是浅拷贝

  但是我个人认为,上面这个根本不涉及拷贝,只是一个简单的引用赋值。以我的理解,浅拷贝应该是不考虑对象的引用类型的属性,只对当前对象的所有成员进行拷贝,代码如下:

  function copy(obj){ var objCopy={}; for(var key in obj){ objCopy[key]=obj[key]; } return objCopy;}var person={name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};var personCopy=copy(person);

  上面这段代码中,person对象拥有两个基本类型的属性name和age,一个引用类型的属性car,当使用如上方法进行拷贝的时候,name和age属性会被正常的拷贝,但是car属性,只会进行引用的拷贝,这样会导致拷贝出来的对象personCopy和person会共用一个car对象。这样就是所谓的浅拷贝。

  深拷贝的就是在拷贝的时候,需要将当前要拷贝的对象内的所有引用类型的属性进行完整的拷贝,也就是说拷贝出来的对象和原对象之间没有任何数据是共享的,所有的东西都是自己独占的一份。

  实现深拷贝需要考虑如下几个因素:

  传入的对象是使用对象字面量{}创建的对象还是由构造函数生成的对象如果对象是由构造函数创建出来的,那么是否要拷贝原型链上的属性如果要拷贝原型链上的属性,那么如果原型链上存在多个同名的属性,保留哪个处理循环引用的问题

  我们可以通过$.extend()方法来完成深复制。值得庆幸的是,我们在jQuery中可以通过添加一个参数来实现递归extend。调用$.extend(true, {}, ...)就可以实现深复制,参考下面的例子:

  var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ]};var y = $.extend({}, x), //shallow copy z = $.extend(true, {}, x); //deep copyy.b.f === x.b.f // truez.b.f === x.b.f // false

  但是jQuery的这个$.extend()方法,有弊端,什么弊端呢?我们看下面的例子:

  var objA={};var objB={};objA.b=objB;objB.a=objA;$.extend(true,{},a);//这个时候就出现异常了//Uncaught RangeError: Maximum call stack size exceeded(…)

  也就是说,jQuery中的$.extend()并没有处理循环引用的问题。

  使用JSON全局对象的parse和stringify方法来实现深复制也算是一个简单讨巧的方法。

  function jsonClone(obj) { return JSON.parse(JSON.stringify(obj));}var clone=jsonClone({ a:1 });

  然而使用这种方法会有一些隐藏的坑,它能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。

  下面我们给出一个简单的二手设备网解决方案,当然这个方案是参考别人的方式来实现的。希望对大家有用。

  var clone=(function() { //这个方法用来获取对象的类型 返回值为字符串类型 "Object RegExp Date Array..." var classof=function(o) { if (o===null) { return "null"; } if (o===undefined) { return "undefined"; } // 这里的Object.prototype.toString很可能用的就是Object.prototype.constructor.name // 这里使用Object.prototype.toString来生成类型字符串 var className=Object.prototype.toString.call(o).slice(8, -1); return className; }; //这里这个变量我们用来存储已经保存过的属性,目的在于处理循环引用的问题 var references=null; //遇到不同类型的对象的处理方式 var handlers={ //正则表达式的处理 'RegExp': function(reg) { var flags=''; flags +=reg.global ? 'g' : ''; flags +=reg.multiline ? 'm' : ''; flags +=reg.ignoreCase ? 'i' : ''; return new RegExp(reg.source, flags); }, //时间对象处理 'Date': function(date) { return new Date(+date); }, //数组处理 第二个参数为是否做浅拷贝 'Array': function(arr, shallow) { var newArr=[], i; for (i=0; i < arr.length; i++) { if (shallow) { newArr[i]=arr[i]; } else { //这里我们通过reference数组来处理循环引用问题 if (references.indexOf(arr[i]) !==-1) { continue; } var handler=handlers[classof(arr[i])]; if (handler) { references.push(arr[i]); newArr[i]=handler(arr[i], false); } else { newArr[i]=arr[i]; } } } return newArr; }, //正常对象的处理 第二个参数为是否做浅拷贝 'Object': function(obj, shallow) { var newObj={}, prop, handler; for (prop in obj) { //关于原型中属性的处理太过复杂,我们这里暂时不做处理 //所以只对对象本身的属性做拷贝 if (obj.hasOwnProperty(prop)) { if (shallow) { newObj[prop]=obj[prop]; } else { //这里还是处理循环引用的问题 if (references.indexOf(obj[prop]) !==-1) { continue; } handler=handlers[classof(obj[prop])]; //如果没有对应的处理方式,那么就直接复制 if (handler) { references.push(obj[prop]); newObj[prop]=handler(obj[prop], false); } else { newObj[prop]=obj[prop]; } } } } return newObj; } }; return function(obj, shallow) { //首先重置我们用来处理循环引用的这个变量 references=[]; //我们默认处理为浅拷贝 shallow=shallow===undefined ? true : false; var handler=handlers[classof(obj)]; return handler ? handler(obj, shallow) : obj; };}());(function() { //下面是一些测试代码 var date=new Date(); var reg=/hello word/gi; var obj={ prop: 'this ia a string', arr: [1, 2, 3], o: { wow: 'aha' } }; var refer1={ arr: [1, 2, 3] }; var refer2={ refer: refer1 }; refer1.refer=refer2; var cloneDate=clone(date, false); var cloneReg=clone(reg, false); var cloneObj=clone(obj, false); alert((date !==cloneDate) && (date.valueOf()===cloneDate.valueOf())); alert((cloneReg !==reg) && (reg.toString()===cloneReg.toString())); alert((obj !==cloneObj) && (obj.arr !==cloneObj.arr) && (obj.o !==cloneObj.o) && (JSON.stringify(obj)===JSON.stringify(cloneObj))); clone(refer2, false); alert("I'm not dead yet!"); // Output: // true // true // true // I'm not dead yet!}());

目录
相关文章
|
消息中间件 缓存 监控
Kafka中的Controller(控制器)节点
Kafka中的Controller(控制器)节点
1450 0
Kafka中的Controller(控制器)节点
|
5月前
|
人工智能 自然语言处理 安全
学不会编程也能写测试?AI让测试更平权
在传统的软件开发体系中,测试常被划分为“技术型测试”(如自动化、性能、安全)和“业务型测试”(如功能验证、用户体验)。前者掌握技术话语权,后者则更多依赖经验和流程规范。然而,随着大语言模型(LLM)等AI技术的迅猛发展,这一固有格局正被悄然打破:
162 10
|
5月前
|
Kubernetes Linux Go
使用 Uber automaxprocs 正确设置 Go 程序线程数
`automaxprocs` 包就是专门用来解决此问题的,并且用法非常简单,只需要使用匿名导入的方式 `import _ "go.uber.org/automaxprocs"` 一行代码即可搞定。
247 78
|
机器学习/深度学习 算法
贝叶斯线性回归:概率与预测建模的融合
本文探讨了贝叶斯方法在线性回归中的应用,从不确定性角度出发,介绍了如何通过概率来表达变量间关系的不确定性。文章首先回顾了古希腊天文学家使用本轮系统模拟行星运动的历史,并将其与傅里叶级数分解方法类比,强调了近似的重要性。接着,通过高斯分布和贝叶斯推断,详细讲解了线性回归中的不确定性处理方法。文章使用Howell1数据集,展示了如何构建和拟合高斯模型,并通过先验预测模拟验证模型合理性。最后,介绍了多项式回归和样条方法,展示了如何逐步增加模型复杂性以捕捉更细微的数据模式。贝叶斯方法不仅提供了点估计,还提供了完整的后验分布,使得模型更具解释性和鲁棒性。
317 1
贝叶斯线性回归:概率与预测建模的融合
|
5月前
|
XML 安全 前端开发
一行代码搞定禁用 web 开发者工具
在如今的互联网时代,网页源码的保护显得尤为重要,特别是前端代码,几乎就是明文展示,很容易造成源码泄露,黑客和恶意用户往往会利用浏览器的开发者工具来窃取网站的敏感信息。为了有效防止用户打开浏览器的 Web 开发者工具面板,今天推荐一个不错的 npm 库,可以帮助开发者更好地保护自己的网站源码,本文将介绍该库的功能和使用方法。 功能介绍 npm 库名称:disable-devtool,github 路径:/theajack/disable-devtool。从 f12 按钮,右键单击和浏览器菜单都可以禁用 Web 开发工具。 🚀 一行代码搞定禁用 web 开发者工具 该库有以下特性: • 支持可配
262 22
|
5月前
|
JavaScript 前端开发 编译器
Vue与TypeScript:如何实现更强大的前端开发
Vue.js 以其简洁的语法和灵活的架构在前端开发中广受欢迎,而 TypeScript 作为一种静态类型语言,为 JavaScript 提供了强大的类型系统和编译时检查。将 Vue.js 与 TypeScript 结合使用,不仅可以提升代码的可维护性和可扩展性,还能减少运行时错误,提高开发效率。本文将介绍如何在 Vue.js 项目中使用 TypeScript,并通过一些代码示例展示其强大功能。
217 22
|
5月前
|
存储 监控 安全
C语言与操作系统交互探秘
系统调用与库函数 在 C语言中,系统调用是用户程序与操作系统内核交互的桥梁。以下是常见系统调用的概述: 文件操作类:open()、read()、write()、close()、lseek() 进程控制类:fork()、exec()、wait()、exit() 信号处理类:signal()、kill() 进程间通信:pipe()、shmget()、msgget() 网络通信:socket()、bind()、listen()、accept() 系统调用 vs 库函数:
161 20
|
5月前
|
JSON 机器人 API
gewe微信机器人搭建教程
GeWe开放平台是基于 微信开放平台的二次封装API服务,开发者可以使用本服务来处理微信中的各种事件,并可以通过后台调用对应的 API 来驱动微信自动执行任务,如自动收发消息、自动化应答、自动群邀请、群管理等,封装了 RPA技术流程,简化开发者二次开发难度,提供了开发者与微信对接的能力,使用简单,操作快捷,支持多种语言接入。
205 17
|
5月前
|
数据采集 安全 BI
用Python编程基础提升工作效率
一、文件处理整明白了,少加两小时班 (敲暖气管子)领导让整理100个Excel表?手都干抽筋儿了?Python就跟铲雪车似的,哗哗给你整利索!
126 11
|
存储 缓存 Java
涨姿势啦!Go语言中正则表达式初始化的最佳实践
在Go语言中,正则表达式是处理字符串的强大工具,但其编译过程可能消耗较多性能。本文探讨了正则表达式编译的性能影响因素,包括解析、状态机构建及优化等步骤,并通过示例展示了编译的时间成本。为了优化性能,推荐使用预编译策略,如在包级别初始化正则表达式对象或通过`init`函数进行错误处理。此外,简化正则表达式和分段处理也是有效手段。根据初始化的复杂程度和错误处理需求,开发者可以选择最适合的方法,以提升程序效率与可维护性。
172 0
涨姿势啦!Go语言中正则表达式初始化的最佳实践