背景

在日常开发中,偶尔会遇到需要复制对象的情况,需要进行对象的复制。

由于现在流行标题党,所以,一文带你了解js数据储存及深复制(深拷贝)与浅复制(浅拷贝)

理解

首先就需要理解 js 中的数据类型了

js 数据类型包含

  1. 基础类型:StringNumbernullundefinedBoolean以及ES6引入的Symboles10中的BigInt
  2. 引用类型:Object

由于 js 对变量的储存是栈内存堆内存完成的。

  • 基础类型将数据保存在栈内存
  • 引用类型将数据保存在堆内存

由于 js 在数据读取和写入的时候,对基础类型是直接读写栈内存中的数据,引用类型是将一个内存地址保存在栈内存中,读写都是修改栈内存中,指向堆内存的地址

以如下代码为例

let obj = {
a:1,
arr:[1,3,5,7,9],
b:2,
c:{
num:100
}
}
let num = 10

在内存中的表现为

我们声明个obj1

let obj1 = obj;
console.log(obj1 == obj);//true

因为这个赋值,把内存变成了这样

然后,内存中只是给js栈内存新增了一个指向堆内存的地址而已,这种就叫做浅复制。因为如图可以看到,如果我们修改obj.a的话,实际修改的是堆内存0x88888888中的变量a,由于obj1也指向这个地址,所以obj1.a也被修改了

深复制是指,不单单复制引用地址,连堆内存都复制一遍,使objobj1不指向同一个地址。

代码

分开来看深复制浅复制

浅复制

由上述图可知,浅复制只是复制第一层,也就是,基本类型复制新值,引用类型复制引用地址

浅复制可以使用的方案有循环赋值扩展运算符object.assign(),

let obj = {
a:1,
arr:[1,3,5,7,9],
b:2,
c:{
num:100
}
} function clone1(obj){ // 使用循环赋值
let b = {};
for(let key in obj){
b[key] = obj[key]
}
return b
}
function clone2(obj){ // 使用扩展运算符
let b = {
...obj
};
return b
}
function clone3(obj){ // 使用object.assign()
let b = {};
Object.assign(b,obj)
return b
}
let obj1 = clone1(obj);
let obj2 = clone2(obj);
let obj3 = clone3(obj); console.log(obj1 === obj); //false 代表复制成功了
console.log(obj2 === obj); //false 代表复制成功了
console.log(obj3 === obj); //false 代表复制成功了 console.log('obj0.c.num修改前',obj.c.num); //100
console.log('obj1.c.num修改前',obj1.c.num); //100
console.log('obj2.c.num修改前',obj2.c.num); //100
console.log('obj3.c.num修改前',obj3.c.num); //100 obj0.c.num = 555; console.log('obj0.c.num修改后',obj.c.num); //555
console.log('obj1.c.num修改后',obj1.c.num); //555
console.log('obj2.c.num修改后',obj2.c.num); //555
console.log('obj3.c.num修改后',obj3.c.num); //555

由于是浅复制,所以引用类型只是复制了内存地址,修改其中一个对象的子属性后,引用这个地址的值都会被修改。

深复制

由于浅复制只是复制第一层,为了解决引用类型的复制,需要使用深复制来完成对象的复制,基本类型复制新值,引用类型开辟新的堆内存

浅复制可以使用的方案有JSON.parse(JSON.stringify(obj))循环赋值

JSON.parse(JSON.stringify(obj))

let obj = {
a:1,
arr:[1,3,5,7,9],
c:{
num:100
},
fn:function(){
console.log(1)
},
date:new Date(),
reg:/\.*/g
} function clone1(obj){ // 使用JSON.parse(JSON.stringify(obj))
return JSON.parse(JSON.stringify(obj))
}
let obj1 = clone1(obj);
console.log(obj === obj1); //false 代表复制成功了
obj.c.num = 555; console.log(obj.c.num,obj1.c.num) // 555,100

看起来是复制成功了!!~地址也变了,修改obj,obj1的引用地址不会跟着变化。

但是我们来console一下obj以及obj1

console.log(obj)
console.log(obj1)

似乎发现了离奇的事情,只有obj.a以及obj.c正确的复制了,日期类型方法正则表达式均没有复制成功,发生了一些奇怪的事情

循环赋值 deepClone

那么为了解决这种事情,就需要写一个deepClone方法来完成深复制了,参考了许多开源库的写法,将所有的复制项单独拆出,方便未来对特殊类型进行扩展,也防止不同功能间的变量互相干扰

 //既然是深复制,一定要传入一个object,再return 一个新的 Object
function deepClone(obj){
let newObj;
if(obj instanceof Array){ // 数组的话,要new一个数组
newObj = []
}else if(obj instanceof Object){ // 对象的话,要new一个对象
newObj = {}
}
if(obj === null) {
return cloneNull(obj)
}
if(typeof obj=='function'){
return cloneFunction(obj)
}
if(typeof obj!='object') {
return cloneOther(obj)
}
if(obj instanceof RegExp) {
return cloneRegExp(obj)
}
if(obj instanceof Date){
return cloneDate(obj)
}
if(obj instanceof Array){
for(let index in obj){
newObj[index] = deepClone(obj[index]); // 对数组子项进行复制
}
}
if(obj instanceof Object){
for(let key in obj){
newObj[key] = deepClone(obj[key]); // 对对象子项进行复制
}
}
return newObj;
}
function cloneNull(obj){ // 复制NULL
return obj
}
function cloneFunction(obj){ // 复制方法,
//这个方法待完善,暂时未找到能够完美复制function的方案,如果有方案,望指出
return obj
}
function cloneOther(obj){ // 复制非对象的数据
return obj
}
function cloneRegExp(obj){ // 复制正则对象
return new RegExp(obj)
}
function cloneDate(obj){ // 复制日期对象
return new Date(obj)
}

这样一个基本上满足功能的深复制就完成了。先测试一下

let obj = {
a:1,
arr:[1,3,5,7,9],
c:{
num:100
},
fn:function(){
console.log(1)
},
date:new Date(),
reg:/\.*/g
} let obj1 = deepClone(obj);
console.log(obj.c === obj1.c); // false 代表复制成功
console.log(obj.fn === obj1.fn);// true 由于方法单纯修改了引用的地址,所以这里是浅复制
console.log(obj.date === obj1.date);// false 代表复制成功
console.log(obj.reg === obj1.reg);// false 代表复制成功

console一下

console.log(obj)
console.log(obj1)

这样,就完成了deepClone深复制方法

经过深复制后,图解如下

优化 deepClone

上述代码还有优化空间,参考了lodash库,在进行 new 对象时,可以使用 constructor构造函数 来进行创建新的实例,这样

  1. 可以不用判断递归中,是数组还是对象
  2. 如果深复制的某一项是某个原型的实例,深复制完成后,依然是该原型的实例
function deepClone(obj){
let newObj = new obj.constructor;
if(obj === null) {
return cloneNull(obj)
}
if(typeof obj=='function'){
return cloneFunction(obj)
}
if(typeof obj!='object') {
return cloneOther(obj)
}
if(obj instanceof RegExp) {
return cloneRegExp(obj)
}
if(obj instanceof Date){
return cloneDate(obj)
}
if(obj instanceof Array){
for(let index in obj){
newObj[index] = deepClone(obj[index]); // 对数组子项进行复制
}
}
if(obj instanceof Object){
for(let key in obj){
newObj[key] = deepClone(obj[key]); // 对对象子项进行复制
}
}
return newObj;
}
function cloneNull(obj){ // 复制NULL
return obj
}
function cloneFunction(obj){ // 复制方法,
//这个方法待完善,暂时未找到能够完美复制function的方案,如果有方案,望指出
return obj
}
function cloneOther(obj){ // 复制非对象的数据
return obj
}
function cloneRegExp(obj){ // 复制正则对象
return new RegExp(obj)
}
function cloneDate(obj){ // 复制日期对象
return new Date(obj)
}

最终版本 deepClone

然后可以有一个合并版本的,比较节省代码,将下方区分开的复制方法,合并到deepClone中,可以极大地减少代码体积

function deepClone(obj){ //
let newObj = new obj.constructor;
if(obj === null) return obj
// if(typeof obj=='function') return obj
// 由于typeof obj=='function'也符合下方的typeof obj!='object',所以此条可以省略
if(typeof obj!='object') return obj
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Date) return new Date(obj)
// 运行到这里,基本上只存在数组和对象两种类型了
for(let index in obj){
newObj[index] = deepClone(obj[index]); // 对子项进行递归复制
}
return newObj;
}

