Javascript 中的深浅拷贝
工作中经常会遇到需要复制 JS 数据的时候,遇到 bug 时实在令人头疼;面试中也经常会被问到如何实现一个数据的深浅拷贝,但是你对其中的原理清晰吗?一起来看一下吧!
为什么会有深浅拷贝
想要更加透彻的理解为什么 JS 会有深浅拷贝,需要先了解下 JS 的数据类型有哪些,一般分为基本类型(Number、String、Null、Undefined、Boolean、Symbol )和引用类型(对象、数组、函数)。
基本类型是不可变的,任何方法都无法改变一个基本类型的值,也不可以给基本类型添加属性或者方法。但是可以为引用类型添加属性和方法,也可以删除其属性和方法。
基本类型和引用类型在内存中的存储方式也大不相同,基本类型保存在栈内存中,而引用类型保存在堆内存中。为什么要分两种保存方式呢? 因为保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是我们可以把它的地址写在栈内存中以供我们访问。
说来这么多,我们来看个示例:
let num1 = 10;
let obj1 = {
name: "hh"
}
let num2 = num1;
let obj2 = obj1;
num2 = 20;
obj2.name = "kk";
console.log(num1); // 10
console.log(obj1.name); // kk
执行完这段代码,内存空间里是这样的:
可以看到 obj1 和 obj2 都保存了一个指向该对象的指针,所有的操作都是对该引用的操作,所以对 obj2 的修改会影响 obj1。
小结:
之所以会出现深浅拷贝,是由于 JS 对基本类型和引用类型的处理不同。基本类型指的是简单的数据段,而引用类型指的是一个对象保存在堆内存中的地址,JS 不允许我们直接操作内存中的地址,也就是说不能操作对象的内存空间,所以,我们对对象的操作都只是在操作它的引用而已。
在复制时也是一样,如果我们复制一个基本类型的值时,会创建一个新值,并把它保存在新的变量的位置上。而如果我们复制一个引用类型时,同样会把变量中的值复制一份放到新的变量空间里,但此时复制的东西并不是对象本身,而是指向该对象的指针。所以我们复制引用类型后,两个变量其实指向同一个对象,所以改变其中的一个对象,会影响到另外一个。
深浅拷贝
1. 浅拷贝
浅拷贝只是复制基本类型的数据或者指向某个对象的指针,而不是复制对象本身,源对象和目标对象共享同一块内存;若对目标对象进行修改,存在源对象被篡改的可能。
我们来看下浅拷贝的实现:
/* sourceObj 表示源对象
* 执行完函数,返回目标对象
*/
function shadowClone (sourceObj = {}) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let copy;
for (var key in sourceObj) {
copy = sourceObj[key];
targetObj[key] = copy;
}
return targetObj;
}
// 定义 source
let sourceObj = {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr1'}, 1],
func: () => 'sourceFunc1',
obj: {
string: 'obj1',
func: () => 'objFunc1'
}
}
// 拷贝sourceObj
let copyObj = shadowClone(sourceObj);
// 修改 sourceObj
copyObj.number = 2;
copyObj.string = 'source2';
copyObj.boolean = false;
copyObj.arr[0].name = 'arr2';
copyObj.func = () => 'sourceFunc2';
copyObj.obj.string = 'obj2';
copyObj.obj.func = () => 'objFunc2';
// 执行
console.log(sourceObj);
/* {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr2'}],
func: () => 'sourceFunc1',
obj: {
func: () => 'objFunc2',
string: 'obj2'
}
}
*/
2. 深拷贝
深拷贝能够实现真正意义上的对象的拷贝,实现方法就是递归调用“浅拷贝”。深拷贝会创造一个一模一样的对象,其内容地址是自助分配的,拷贝结束之后,内存中的值是完全相同的,但是内存地址是不一样的,目标对象跟源对象不共享内存,修改任何一方的值,不会对另外一方造成影响。
/* sourceObj 表示源对象
* 执行完函数,返回目标对象
*/
function deepClone (sourceObj = {}) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let copy;
for (var key in sourceObj) {
copy = sourceObj[key];
if (typeof(copy) === 'object') {
if (copy instanceof Object) {
targetObj[key] = deepClone(copy);
} else {
targetObj[key] = copy;
}
} else if (typeof(copy) === 'function') {
targetObj[key] = eval(copy.toString());
} else {
targetObj[key] = copy;
}
}
return targetObj;
}
// 定义 sourceObj
let sourceObj = {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr1'}],
func: () => 'sourceFunc1',
obj: {
string: 'obj1',
func: () => 'objFunc1'
}
}
// 拷贝sourceObj
let copyObj = deepClone(sourceObj);
// 修改 source
copyObj.number = 2;
copyObj.string = 'source2';
copyObj.boolean = false;
copyObj.arr[0].name = 'arr2';
copyObj.func = () => 'sourceFunc2';
copyObj.obj.string = 'obj2';
copyObj.obj.func = () => 'objFunc2';
// 执行
console.log(sourceObj);
/* {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr1'}],
func: () => 'sourceFunc1',
obj: {
func: () => 'objFunc1',
string: 'obj1'
}
}
*/
两个方法可以合并在一起:
/* deep 为 true 表示深复制,为 false 表示浅复制
* sourceObj 表示源对象
* 执行完函数,返回目标对象
*/
function clone (deep = true, sourceObj = {}) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let copy;
for (var key in sourceObj) {
copy = sourceObj[key];
if (deep && typeof(copy) === 'object') {
if (copy instanceof Object) {
targetObj[key] = clone(deep, copy);
} else {
targetObj[key] = copy;
}
} else if (deep && typeof(copy) === 'function') {
targetObj[key] = eval(copy.toString());
} else {
targetObj[key] = copy;
}
}
return targetObj;
}
技巧
1. 浅拷贝技巧
(1)可以使用 concat() 和 slice() 方法来实现数组的浅拷贝:
let a = [1, {name: 'hh1'}];
let b = [2, {name: 'kk1'}];
let copy = a.concat(b);
copy[1].name = 'hh2';
copy[3].name = 'kk2';
console.log(copy);
// [1, {name: 'hh2'}, 2, {name: 'kk2'}]
无论 a[1].name 或者 b[1].name 改变,copy[1].name 的值都会改变。
let a = [1, {name: 'hh1'}];
let copy = a.slice();
copy[1].name = 'hh2';
console.log(a);
// [1, {name: 'hh2'}]
改变了 a[1].name 后,copy[1].name 的值也改变了。
(2)可以使用 Object.assign() 来实现对象的浅拷贝:
Object.assign() 是 ES6 的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。Object.assign() 拷贝的是对象的属性的引用,而不是对象本身。
let sourceObj = {
str: 'hh',
number: 10,
obj: {
str: 'kk1'
}
}
let targetObj = Object.assign({}, sourceObj)
targetObj.obj.str = 'kk2'
console.log(sourceObj);
// {
// str: 'hh',
// number: 10,
// obj: {
// str: 'kk2'
// }
// }
修改了 targetObj.obj.str 的值之后,sourceObj.obj.str 的值也改变了。
2. 深拷贝技巧
(1)转成 JSON 再转回来:
用 JSON.stringify() 把对象转成字符串,再用 JSON.parse() 把字符串转成新的对象。
let source = ['hh', 1, [2, 3], {name: 'kk1'}];
let copy = JSON.parse(JSON.stringify(source));
copy[2][1] = 4;
copy[3].name = 'kk2';
console.log(source);
// ['hh', 1, [2, 3], {name: 'kk1'}]
可以看出,虽然改变了 copy[2].name 的值,但是 source[2].name 的值没有改变。
JSON.parse(JSON.stringify(obj)) 不仅能复制数组还可以复制对象,但是几个弊端:
1)它会抛弃对象的 constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成 Object;
2)这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp 对象是无法通过这种方式深拷贝。
3)只有可以转成 JSON 格式的对象才可以这样用,像 function 没办法转成 JSON。
(2)使用Object.create()方法:
直接使用let targetObj = Object.create(sourceObj),可以达到深拷贝的效果。
let sourceObj = {name: 'hh1'};
let targetObj = Object.create(sourceObj);
targetObj.name = 'hh2';
console.log(sourceObj);
// {name: 'hh1'}
修改 targetObj.name 的值后,sourceObj.name 的值没有跟着变化。
3. 可以使用的库
jQuery
具体使用可以参考:官方文档
Lodash
具体使用可以参考:官方文档
来源:https://segmentfault.com/a/1190000017469386
Javascript 中的深浅拷贝的更多相关文章
- JavaScript中的深浅拷贝
深浅拷贝 在JS中,数据类型分为两类: 简单数据类型:Number.Boolean.String.undefined 引用数据类型:Array.Object.Function 简单数据类型通常 ...
- javascript中的对象拷贝
js中的数据类型 在介绍javascript中的对象的拷贝之前,我先介绍一个基础的东西,javascript中的数据类型. 我们做前端的应该都知到在es6 之前,javascript中的数据类型Boo ...
- js中的深浅拷贝
js中的深浅拷贝 js中有深拷贝.浅拷贝一说,所谓的深浅拷贝是针对value类型为引用类型(函数.对象.数组)而言的,大概理解的就是: 浅拷贝: 拷贝出的对象c和原始对象o,c和o在key对应的val ...
- 天啦噜!仅仅5张图,彻底搞懂Python中的深浅拷贝
Python中的深浅拷贝 在讲深浅拷贝之前,我们先重温一下 is 和==的区别. 在判断对象是否相等比较的时候我们可以用is 和 == is:比较两个对象的引用是否相同,即 它们的id 是否一样 == ...
- Python 中的深浅拷贝
Python 中的深浅拷贝 参考文献:https://blog.csdn.net/lnotime/article/details/81194633 参考文献:https://blog.csdn.net ...
- javascript简单实现深浅拷贝
深浅拷贝知识在我们的日常开发中还算是用的比较多,但是之前的状态一直都是只曾听闻,未曾使用(其实用了只是自己没有意识到),所以今天来跟大家聊一聊js的深浅拷贝: 首先我们来了解一下javascript的 ...
- Python中的深浅拷贝
1.什么是深浅拷贝? python中一切皆对象,python中的数字.字符串.元组等,如果存放在了内存中,这部分内存里面的内容是不会改变的,但是也有情况,内存中存放了可变对象,比如说列表和字典,他们的 ...
- Core Python Programming一书中关于深浅拷贝的错误
该书关于深浅拷贝的论述: 6.20. *Copying Python Objects and Shallow and Deep Copies "when shallow copies are ...
- Python基础【3】:Python中的深浅拷贝解析
深浅拷贝 在研究Python的深浅拷贝区别前需要先弄清楚以下的一些基础概念: 变量--引用--对象(可变对象,不可变对象) 切片(序列化对象)--拷贝(深拷贝,浅拷贝) 我是铺垫~ 一.[变量--引用 ...
随机推荐
- mongdb 数据库
安装mongdb 下载地址 https://www.runoob.com/mongodb/mongodb-window-install.html 检查 mongdb 是否安装成功which mongd ...
- Sublime Text3 使用Package Control 报错There Are No Packages Available For Installation 解决
"channels": [ "https://packagecontrol.io/channel_v3.json"], 无法连接的问题 网上说了挺多原因,简单例 ...
- JSP上传整个文件夹
文件上传是最古老的互联网操作之一,20多年来几乎没有怎么变化,还是操作麻烦.缺乏交互.用户体验差. 一.前端代码 英国程序员Remy Sharp总结了这些新的接口 ,本文在他的基础之上,讨论在前端采用 ...
- CodeForces 1187D Subarray Sorting
Problem You are given an array \(a_1\),\(a_2\),-,\(a_n\) and an array \(b_1\),\(b_2\),-,\(b_n\). For ...
- 解决:@Auarowired为null
使用@Auarowired时程序报空指针.如图: 将private 更改为 public 即可
- C/C++头文件的编写
在C语言的学习过程中,我们一般把所有的代码写在一个文件中.随着自身水平的提高,我们发现代码越写越长,代码行数越来越多,把一个工程的所有代码写在一个文件中让人看起开非常吃力.于是我们开始想把代码中的函数 ...
- [Codeforces 274E]:Mirror Room(模拟)
题目传送门 题目描述 有一个$n\times m$的格子图,其中有一些是黑色的,另一些为白色.从某个白色格子的中心点向左上($NW$),左下($SW$),右上($NE$),右下($SE$)四个方向中的 ...
- 如何实现echarts组织结构图节点的收缩
echarts本身没有组织结构图的节点收缩功能,因为项目需求要用到此功能. 引入的echarts必须是2版本的,因为3.0取消了对组织结构图的支持.下载2版本的源码,找到关于onclick事件那部分的 ...
- 手把手教你做echarts图表系列之组织结构图
在实际项目中使用echarts越来越多了,今天从一个组织结构图开始,手把手教大家开发echarts图表. 公司里的组织结构图如下: 可以参考echarts入门教程:http://echarts.bai ...
- Chrome谷歌页面翻译增强插件开发
最近想做一个Chrome的插件(看别的博客说其实叫插件不准确,应该叫拓展,大家叫习惯了就按习惯的来吧).一开始咱先直接看了Chrome开发(360翻译)和chrome extensions(这个官方的 ...