在js中如何区分深拷贝与浅拷贝?
一、自我理解
简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层。
在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改。
在深拷贝中,原对象与新对象不共享相同的属性,而在浅拷贝中,它们具有相同的属性。
举个栗子:存在A和B两个数据,假设B复制了A,当修改A时,如果B跟着A变化了,则是浅拷贝;如果B不受A的改变而改变,则是深拷贝
let A = [0,1,2]
let B = A
console.log(A === B);
A[0] = 3
console.log(A,B);
结果如下:
这就是一个简单的浅拷贝例子,虽然B复制了A,但是修改了A,B却跟着A变化了?
二、数据存储形式
通过上述栗子,我们就需要了解数据类型的存储方式了,如果还没有了解数据类型的小伙伴欢迎阅读 JS中8种数据类型
- 基本数据类型:number,string,boolean,null,undefined,symbol(es6),还有谷歌67版本中的BigInt(任意精度整数)
- 引用数据类型:Object【Object是个大类,function函数、array数组、date日期...等都归属于Object】
(1)基本数据类型存储于栈中
变量名和值都存储与栈中
每当声明一个变量,就会开辟一个新的内存空间来存储值,例如:let a = 1
// 声明一个变量
let a = 1;
当b复制了a后,因为b也是一个变量,所以栈内存会再开辟一个新的内存空间:
// 声明一个变量
let a = 1;
let b = a; //b变量复制a变量
console.log(a,b); //1 1
这就一目了然了,a和b两个变量归属于不同的内存空间,所以当你修改其中一个值,另外一个值不会受其影响!
// 声明一个变量
let a = 1;
let b = a; //b变量复制a变量
console.log(a,b); //1 1
// 修改a的值不影响b
a = 123;
console.log(a,b); //123 1
(2)引用数据类型存储与堆中
变量名存储在栈中,值存在于堆中,这时栈内存中会提供一个指针用于指向堆内存中的值
当我们声明一个引用数据类型时,会提供一个指针指向堆内存中的值:
// 声明一个引用类型的值
let a = [0,1,2,3,4]
当b复制了a后,只是复制了当前栈内存中提供的指针,指向同一个堆内存中的值,并不是复制了堆内存中的值:
let a = [0,1,2,3,4]
let b = a //复制的是一个指针,而不是堆中的值
所以当我们进行a[0]=1 时,导致指针所指向的堆内存中的值发生了改变,而a和b是同一个指针,指向同一个堆内存地址,所以b的值也会受影响,这就是浅拷贝!
// 声明一个引用类型的值
let a = [0,1,2,3,4];
let b = a; //复制的是一个指针,而不是堆中的值
console.log(a === b);
a[0] = 1; //改变a数组值,b数组跟着改变(浅拷贝)
console.log(a,b);
所以,我们需要在堆内存中新开辟一个内存专门接收存储b的值,道理与基本数据类型一样,这样就可以实现深拷贝(修改一个值不会影响另一个值的变化):
三、怎样实现深拷贝?
(1)借助JSON对象的parse和stringify
parse()方法用于将一个字符串解析为json对象
stringify()方法用于将一个对象解析为字符串
// 浅拷贝
let arr = [1,"hello",{name:"张三",age:21}]
let copyArr = arr
console.log(arr === copyArr); //true
arr[1] = "您好"
console.log("浅拷贝",arr, copyArr);
// JSON对象方法实现深拷贝
let arr = [1,"hello",{name:"张三",age:21}]
let newArr = JSON.parse(JSON.stringify(arr)) //将arr数组转换为字符串,再作为参数转化为对象
arr[1] = "您好"
arr[2].name = "法外狂徒"
console.log("深拷贝",arr, newArr);
原理是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,这样在堆中开辟了新的内存,实现深拷贝
这种写法非常简单,而且可以应对大部分的应用场景,但是它还是有很大缺陷的,比如拷贝其他引用类型、拷贝函数、循环引用等情况
(2)手写递归
递归方法实现深度克隆原理:遍历对象、数组...直到里边都是基本数据类型,然后再去复制,就是深度拷贝
- 如果是原始类型,无需继续拷贝,直接返回
- 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上
// 手写递归实现深拷贝
function clone(target) {
if (typeof target === 'object') {
// 创建一个新对象
let cloneTarget = {};
// 遍历需要克隆的对象
for (const key in target) {
// 将需要克隆对象的属性执行深拷贝后依次添加到新对象上
cloneTarget[key] = clone(target[key]);
}
// 返回克隆对象
return cloneTarget;
} else {
return target;
}
};
// 模拟拷贝对象
const person = {
name: "李小白",
age: 18,
job: {
name:"web前端",
salary:"15k",
address:"成都"
}
};
// 将需要克隆的对象传递给clone函数作为参数,接收一个克隆对象作为返回结果
let newTarget = clone(person)
//修改原对象属性
person["name"] = "李大白"
person["job"]["salary"] = "25k"
console.log(person,newTarget); //实现深拷贝
利用递归可以实现深度拷贝,但是有局限性,例如数组还没考虑进去!
想要兼容数组其实很简单,直接判断一下参数是不是数组,然后利用三木运算决定是创建一个新对象还是一个新数组:
function clone(target) {
if (typeof target === 'object') {
let isArr = Array.isArray(target)
// 判断传入的参数是数组吗 ? 是则赋值[] : 否则赋值{}
let cloneTarget = isArr ? [] : {};
// 遍历需要克隆的对象
for (const key in target) {
// 将需要克隆对象的属性执行深拷贝后依次添加到新对象上
cloneTarget[key] = clone(target[key]);
}
// 返回克隆对象
return cloneTarget;
} else {
return target;
}
};
(3)JQuery中extend方法
在jQuery中提供一个$extend()来实现深拷贝
语法:
$.extend( [deep ], target, object1 [, objectN ] )
- deep 表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
- target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
- object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。
let arr = ["李小白",18,["前端","15k"],21]
// true:开启深拷贝,目标对象:[],原对象:arr
let cloneArr = $.extend(true, [], arr)
arr[0] = "李大白";
arr[2][0] = "java";
console.log(arr, cloneArr);
$extend()第一个参数为true时可以实现深拷贝!但是第一个参数为false,则只会拷贝第一层属性,不能实现深拷贝:
let arr = ["李小白",18,["前端","15k"],21]
// false:不开启深拷贝,目标对象:[],原对象:arr
let cloneArr = $.extend(false, [], arr)
arr[0] = "李大白";
arr[2][0] = "java";
console.log(arr, cloneArr);
总结/注意
实现深拷贝方式有多种,以上三种较为常用,另外还有一个:函数库lodash中_.cloneDeep方法(本人未使用过)也可以实现深拷贝。
最后需要补充一下,网上有很多人说slice和concat方法可以实现,但我要说的是slice和concat方法不能实现深拷贝的!!!
下面举例说明:
// slice()
let arr = [0,1,["hello","world"]];
let cloneArr = arr.slice();
arr[0] = "零";
arr[2][0] = "hello2022"
console.log(arr,cloneArr);
// concat()
let arr = [0,1,["hello","world"]];
let cloneArr = arr.concat();
arr[0] = "零";
arr[2][0] = "hello2022"
console.log(arr,cloneArr);
这两个方法得到的结果都是一样的:
这一点大家一定要注意,深拷贝必须要拷贝所有层级的属性,而这两个方法拷贝不彻底,也就是只能拷贝第一层,希望大家不要踩坑!
在js中如何区分深拷贝与浅拷贝?的更多相关文章
- js 中引用类型 的深拷贝 和 浅拷贝的区别
一.曾经在读JQ源码的时候,对深拷贝算是有了一点的理解.我们在项目中是不是经常会遇到这样的问题呢? 后台返回一个数组对象(引用类型).次数在页面渲染中需要对部分数据进行处理 比如:银行卡6234509 ...
- 探究JS中对象的深拷贝和浅拷贝
深拷贝和浅拷贝的区别 在讲深拷贝和浅拷贝的区别之前,回想一下我们平时拷贝一个对象时是怎么操作的?是不是像这样? var testObj1 = {a: 1, b:2}, testObj2=testObj ...
- js中的数据类型、以及浅拷贝和深拷贝
一.js中的数据类型 1.基本类型(值类型):Undefined.Boolean.String.Number.Symbol 2.引用类型:函数.数组.对象.null.new Number(10)都是对 ...
- Python中复制、深拷贝和浅拷贝的区别
深拷贝定义(deepcopy) 在Python中,由于一切皆对象,所以任何变量都可以被引用,也即可以被赋值给任何变量.但是在Python中,给变量赋值,是区分的,一般情况下,Python中的变量赋值都 ...
- js引用类型赋值,深拷贝与浅拷贝
JS中引用类型使用等号“=” 赋值,相当于把原来对象的地址拷贝一份给新的对象,这样原来旧的对象与新的对象就指向同一个地址,改变其中一个对象就会影响另外那个对象,也就是所谓的浅拷贝.例如: var ar ...
- PHP中对象的深拷贝与浅拷贝
先说一下深拷贝和浅拷贝通俗理解 深拷贝:赋值时值完全复制,完全的copy,对其中一个作出改变,不会影响另一个 浅拷贝:赋值时,引用赋值,相当于取了一个别名.对其中一个修改,会影响另一个 PHP中, = ...
- 谈谈java中对象的深拷贝与浅拷贝
知识点:java中关于Object.clone方法,对象的深拷贝与浅拷贝 引言: 在一些场景中,我们需要获取到一个对象的拷贝,这时候就可以用java中的Object.clone方法进行对象的复制,得到 ...
- Python中什么是深拷贝和浅拷贝且有什么区别
浅拷贝: >>> a = [1, 2, 3] >>> b = a >>> a [1, 2, 3] >>> b [1, 2, 3] ...
- python中赋值,深拷贝,浅拷贝区别
这三种 的区别就是 复制的变量 是否是原变量的引用. 赋值:只是原变量的引用. 浅拷贝和深拷贝的区别 需要通过 子元素 区分 浅拷贝:子元素的 引用相同 深拷贝:所以引用都不相同,完全复制一份 这三种 ...
随机推荐
- 【LeetCode】611. Valid Triangle Number 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/valid-tri ...
- 【剑指Offer】10- II. 青蛙跳台阶问题 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人微信公众号:负雪明烛 目录 题目描述 解题方法 动态规划 日期 题目地址:https: ...
- GCD(hdu1695)
GCD Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...
- bootstrap可编辑下拉框jquery.editable-select
搜了半天发现在某处下载jquery.editable-select需要积分,于是整理出来方便 其他人. 先上下载链接: http://pan.baidu.com/s/1kUXvwlL pas ...
- EMA
目录 源 设置 结果 源 Exponential moving average (EMA) 是一个非常有用的trick, 起到加速训练的作用. 近来发现, 该技巧还可以用于提高网络鲁棒性(约1% ~ ...
- Product Integration
目录 生存模型 连续情形 离散情形 统一 Richard D. Gill, Product integration 一般的积分是指黎曼积分, 其计算是把区域无限细分求和并取极限, 有另外一种积分是把区 ...
- BL8810|USB2.0高速闪存读卡器芯片|BL8810规格书
1.说明 BL8810是一款USB 2.0读卡器控制器,采用高度集成的单芯片解决方案,旨在提供USB2.0和SD.SDHC.mini SD.Micro SD(T-Flash)接口规范之间的高速数据传输 ...
- CS5213demoboard设计电路|DMI转VGA带II2S音频输出转接线|CS5213方案
CS5213是台湾CAPSTONE瑞奇达推出的一款HDMI(高清多媒体接口)到VGA转换芯片. CS5213设计HDMI转VGA带II2S转接线产品特性: ◇将完整的HDMI信号转换为VGA输出◇支持 ...
- Java基础(八)——IO流2_缓冲流、转换流
一.缓冲流 1.介绍 缓冲流:不能直接作用在文件上,需要包一层,它是一种处理流.用于提高文件的读写效率.它在流的基础上对流的功能进行了增强.提高读写速度的原因:内部提供了一个缓冲区.缺省使用 8192 ...
- Hadoop问题解决记录
# 1.解决Unable to load native-hadoop library for your platform告警 安装Hadoop启动之后总有警告:Unable to load nativ ...