JS-浅拷贝和深拷贝

内存

在介绍浅拷贝和深拷贝之前先来看一下什么是内存

内存分区

任何编程语言的内存分区几乎都是一样的

1. 先说一下C语言中定义的五个内存空间

1-1. 栈

栈用来存放函数的形参和函数内的局部变量,它是由编译器分配空间,在函数执行完后由编译器自动释放。

1-2. 堆

堆用来存放由动态分配函数分配的空间,它是由程序员手动配置,并且必须由程序员使用free释放。如果忘记使用free释放,会使得所分配的空间一直占着不放,从而导致内存泄露。

1-3. 全局区

全局区用来存放全局变量和静态变量,它的存在于程序的整个运行期间,是由编译器分配和释放的。

1-4. 文字常量区

文字常量区用来存放文字常量。如:char*c=’123456’,其中,’123456′ 为文字常量,存放于文字常量区。文字常量区也是由编译器分配和释放的。

1-5. 程序代码区

程序代码区用来存放程序的二进制代码。

2. javascript在浏览器中的内存空间

2-1. C语言提到的编译器是什么?

编译器就是将高级语言编译成机器代码,例如:java、C、C#、C++就需要通过编译成机器代码。
这里,就需要了解一下语言类型了。

  1. 编译型语言是通过编译器将源代码编译成机器码:C/C++
  2. 解释型语言不需要中间过程的编译环节,而是在执行程序时通过解释器逐行翻译:Python,Javascript,文本格式的 .py 或者 .js 文件会直接被 python 解释器或者 v8 解释执行,不需要编译。
  3. 编译加解释,例如 java,文本格式的 .java 文件被编译成 .class 二进制字节文件,再被 jvm 解释执行。

因此js并不需要用到编译器,直接逐行解释运行。

2-2. js-栈内存

栈内存ECStack(Execution Context Stack)(作用域)
JS之所以能够在浏览器中运行,是因为浏览器给JS提供了执行的环境栈内存
浏览器会在计算机内存中分配一块内存,专门用来供代码执行=>栈内存ECStack(Execution Context Stack)执行环境栈,每打开一个网页都会生成一个全新的ECS

基本数据类型

String、Boolean、Number、undefined、null、Symbol、

var a = 12;
复制代码

创建一个值->创建一个变量->让变量和值关联在一起
基本数据类型值都是直接存储到栈内存中的

ECS的作用
  1. 提供一个供JS代码自上而下执行的环境(代码都在栈中执行)
  2. 由于基本数据类型值比较简单,他们都是直接在栈内存中开辟一个位置,把值直接存储进去的,当栈内存被销毁,存储的那些基本值也都跟着销毁
栈内存的释放
  1. 一般情况下,函数执行形成的栈内存,函数执行完,浏览器会把形成的栈内存自动释放。
  2. 有时候函数执行完成,栈内存无法释放(被外部占用了)。
  3. 全局作用域在加载页面时形成,在关闭页面时销毁(window)。
  4. 全局作用域会在页面关闭或者刷新的时候释放。(栈内存释放后,存储在栈内存中的值也都会销毁。)
  5. 私有作用域:一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉,但是也有特殊的情况。
  6. 函数执行完成,当前形成的栈内存中,某些内容被栈内存意外的变量一直占用,此时栈内存不能释放,栈内存中存储的基本值也不会被释放,一直保存下来。最典型的就是闭包。

2-3. js-堆内存

堆内存:引用值对应的空间
存储引用类型值(对象:键值对, 函数:代码字符串),当内存释放销毁,那么这个引用值彻底没了.

(1)对象数据类型 object、
普通对象{}、数组对象[]、正则对象/^$/、日期对象 new Date、数学函数对象 Math
(2)函数数据类型 function

引用数据类型
var a = {n:12};
复制代码

创建一个堆内存->把键值对存储到内存中->堆内存地址放到栈中供变量调用。
引用数据类型值都是先开辟一个堆内存,把东西存储进去,最后把地址放到栈中供变量关联使用
所有的指针赋值,都是指针的关联指向

堆内存释放

当堆内存没有被任何得变量或者其他东西所占用,浏览器会在空闲的时候,自主进行内存回收,把所有不被占用得内存销毁掉

谷歌浏览器(webkit),每隔一定时间查找对象有没有被占用
引用计数器:当对象引用为0时释放它

堆内存利用空对象指针null来释放空间。

var obj = {};此时当前对象对应的堆内存被变量obj占用,无法销毁空间。
obj = null;由于null是空对象指针(不指向任何的堆内存),此时上一次的堆内存就没有被占用了,谷歌浏览器会在空闲时间把没有被占用的堆内存自动释放(销毁/回收)。

null为什么显示object

对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug

因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

2-4.全局对象 GO

全局对象GO(Global Object)浏览器端会让WINDOW指向GO

浏览器把内置得一些属性方法收到一个单独得内存中堆内存(Heap)任何开辟得内存都有一个16进制得内存地址,方便后期找到这个内存

2-5.执行上下文 EC

EC(Execution Context)执行上下文:代码自己执行所在的环境

  • 全局的执行上下文EC(G)
  • 函数中的代码都会在一个单独的私有的执行上下文中处理
  • 块级的执行上下文

形成的全局执行上下文,进入到栈内存中执行“进栈”
执行完代码,可能会把形成的上下文出栈释放“出栈”

2-6.变量对象 VO

  • VO(Varibate Object)变量对象:

在当前上下文中,用来存放创建的变量和值的地方(每一个执行上下文中都有一个自己的变量对象,函数私有上下文中叫做AO(Activation Object)活动对象,但也是变量对象)

  • VO(G)全局变量对象:

全局上下文中用来存储全局变量的空间,它不是GO,只不过某些情况下VO(G)中的东西会和GO中的东西有所关联而已“映射机制”

2-7.变量提升

image.png
变量提升是var和function独有的属性
所谓的变量提升和函数提升就是将所有变量声明和函数声明提升到全局作用域的顶端(只是提升变量声明,并不将赋值初始化提升。)

2-8.事例

image.png

3. 浅拷贝与深拷贝

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝(修改堆内存中的不同的值)

  • 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
  • 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存

3-1.浅拷贝实例

复制后只是有一个新指针指向了堆区中的地址

const a = [1,2,3,4,5,6];
const b = a;

b[0] = 2;
console.log(a); // [2,2,3,4,5,6]
复制代码

3-2.深拷贝实例

复制后是在堆区中又开辟了一个位置,一个新的指针指向它

let arr = [1,2,3,4,5,6];
let arrs = arr.concat();
arrs[0] = 2;
console.log(arr); // [1,2,3,4,5,6]
console.log(arrs); // [2,2,3,4,5,6]
复制代码

参见

1. 百度搜索前端代码包括HTML,CSS,JS是在哪里编译和运行的?
2. 博客园-柠檬仔仔
3.博客园-张小中
4.CSDN-鲸鲸景鲸叻
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享