JavaScript深度复制(deep clone)的实现方法


Posted in Javascript onFebruary 19, 2016

在代码复用模式里面有一种叫做“复制属性模式”(copying properties pattern)。谈到代码复用的时候,很有可能想到的是代码的继承性(inheritance),但重要的是要记住其最终目标——我们要复用代码。继承性只是实现代码复用的一种手段,而不是唯一的方法。复制属性也是一种复用模式,它跟继承性是有所不同的。这种模式中,对象将从另外一个在对象中获取成员,其方法是仅需将其复制即可。用过jQuery的都知道,它有一个$.extend()方法,它的用途除了扩展第三方插件之外,还可以用来复制属性的。下面我们来看一个extend()函数的实现代码(注意这里的并不是jQuery的源码,只是一个简单的示例):

function extend(parent, child) {
var i;
//如果不传入第二参数child
//那么就创建一个新的对象
child = child || {}; 
//遍历parent对象的所有属性
//并且过滤原型上的属性
//然后将自身属性复制到child对象上
for(i in parent) {
if(parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
//返回目标对象child
return child;
}

上面的代码是一个简单的实现,它仅遍历父对象的成员并将其复制到子对象中去。下面我们用上面的extend()方法来测试一下:

var dad = {name: "Adam"};
var kid = extend(dad);
console.log(kid.name); //Adam

我们发现,extend()方法已经可以正常工作了。但是有一个问题,上面给出的是一种所谓的浅复制(shallow clone)。在使用浅复制的时候,如果改变了子对象的属性,并且该属性恰好又是一个对象,那么这种操作也会修改父对象,单是很多情况这不是我们想要的结果。考虑下列情况:

var dad = {
counts: [1, 2, 3],
reads: {paper: true}
};
var kid = extend(dad) //调用extend()方法将dad的属性复制到kid上面
kid.counts.push(4); //把4追加到kid.counts数组里面
console.log(dad.counts); //[1, 2, 3, 4]

通过上面的例子,我们会发现,修改了kid.counts属性以后(把元素4追加进去了),dad.counts也会受到影响。这是因为在使用浅复制的时候,由于对象是通过引用传递的,即kid.counts和dad.counts指向的是同一个数组(或者说在内存上他们指向同一个堆的地址)。

下面,让我们修改extend()函数以实现深度复制。我们需要做的事情就是检查父对象的每一个属性,如果该属性恰好是对象的话,那么就递归复制出该对象的属性。另外,还需要检测该对象是否为一个数组,这是因为数组的字面量创建方式和对象的字面量创建方式不一样,前者是[],后者是{}。检测数组可以使用Object.prototype.toString()方法进行检测,如果是数组的话,他会返回"[object Array]"。下面我们来看一下深度复制版本的extend()函数:

function extendDeep(parent, child) {
child = child || {};
for(var i in parent) {
if(parent.hasOwnProperty(i)) {
//检测当前属性是否为对象
if(typeof parent[i] === "object") {
//如果当前属性为对象,还要检测它是否为数组
//这是因为数组的字面量表示和对象的字面量表示不同
//前者是[],而后者是{}
child[i] = (Object.prototype.toString.call(parent[i]) === "[object Array]") ? [] : {};
//递归调用extend
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}

好了,深度复制的函数已经写好了,下面来测试一下看是否能够预期那样子工作,即是否可以实现深度复制:

var dad = {
counts: [1, 2, 3],
reads: {paper: true}
};
var kid = extendDeep(dad);
//修改kid的counts属性和reads属性
kid.counts.push(4);
kid.reads.paper = false;
console.log(kid.counts); //[1, 2, 3, 4]
console.log(kid.reads.paper); //false
console.log(dad.counts); //[1, 2, 3]
console.log(dad.reads.paper); //true

通过上面例子,我们可以发现,即使修改了子对象的kid.counts和kid.reads,父对象的dad.counts和kid.reads并没有改变,因此我们的目的实现了。

下面来总结一下实现深复制的的基本思路:

1.检测当前属性是否为对象

2.因为数组是特殊的对象,所以,在属性为对象的前提下还需要检测它是否为数组。

3.如果是数组,则创建一个[]空数组,否则,创建一个{}空对象,并赋值给子对象的当前属性。然后,递归调用extendDeep函数。

上面例子使我们自己使用递归算法实现的一种深度复制方法。事实上,ES5新增的JSON对象提供的两个方法也可以实现深度复制,分别是JSON.stringify()和JSON.parse();前者用来将对象转成字符串,后者则把字符串转换成对象。下面我们使用该方法来实现一个深度复制的函数:

function extendDeep(parent, child) {
var i,
proxy;
proxy = JSON.stringify(parent); //把parent对象转换成字符串
proxy = JSON.parse(proxy) //把字符串转换成对象,这是parent的一个副本
child = child || {};
for(i in proxy) {
if(proxy.hasOwnProperty(i)) {
child[i] = proxy[i];
}
}
proxy = null; //因为proxy是中间对象,可以将它回收掉
return child;
}

下面是测试例子:

var dad = {
counts: [1, 2, 3],
reads: {paper: true}
};
var kid = extendDeep(dad);
//修改kid的counts属性和reads属性
kid.counts.push(4);
kid.reads.paper = false;
console.log(kid.counts); //[1, 2, 3, 4]
console.log(kid.reads.paper); //false
console.log(dad.counts); //[1, 2, 3]
console.log(dad.reads.paper); //true

测试发现,它也实现了深度复制。一般推荐使用后面这种方法,因为JSON.parse和JSON.stringify是内置函数,处理起来会比较快。另外,前面的那种方法使用了递归调用,我们都知道,递归是效率比较低的一种算法。

关于JavaScript深度复制(deep clone)的实现方法就给大家介绍这么多,希望对大家有所帮助!

Javascript 相关文章推荐
javascript 强制刷新页面的实现代码
Dec 13 Javascript
一行代码告别document.getElementById
Jun 01 Javascript
使用POST方式弹出窗口的两种方法示例介绍
Jan 29 Javascript
JS获取网页属性包括宽、高等等
Apr 03 Javascript
jQuery如何取id有.的值一般的方法是取不到的
Apr 18 Javascript
基于jQuery实现文本框缩放以及上下移动功能
Nov 24 Javascript
JavaScript中iframe实现局部刷新的几种方法汇总
Jan 06 Javascript
理解javascript定时器中的setTimeout与setInterval
Feb 23 Javascript
Javascript前端经典的面试题及答案
Mar 14 Javascript
Vue2.0实现组件之间数据交互和通信操作示例
May 16 Javascript
js实现图片无缝循环轮播
Oct 28 Javascript
VUE UPLOAD 通过ACTION返回上传结果操作
Sep 07 Javascript
百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换
Feb 19 #Javascript
基于JavaScript实现弹出框效果
Feb 19 #Javascript
jQuery on()绑定动态元素出现的问题小结
Feb 19 #Javascript
学习javascript文件加载优化
Feb 19 #Javascript
初识angular框架后的所思所想
Feb 19 #Javascript
复杂的javascript窗口分帧解析
Feb 19 #Javascript
javascript轻量级库createjs使用Easel实现拖拽效果
Feb 19 #Javascript
You might like
用php或asp创建网页桌面快捷方式的代码
2010/03/23 PHP
使用PHP curl模拟浏览器抓取网站信息
2013/10/28 PHP
Yii2简单实现给表单添加验证码的方法
2016/07/18 PHP
PHP获取客户端及服务器端IP的封装类
2016/07/21 PHP
Yii2框架BootStrap样式的深入理解
2016/11/07 PHP
关于JavaScript的gzip静态压缩方法
2007/01/05 Javascript
Jquery 快速构建可拖曳的购物车DragDrop
2009/11/30 Javascript
Javascript delete 引用类型对象
2013/11/01 Javascript
JavaScript实现按Ctrl键打开新页面
2014/09/04 Javascript
下拉框select的绑定示例
2014/09/04 Javascript
使用JQ来编写最基本的淡入淡出效果附演示动画
2014/10/31 Javascript
WordPress 单页面上一页下一页的实现方法【附代码】
2016/03/10 Javascript
indexedDB bootstrap angularjs之 MVC DOMO (应用示例)
2016/06/20 Javascript
浅谈Vue.js 组件中的v-on绑定自定义事件理解
2017/11/17 Javascript
小程序开发基础之view视图容器
2018/08/21 Javascript
vue自定义指令实现方法详解
2019/02/11 Javascript
es6函数之严格模式用法实例分析
2020/03/17 Javascript
js面试题之异步问题的深入理解
2020/09/20 Javascript
解决vue scoped html样式无效的问题
2020/10/24 Javascript
使用Python保存网页上的图片或者保存页面为截图
2016/03/05 Python
python学习笔记--将python源文件打包成exe文件(pyinstaller)
2018/05/26 Python
Python从ZabbixAPI获取信息及实现Zabbix-API 监控的方法
2018/09/17 Python
Python创建一个空的dataframe,并循环赋值的方法
2018/11/08 Python
python读取图片任意范围区域
2019/01/23 Python
Python3实现的回文数判断及罗马数字转整数算法示例
2019/03/27 Python
pycharm工具连接mysql数据库失败问题
2020/04/01 Python
Python如何避免文件同名产生覆盖
2020/06/09 Python
Python自动化之UnitTest框架实战记录
2020/09/08 Python
通过Python pyecharts输出保存图片代码实例
2020/11/25 Python
css3 图片圆形显示 如何CSS将正方形图片显示为圆形图片布局
2014/10/10 HTML / CSS
使用SVG实现提示框功能的示例代码
2020/06/05 HTML / CSS
Under Armour安德玛法国官网:美国高端运动科技品牌
2018/06/29 全球购物
基层党建工作宣传标语
2014/06/24 职场文书
2016年大学生党员承诺书
2016/03/24 职场文书
Python爬虫实战之爬取京东商品数据并实实现数据可视化
2021/06/07 Python
Python常用配置文件ini、json、yaml读写总结
2021/07/09 Python