从内存的角度分析深浅拷贝

大家好,这里是前端小橙,本文章将根据内存在深度讲解深浅拷贝,会分别介绍内存和深浅拷贝

先来简单看一张图片

1、什么是深浅拷贝

首先先了解什么是深浅拷贝

  • 浅拷贝
    两个变量指向同一个内存地址, 例如上图中, 当a修改内存中的值时, b的值也会发生改变。
  • 深拷贝
    两个变量指向两个不同的内存地址, 例如上图中, 当a修改内存中的值时, b的值不会发生改变。

看完这两个概念的话是否会产生一些疑问, 不管有或者没有思考两个问题:

  • 变量在内存中分别是如何存储的?
  • 什么情况下两个变量会指向同一个地址?

如果是从 前端面试题过来的思考第三个问题?

  • 为什么Object.assign有时是深拷贝有时是浅拷贝?

2、js的数据类型在内存中的存储

2.1、js的数据类型

  • 基本数据类型
    Number、String、Boolean、Null、Undefined、Symbol
  • 引用数据类型
    Object、Array、Function

2.2、了解内存

学过c/c++的同学对内存应该非常的了解, 但是只学习前端的小伙伴可能对内存就相对生疏一些,接下来会从最基础讲解内存

我们现在所使用的编程语言不管是c/c++、Java、JS等都是高级语言,我们所编写的代码最终都会被编译成计算机能识别的机器语言(也就是二进制)来被计算机识别。当然这不是我们需要关心的事情,但是需要有一定的了解。

2.3、不同变量在内存中的存储

每一个变量都会进入内存, 大家应该都听说过堆栈, 每一个变量或语句的运行都会存储在堆或者栈, 堆和栈都是内存的一部分, 下面就来看看不同变量在堆栈中的存储

  • 基本数据类型
ini 复制代码
//基本数据类型都会存储在栈空间
let a = 1;
let b = "b";
let c = true;
let d = null;
let e = undefined;

代码运行都会进行入栈和出栈的操作, 这个我们不关心, 当我们定义变量的时候, 基本数据类型的变量会被存储在栈中, 入栈的方式都是从栈顶入栈,然后后来的就会把先来的往下压, 被称为压栈, 栈顶在上面还是在下面这不重要,画图本身就是为了更直观的理解问题, 而内存空间本身也不是这样的, 所以不要在这个地方纠结, 我们了解了基本数据类型在内存中的存储方式,接下来看看引用类型是如何存储的

  • 引用数据类型
js 复制代码
//引用数据类型都会存储在堆空间
let obj = {
    name:"orange"
}

引用类型的值是进行单独存储的,和基本数据类型不同的是,

    • 基本数据类型是直接存储值
    • 引用类型是存储值的地址, 而值是单独存放在另一个内存空间中

如果不理解欢迎评论,可以多听听大家的建议, 认识了变量在内存中的存储后, 我们就正式开始在内存的角度讲讲深浅拷贝

3、通过内存来解释深浅拷贝

3.1、区分基本类型和引用类型

先来看一段代码

js 复制代码
//基本数据类型
let a = 10;
let b = a;
a = 20;
console.log(b)          // b == 10

从图中可以看出,通过赋值运算符(=)是直接将a的值(a在内存中的值)给了另一个变量b,因为基本数据类型是值存储,所以直接就是将10给了b, 所以可以看出, 修改a的值,b的值不会受到影响。那么再来看一段代码

js 复制代码
//引用类型
let a = { name:"orange" };
let b = a;
a.name = "apple";
console.log(b.name)          // b.name == "apple"

由于引用类型的值在内存中是单独存储的, 使用赋值运算符(=)会将值的地址赋值给变量, 这样会导致两个变量同时指向一个内存地址, 所以一个变量改变,另一个也会跟着改变。

总结:对于基本数据类型来说, 无论如何操作都会有新的内存地址, 所以都属于深拷贝, 或者深浅拷贝只对于引用类型而言,不考虑基本数据类型,了解一下就可以了。

3.2、Object.assign

对于引用类型来说Object.assign方法可以是深拷贝也可以是浅拷贝, 这时就需要区分情况了

  • Object.assign深拷贝
    使用:Object.assign({},obj)
ini 复制代码
let a = { name:"orange" };
let b = Object.assign({}, a);
a.name = "apple";
console.log(b.name)           // b.name == "orange"

Object.assign({},obj)会将obj中第一层的数据都开辟新的内存进行存储, 所以可以实现引用类型的深拷贝,为什么说是将第一层的数据存入新的内存呢? 我们继续往下看,

先知道一个概念:当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候(即有嵌套对象),此方法,在二级属性以后就是浅拷贝

  • Object.assign浅拷贝
    使用:Object.assign({},obj)
ini 复制代码
let a = { 
    name:"orange",
    like:{
        foot:"orange",
    }
};
let b = Object.assign({}, a);
a.name = "apple";
console.log(b.name)           // b.name == "orange"

这时候将a的其中一个元素改为引用类型,这是a相当于有两层对象, 而上面的例子是单层的,可以拷贝代码看看,使用Object.assign拷贝的b会不会受a的影响, 或者哪些属性会受影响,然后再来看下图:

这是a多层级引用类型在内存中的存储, 当使用Object.assign进行拷贝时, 拷贝的是a对象的第一层数据, 而a中的like也是一个引用类型,所以是单独存放在另一个内存空间的, 所以like并没有被重新开辟一块空间进行存储。

a的第一层数据被拷贝了,所以修改a.name,不会影响b.name,因为他们已经指向不同的内存地址了,而修改like中的foot的时候,b还是会被影响, 说明第二层之后的值,并不会开辟新的内存空间,还是指向同一地址, 所以在多层级引用类型时,Object.assign就属于浅拷贝。

3.3、100%深拷贝

经过上面的讲解, 大家应该都熟悉了,数据类型在内存中的存储, 也从内存的方面对深浅拷贝有了进一步的了解, 最后一个, 无论是单层还是多层的引用类型, 都要有方法进行深拷贝, 那么这个方法就是JSON.parse和JSON.stringify

ini 复制代码
let a = { 
    name:"orange",
    like:{
        foot:"orange",
    }
};
let b = JSON.parse(JSON.stringify(a));
a.name = "apple";
console.log(b.name)

这个例子就不画图了, 原理很简单, 就是会将每一层的数据都进行新的内存开辟, 然后修改指向, 如果上面的都懂了, 这个也就很好理解啦。

总结 : 最后祝大家技术越来越好, 不好就来看看高级前端面试题

相关推荐
Boilermaker199213 分钟前
【Java EE】SpringIoC
前端·数据库·spring
中微子25 分钟前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上102440 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁1 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry1 小时前
Fetch 笔记
前端·javascript
拾光拾趣录1 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟1 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan1 小时前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构