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

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

先来简单看一张图片

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)

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

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

相关推荐
M_emory_20 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito23 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
fighting ~1 小时前
react17安装html-react-parser运行报错记录
javascript·react.js·html
老码沉思录1 小时前
React Native 全栈开发实战班 - 列表与滚动视图
javascript·react native·react.js
abments1 小时前
JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)
javascript·爬虫·python
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
老码沉思录1 小时前
React Native 全栈开发实战班 - 状态管理入门(Context API)
javascript·react native·react.js
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript