聊一聊深拷贝和浅拷贝(JS)
在 JS 中数据类型分为值类型和引用类型,对于值类型,变量中存放的是具体的值,而对于引用类型,变量中存放的是地址。
对于值类型:
const a = 3;
let b = a;
b = 4;
console.log(a);
输出结果是 3.
对于引用类型:
const obj1 = {
age: 18
};
let obj2 = obj1;
obj2.age = 20;
console.log(obj2.age);
输出结果:20.
上述变量在内存中的存储:
// 深拷贝代码
const obj1 = {
age: 20,
address: {
city: 'nanjing'
},
};
// 验证代码
const obj2 = deepClone(obj1);
obj2.age = 33;
console.log('obj2 = ', obj2);
console.log('obj1 = ', obj1);
/**
* 深拷贝
* @param obj 要拷贝的对象
* @returns {{}}
*/
function deepClone(obj = {}) {
// 判断 obj 是不是数组或对象,这也是递归的终止条件
if (typeof obj !== 'object' || typeof obj == null) {
return obj;
}
// 初始化返回值
let result;
result = obj instanceof Array ? [] : {};
// 递归拷贝
for(key in obj) {
if(obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
在程序运行过程中,当一个函数被调用时会把调用时的位置等信息保存在堆栈中,这就是通常说的保护现场,当程序执行完成之后再将保存在堆栈中的信息pop出去,恢复现场。
在上面的深拷贝程序中:
// 递归拷贝
for(key in obj) {
if(obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
1) key = age 时
result[age] = deepClone(20)
此时调用 deepClone(20) 时,会将调用位置等信息 push 到堆栈中,
此时堆栈中的内容:
| 调用位置 | 调用信息 |
|---|---|
| 31行 | deepClone(20) |
| 9行 | deepClone({ age: 20, address: { city: 'nanjing' }, }) |
由于 20 不是数组或对象此时在执行
if (typeof obj !== 'object' || typeof obj == null) {
return obj;
}
时,会将 20 返回,此时函数执行结束,堆栈中的 deepClone(20)被 pop 出去。
此时堆栈中的内容:
| 调用位置 | 调用信息 |
|---|---|
| 9行 | deepClone({ age: 20, address: { city: 'nanjing' }, }) |
此时拷贝的结果 result 内容内容如下:
result:
| key | value |
|---|---|
| age | 20 |
2)当 key = address 时
result[address] = deepClone({
city: 'nanjing'
})
此时拷贝结果 result 的内容:
result:
| key | value |
|---|---|
| age | 20 |
| address | deepClone({ city: 'nanjing' }) |
此时调用
deepClone({
city: 'nanjing'
})
会将调用位置等信息 push 到堆栈中,
此时堆栈中的内容:
| 调用位置 | 调用信息 |
|---|---|
| 31行 | deepClone({city: 'nanjing'}) |
| 9行 | deepClone({ age: 20, address: { city: 'nanjing' }, }) |
由于 {city: 'nanjing'} 是一个对象,所以会执行初始化返回值
// 初始化返回值
let result;
result = obj instanceof Array ? [] : {};
由于 obj 此时是一个对象,所以 result = {},然后接着执行递归拷贝
// 递归拷贝
for(key in obj) {
if(obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
此时 result[city] = deepClone('nanjing') 。
此时堆栈中的内容:
| 调用位置 | 调用信息 |
|---|---|
| 31行 | deepClone({'nanjing') |
| 31行 | deepClone({city: 'nanjing'}) |
| 9行 | deepClone({ age: 20, address: { city: 'nanjing' }, }) |
由于 'nanjing' 不是一个数组或对象,所以会直接返回 'nanjing',result[city] = deepClone('nanjing')='nanjing' ,deepClone('nanjing') 执行结束,此时堆栈中的内容:
| 调用位置 | 调用信息 |
|---|---|
| 31行 | deepClone({city: 'nanjing'}) |
| 9行 | deepClone({ age: 20, address: { city: 'nanjing' }, }) |
紧接着在 31 行( 因为 deepClone({'nanjing') 就是在31行调用的)接着向下执行执行for循环,由于只有一个city自有属性所以此时不满足for循环了,所以会继续向下执行 return result ,因为上面 result[city] = deepClone('nanjing')='nanjing' 所以 result = { city: 'nanjing'}(再强调一遍这里的 result 和最终返回的 result 不是一个)
此时堆栈的内容为:
| 调用位置 | 调用信息 |
|---|---|
| 9行 | deepClone({ age: 20, address: { city: 'nanjing' }, }) |
deepClone({city: 'nanjing'}) 被 pop 出去之后,由于是在 31 行被调用的,所以会继续从31行向下执行for循环,由于 obj1 只有 age 和 address 两个属性而且都已经遍历过了,所以会跳出 for 循环向下执行 return result;
此时 result 的内容为:
result:
| key | value |
|---|---|
| age | 20 |
| address | { city: 'nanjing' } |
这个时候 deepClone({<br/> age: 20,<br/> address: {<br/> city: 'nanjing'<br/> },<br/>}) 被从堆栈中 pop 出去,堆栈中现在没有函数的调用信息了,程序从第 9 行继续向下执行,验证深拷贝是否成功。
深拷贝和浅拷贝之间的区别就在于深拷贝中创建了一个 result 来复制原有的对象中的所有属性,浅拷贝仅仅是复制了原有对象的地址。
有几点需要注意
1)注意 deepClone 函数刚开始引用类型和值类型的判断,如果是值类型没有必须要再进行for 循环取属性遍历了,直接返回即可。这也是作为递归的终止条件,只要用到递归就必须要设置递归终止条件。
2)注意判断是数组还是对象,传递进来的就是需要拷贝的,传递进来的是对象就要将拷贝的结果初始化为对象,传递进来的是数组就要将拷贝的结果初始化为数组。注意每次的名字虽然都叫 result,但是并不是同一个,它们的地址都是不同的。
3)递归,这是深拷贝逻辑上的核心。
聊一聊深拷贝和浅拷贝(JS)的更多相关文章
- 深拷贝与浅拷贝js,方法
在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问题的发生. 参考 ...
- js的命名空间 && 单体模式 && 变量深拷贝和浅拷贝 && 页面弹窗设计
说在前面:这是我近期开发或者看书遇到的一些点,觉得还是蛮重要的. 一.为你的 JavaScript 对象提供命名空间 <!DOCTYPE html> <html> <he ...
- js 深拷贝和浅拷贝
js 深拷贝和浅拷贝 先举一下项目中遇到的两个例子: 例子1: var json = $.parseJSON(data.data);//data.data是接口返回的值var a = json.cha ...
- JS 数据类型、赋值、深拷贝和浅拷贝
js 数据类型 六种 基本数据类型: Boolean. 布尔值,true 和 false. null. 一个表明 null 值的特殊关键字. JavaScript 是大小写敏感的,因此 null 与 ...
- 从JS的深拷贝与浅拷贝到jq的$.extend()方法
一.堆内存与栈内存 堆和栈都是内存中划分出来的用来存储的区域,栈为自动分配的内存空间,它由系统自动释放,堆为动态分配的内存,大小不定也不会自动释放. 二.js基本数据类型与引用类型的不同 基本数据类型 ...
- js 中引用类型 的深拷贝 和 浅拷贝的区别
一.曾经在读JQ源码的时候,对深拷贝算是有了一点的理解.我们在项目中是不是经常会遇到这样的问题呢? 后台返回一个数组对象(引用类型).次数在页面渲染中需要对部分数据进行处理 比如:银行卡6234509 ...
- 在vue中子组件修改props引发的对js深拷贝和浅拷贝的思考
不管是react还是vue,父级组件与子组件的通信都是通过props来实现的,在vue中父组件的props遵循的是单向数据流,用官方的话说就是,父级的props的更新会向下流动到子组件中,反之则不行. ...
- 老生常谈之js深拷贝与浅拷贝
前言 经常会在一些网站或博客看到"深克隆","浅克隆"这两个名词,其实这个很好理解,今天我们就在这里分析一下js深拷贝和浅拷贝. 浅拷贝 我们先以一个例子来说明 ...
- 一篇文章理解JS数据类型、深拷贝和浅拷贝
前言 笔者最近整理了一些前端技术文章,如果有兴趣可以参考这里:muwoo blogs.接下来我们进入正片: js 数据类型 六种 基本数据类型: Boolean. 布尔值,true 和 false. ...
随机推荐
- MySql锁与InnoDB引擎
MySql锁与InnoDB引擎 mysql的锁是面试中很高频问题,也是我们在日常开发中经常会遇到但是我们并没有注意到的地方.我把我自己理解的锁通过本篇博文分享出来,由于锁需要结合事务来理解,本文只介绍 ...
- JAVA和C#中数据库连接池原理与应用
JAVA和C#中数据库连接池原理 在现在的互联网发展中,高并发成为了主流,而最关键的部分就是对数据库操作和访问,在现在的互联网发展中,ORM框架曾出不穷, 比如:.Net-Core的EFCore.Sq ...
- iOS 原生实现生成二维码(CoreImage)工具类,不依赖第三方库,可自定义背景颜色,添加logo(Swift 4.0)
import Foundation import CoreImage import UIKit extension UIColor { var coreImageColor: CIColor { re ...
- 【转】Java包管理器Maven学习
Maven 引入Jar包的几种情况 1.通过pom.xml中导入的jar包 (1)链接打开http://mvnrepository.com/ (2)搜索罐包,比如我想要的是servlet的api.ja ...
- 利用Docker手动构建WebLogic镜像的步骤
info 我的Docker环境信息如下: [root@localhost ~]# docker info -f " OSType: {{.OperatingSystem}} {{.Archi ...
- Trie树的简单实现
import java.util.ArrayList; import java.util.TreeMap; import util.FileOperation; public class Trie { ...
- 家乐的深度学习笔记「4」 - softmax回归
目录 softmax回归 分类问题 softmax回归模型 softmax运算 矢量表达式 单样本分类的矢量计算表达式 小批量样本分类的矢量计算表达式 交叉熵损失函数 模型预测及评价 图像分类数据集( ...
- 关于虚拟机NAT连接外网!!!
点开Virtual Network Editor(开始->VMware),选择vmnet8, NAT setting就可以知道了. 1. 虚拟机NAT和主机的通讯方式,是通过虚拟网关(即NAT ...
- EF Core 数据变更自动审计设计
EF Core 数据变更自动审计设计 Intro 有的时候我们需要知道每个数据表的变更记录以便做一些数据审计,数据恢复以及数据同步等之类的事情, EF 自带了对象追踪,使得我们可以很方便的做一些审计工 ...
- GitHub 运用实战入门,奶妈级教学
## 前言: 我不会用*官方*的语言告诉你Git 是什么,对此我表示深深得歉意--在我看来像CSDN.博客园.掘金等博客交流平台就是小的“GitHub”,只不过在这里更多的是一些零零散散的笔记或者文章 ...