关于JavaScript的浅拷贝和深拷贝
在 JS 中有一些基本类型像是Number、String、Boolean,而对象就是像这样的东西{ name: 'Larry', skill: 'Node.js' },对象跟基本类型最大的不同就在于他们的传值方式。
基本类型是按值传递,像是这样:在修改a时并不会改到b
var a = ;
var b = a;
b = ;
console.log(a);//
console.log(b);//
但对象就不同,对象传的是按引用传值:
var obj1 = { a: , b: , c: };
var obj2 = obj1;
obj2.b = ;
console.log(obj1);
// { a: 10, b: 100, c: 30 } <-- b 被改到了
console.log(obj2);
// { a: 10, b: 100, c: 30 }
复制一份obj1叫做obj2,然后把obj2.b改成100,但却不小心改到obj1.b,因为他们根本是同一个对象,这就是所谓的浅拷贝。
要避免这样的错误发生就要写成这样:
var obj1 = { a: , b: , c: };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = ;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- b 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
这样就是深拷贝,不会改到原本的obj1。
浅拷贝(Shallow Copy) VS 深拷贝(Deep Copy)

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
浅拷贝的实现方式
也就是简单地复制而已
1、简单地复制语句
<script type="text/javascript">
function simpleClone(initalObj) {
var obj = {};
for ( var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
} var obj = {
a: "hello",
b:{
a: "world",
b:
},
c:["Bob", "Tom", "Jenny"],
d:function() {
alert("hello world");
}
}
var cloneObj = simpleClone(obj);
console.log(cloneObj.b);
console.log(cloneObj.c);
console.log(cloneObj.d); cloneObj.b.a = "changed";
cloneObj.c = [, , ];
cloneObj.d = function() { alert("changed"); };
console.log(obj.b);
console.log(obj.c);
console.log(obj.d);
</script>
结果为:

2、Object.assign()
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign是ES6的新函数。Object.assign()Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
Object.assign(target, ...sources)
参数:
target:目标对象。
sources:任意多个源对象。
返回值:目标对象会被返回。
var obj = { a: {a: "hello", b: } };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"
兼容性:

需要注意的是:
Object.assign()可以处理一层的深度拷贝,如下:
var obj1 = { a: , b: , c: };
var obj2 = Object.assign({}, obj1);
obj2.b = ;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
深拷贝的实现方式
要完全复制又不能修改到原对象,这时候就要用 Deep Copy,这里会介绍几种Deep Copy 的方式。
1、手动复制
把一个对象的属性复制给另一个对象的属性
var obj1 = { a: , b: , c: };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = ;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
但这样很麻烦,要一个一个自己复制;而且这样的本质也不能算是 Deep Copy,因为对象里面也可能回事对象,如像下面这个状况:
var obj1 = { body: { a: } };
var obj2 = { body: obj1.body };
obj2.body.a = ;
console.log(obj1);
// { body: { a: 20 } } <-- 被改到了
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// true
虽然obj1跟obj2是不同对象,但他们会共享同一个obj1.body,所以修改obj2.body.a时也会修改到旧的。
2、对象只有一层的话可以使用上面的:Object.assign()函数
Object.assign({}, obj1)的意思是先建立一个空对象{},接着把obj1中所有的属性复制过去,所以obj2会长得跟obj1一样,这时候再修改obj2.b也不会影响obj1。
因为Object.assign跟我们手动复制的效果相同,所以一样只能处理深度只有一层的对象,没办法做到真正的 Deep Copy。不过如果要复制的对象只有一层的话可以考虑使用它。
3、转成 JSON 再转回来
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
var obj1 = { body: { a: } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = ;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false
这样做是真正的Deep Copy,这种方法简单易用。
但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。
也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。
var obj1 = { fun: function(){ console.log() } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 没复制
要复制的function会直接消失,所以这个方法只能用在单纯只有数据的对象。
4、递归拷贝
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
if (typeof initalObj[i] === 'object') {
obj[i] = (initalObj[i].constructor === Array) ? [] : {};
arguments.callee(initalObj[i], obj[i]);
} else {
obj[i] = initalObj[i];
}
}
return obj;
}
var str = {};
var obj = { a: {a: "hello", b: } };
deepClone(obj, str);
console.log(str.a);
上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。
为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。
改进版代码如下:
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
var str = {};
var obj = { a: {a: "hello", b: } };
deepClone(obj, str);
console.log(str.a);
5、使用Object.create()方法
直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
6、jquery
jquery 有提供一个$.extend可以用来做 Deep Copy。
var $ = require('jquery');
var obj1 = {
a: ,
b: { f: { g: } },
c: [, , ]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);
// false
7、lodash
另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。
var _ = require('lodash');
var obj1 = {
a: ,
b: { f: { g: } },
c: [, , ]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false
这个性能还不错,使用起来也很简单。
参考:
关于JavaScript的浅拷贝和深拷贝的更多相关文章
- javascript篇-浅拷贝与深拷贝
理解javascript 的浅拷贝与深拷贝,首先看一下js的数据类型: js有5种基本数据类型:undefined,null,boolean,number,string 还有一种复杂的数据类型(也叫引 ...
- 全面聊聊JavaScript的浅拷贝和深拷贝
一.背景 首先我们可以看下面这段简单的代码: var obj = {name:'程序猿',sex:'男'}; var arr = ['程序猿','程序媛']; var copyobj = o ...
- JavaScript中浅拷贝和深拷贝的区别和实现
深拷贝和浅拷贝的区别 浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存: 深拷贝(deep copy):复制并创建一个一摸一样的对象,不共 ...
- JavaScript中浅拷贝和深拷贝的区别
JavaScript数据类型 基础数据类型:保存在栈内存中的简单数据段 ,有undefined,boolean,number,string,null 引用数据类型:Array,object,Funct ...
- javascript的浅拷贝和深拷贝
1.浅拷贝:复制一份引用,所有引用对象都指向一份数据,并且都可以修改这份数据. 2.深拷贝(复杂):复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制. 这里画一个简单的图来加深理解: ...
- JavaScript 数据结构与算法之美 - 栈内存与堆内存 、浅拷贝与深拷贝
前言 想写好前端,先练好内功. 栈内存与堆内存 .浅拷贝与深拷贝,可以说是前端程序员的内功,要知其然,知其所以然. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScri ...
- Javascript中的浅拷贝和深拷贝
很多开发语言中都有浅拷贝和深拷贝的说法,这里简单区分一下它们在Javascript中的区别,以及jQuery中深拷贝的实现. 在谈浅拷贝和深拷贝之前,先要屡清楚Javascript中的按值访问和按引用 ...
- javascript中的浅拷贝和深拷贝(拷贝引用和拷贝实例)
作者:千锋教育链接:https://www.zhihu.com/question/23031215/answer/326129003来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
- JS面试题-<变量和类型>-JavaScript浅拷贝与深拷贝
前言 最开始了解到深浅拷贝是因为准备面试,但那个时候因为在学校做的项目比较少需求也比较简单,所以没有在项目中遇到这类问题,所以对这个问题就属于知道这个知识点,看过相关内容,却没有自己的总结,也没有深入 ...
随机推荐
- HTML5预学习 长期更新
HTML5 仍处于完善之中. 为 HTML5 建立的一些规则: 新特性应该基于 HTML.CSS.DOM 以及 JavaScript. 减少对外部插件的需求(比如 ...
- asp.net与Matlab类型转换(待补全)
上上篇的博客已经提到如何配置环境,即如何在asp.net中调用matlab生成的dll文件.这篇博客打算做个笔记,那就是matlab和C#数据类型如何转换.随着需求的增加,我会不断增加新的类型转换. ...
- PHP实现观察者模式
<?php //php设计模式观察者模式 /** 被观察者 */ class user implements SplSubject{ public $lognum; public $hobby; ...
- Linux环境VNC服务安装、配置与使用
前言:作为一名DBA,在创建Oracle数据库的过程中一般要使用dbca和netca图像化进行建库和创建监听(如果使用脚本建库另说),如果您身体好估计可以在瑟瑟发抖的机房中完成数据库的创建过程,由于本 ...
- Java——下拉列表框:JComboBox
import java.awt.Container; import java.awt.GridLayout; import java.awt.event.WindowAdapter; import j ...
- Java包装类
自动装箱:指开发人员可以把一个基本数据类型直接赋给对应的包装类 自动拆箱:指开发人员可以把一个包装类对象直接赋给对应的基本数据类型 要把基本数据类型称为对象的时候,需要把基本数据类型进行包装, 运用: ...
- 和安全有关的那些事(非对称加密、数字摘要、数字签名、数字证书、SSL、HTTPS及其他)
转自http://blog.csdn.net/bluishglc/article/details/7585965 对于一般的开发人员来说,很少需要对安全领域内的基础技术进行深入的研究,但是鉴于日常系统 ...
- Light Pre-Pass相关链接
Key Words: Light Pre-Pass, Deferred Lighting http://mquandt.com/blog/2010/03/light-pre-pass-round-2/ ...
- osharp3 操作日志之数据日志 控制增强
osharp3 原来的数据日志,有配置文件中有这总开关,DataLoggingEnabled,原来的程序是,这个总开关关了,就无法记录数据日志了,,如果开了,,他不管记录不记录数据日志,系统都会存数据 ...
- 巧用svn create patch(打补丁)方案解决定制版需求
最近项目定制版越来越多,维护,同步代码非常费事.以前的思路如下图: 以前的svn目录结构如下图: 这样问题有2个: 若在一个定制包中修复了其他定制包也有的bug,同步更新其他包的代码时,非常费劲+机械 ...