一文带你了解js数据储存及深复制(深拷贝)与浅复制(浅拷贝)的更多相关文章

  1. js中的深复制与浅复制

    前言 所谓深复制与浅复制(深拷贝与浅拷贝),乍一听感觉听高大上,像是一个非常难理解的概念,其实我们平常项目开发都是在用的,只是你可能不知道该怎么叫它的名字而已,就像你听熟了一首歌,就是不知道这首歌叫什 ...

  2. 也来谈一谈js的浅复制和深复制

    1.浅复制VS深复制 本文中的复制也可以称为拷贝,在本文中认为复制和拷贝是相同的意思.另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,unde ...

  3. js的浅复制和深复制

    1.浅复制VS深复制 本文中的复制也可以称为拷贝,在本文中认为复制和拷贝是相同的意思.另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,unde ...

  4. 11.1 js中级,数据类型、数据储存方式、作用域内存空间的区别以及例识别。

    一. 基本数据类型和引用数据类型的区别. 1.基本数据类型:基本数据类型就是简单的操作值. 2.引用数据类型:就是把引用的地址赋给变量. 堆内存: 就是存放代码块的,存放形式有两种 1)对象以键值对的 ...

  5. Ext JS 6学习文档-第4章-数据包

    Ext JS 6学习文档-第4章-数据包 数据包 本章探索 Ext JS 中处理数据可用的工具以及服务器和客户端之间的通信.在本章结束时将写一个调用 RESTful 服务的例子.下面是本章的内容: 模 ...

  6. 一文带你读懂zookeeper在大数据生态的应用

    一个执着于技术的公众号 一.简述 在一群动物掌管的世界中,动物没有人类聪明的思想,为了保持动物世界的生态平衡,这时,动物管理员-zookeeper诞生了. 打开Apache zookeeper的官网, ...

  7. 面试题汇总--数据储存/应用程序/UI控件/客户端的安全性与框架处理。。。

    一 数据储存  1.如果后期需要增加数据库中的字段怎么实现,如果不使用 CoreData 呢?编写 SQL 语句来操作原来表中的字段1)增加表字段ALTER TABLE 表名 ADD COLUMN 字 ...

  8. jquery.cookie 使用文档,$.cookie() 文档教程, js 操作 cookie 教程文档。

    jquery.cookie 使用文档,$.cookie() 文档教程, js 操作 cookie 教程文档. jquery.cookie中的操作: jquery.cookie.js是一个基于jquer ...

  9. Android 里的数据储存

    数据持久化 关于数据储存,这个话题已经被反复讨论过很多次了,我是不建议把网络存储这种方式纳入到数据储存的范围的,因为这个和Android没多少关系,因此就有如下的分类: 本地储存(也称之为数据持久化, ...

随机推荐

  1. 【HBase】快速搞定HBase与Hive的对比、整合

    目录 对比 整合 需求一 步骤 一.将HBase的五个jar包拷贝到Hive的lib目录下 二.修改hive的配置文件 三.在Hive中建表 四.创建hive管理表与HBase映射 五.在HBase中 ...

  2. 【Hadoop离线基础总结】MapReduce 社交粉丝数据分析 求出哪些人两两之间有共同好友,及他俩的共同好友都有谁?

    MapReduce 社交粉丝数据分析 求出哪些人两两之间有共同好友,及他俩的共同好友都有谁? 用户及好友数据 A:B,C,D,F,E,O B:A,C,E,K C:F,A,D,I D:A,E,F,L E ...

  3. Boosting算法总结(ada boosting、GBDT、XGBoost)

    把之前学习xgb过程中查找的资料整理分享出来,方便有需要的朋友查看,求大家点赞支持,哈哈哈 作者:tangg, qq:577305810 一.Boosting算法 boosting算法有许多种具体算法 ...

  4. 【华为云技术分享】MongoDB经典故障系列五:sharding集群执行sh.stopBalancer()命令被卡住怎么办?

    [摘要] MongoDB sharding集群执行sh.stopBalancer()命令时被卡住怎么办?别慌,华为云数据库来给您支招,收下这份方案指南,让您分分钟远离被自建MongoDB数据库支配的恐 ...

  5. Linux开机自启动脚本的总结

    一.在/etc/rc.local中添加 如果不想将脚本粘来粘去,或创建链接什么的, 则: step1. 先修改好脚本,使其所有模块都能在任意目录启动时正常执行; step2. 再在/etc/rc.lo ...

  6. Windows 10 IoT Core用PWM控制器控制树莓派LED灯亮度

    我接到一个需求,需要调节LED灯的亮度,且是从上位机进行控制,我了解到树莓派也有PWM,就准备通过PWM来控制灯的亮度. PWM又叫脉宽调制,是用微处理器的数字输出来对模拟电路进行控制,对模拟信号电平 ...

  7. ES6新增API

    1.Object.assign(a,b,c) a.b均为对象,意思是把b对象的属性添加到a上面去.如果a中已经定义了某个属性,b也定义了的话就会覆盖a的,就是后面覆盖前面的,后面生命的有效.是 一种浅 ...

  8. 手把手教你用Python网络爬虫获取网易云音乐歌曲

    前天给大家分享了用Python网络爬虫爬取了网易云歌词,在文尾说要爬取网易云歌曲,今天小编带大家一起来利用Python爬取网易云音乐,分分钟将网站上的音乐down到本地. 跟着小编运行过代码的筒子们将 ...

  9. 「雕爷学编程」Arduino动手做(29)——DS1302时钟模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...

  10. Angular和Ionic的路由跳转

    一.Angular和Ionic的路由跳转 Angular的路由跳转: constructor(private router:Router){    } .... this.router.navigat ...