js的堆和栈角度浅析对象使用、函数执行的过程

js的堆和栈角度浅析对象使用、函数执行的过程,第1张

为什么突然要了解堆和栈呢? 是对引用类型有困惑从而追溯到堆栈内存的。

js的堆和栈

什么是堆,什么是栈,堆栈的结构 分别存放哪些东西?
是一种数据结构,数据结构(英语:data structure)是计算机中存储、组织数据的方式

执行栈 ECStack 浏览器分配的一块内存,供代码执行
引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。

栈的特点 栈空间先进后出,后进先出的特点。临时存放变量

它是一种特殊的列表,栈内的元素只能通过列表的一端(栈顶)访问,另一端称为栈底。

对象的地址指针 js指针 的工作方式

js中并没有指针的概念,但是有指针的应用,

c语言中 指针

简单的来说,指针就是地址。我们口头上说的指针其实指的是指针变量。指针变量就是一个存放地址的变量。

int c=10,b;
int *p;/* p是指向int类型的指针 */
p=&c;/* &c获取变量c的地址,然后赋值给变量p,这样p存储的是变量c的地址,即p是指向c的指针*/
b=*p;/*  *p访问p指向的对象,然后将值赋值给b */
指针变量就是一个存放地址的变量。

引用类型叫由多个值构成的对象 基本类型只有一个值

let obj={
    year:'2021',
    month:'9'
}
let arr=[1,2,3];let str='123';
let num=120;

疑问:函数存放在哪 对象存放在哪 函数参数存放在哪?什么叫引用类型? 基本类型的话基本数据类型:

基本数据类型保存在栈中,这些类型在内存中别占有固定大小,他们的值保存在栈空间,按值访问。
变量在声明过程中,会在栈中开辟一段内存空间,变量值直接存在该内存中,变量读写的是它们实际保存的值。基本数据类型数据复制时,会为新声明的变量开辟一段新空间, 然后把值复制到为新变量分配的空间中,在当前执行环境结束时销毁。

JS代码的存储环境–堆中

浏览器在读取到函数时,并非直接将函数体中代码执行,而是会将函数以字符串的形式存储在浏览器的内存中,这个过程浏览器会分配一块堆内存用于函数的存储,并记录函数存储的位置(16进制的地址)
即在读取到函数时,先将函数放到堆内存中 记录函数存储的地址

JS代码的执行环境–ECStack

ECStack 执行环境栈,它也是浏览器从内存中拿出的一块内存,但是是栈结构内存,作用是执行代码,而非保存代码。因为栈特点,临时存放变量
当函数执行时首先会形成一个私有作用域,这个作用域是栈 内存 ,然后按以下步骤执行:
1)有形参则给形参赋值
2)私有作用域预解释
3)在私有作用域中将函数代码从上到下执行
函数执行时形成了一个私有作用域保护了作用域内的变量不受外界的干扰,这样的机制就是闭包

栈内存和堆内存区别

💡 栈内存的大小
栈内存由 *** 作系统直接管理,所以栈内存的大小也由 *** 作系统决定。
通常来说,每一条线程(Thread)都会有独立的栈内存空间,Windows 给每条线程分配的栈内存默认大小为 1MB 因此执行完会销毁
缺点:栈内存的容量较小,主要用于存放函数调用信息和变量等数据,大量的内存分配 *** 作会导致栈溢出(Stack overflow)。
所以栈内存的数据储存基本都是临时性的,数据会在使用完之后立即被回收(如函数内创建的局部变量在函数返回后就会被回收)。
优点:
栈内存之所以叫做栈内存,是因为栈内存使用了栈的结构。
栈内存是一段连续的内存空间,得益于栈结构的简单直接,栈内存的访问和 *** 作速度都非常快。

函数存储:在堆中,在栈中保存函数的引用地址
函数进栈执行:
函数执行时形成了一个私有作用域 应该就是常说的函数作用域,而非全局作用域

关于对象使用和函数执行这两种情况的 总结:

在多数情况下,原始类型的数据储存在栈内存,而引用类型的数据(对象)则储存在堆内存。

例如,以下有个函数test,全局变量a=0;test(a)调用,结果会改变全局变量的a的值吗?

var n=0;
function test(n){
 n++;
 return n;
}
test(n);
console.log(n); //0

为什么函数执行不能改变全局变量的值呢?
函数调用时,进栈,将栈分为两部分,一部分存放变量和值,一部分执行代码,返回函数执行结果
就算传入的实参是已经定义的全局变量,函数执行扔不会影响影响全局变量的值,因为又开辟了空间,复制一个新的参数n,改变的n不是全局变量中的n,而是私有栈中新存储的n.
下图说明了,函数执行时,变量在栈中的存储,可以便于理解以上的例子。

几种情况下的对象的使用

let obj={n:1};
obj2=obj;
obj2.n=3
console.log(obj.n)//3  

这时obj2 的值,即指向的地址没改变,都是 *** 作原地址{n:1},复制了 obj的地址,所以改变obj2的属性,也会改变obj的属性值

let obj={n:1};
obj2=obj;
obj2={n:7}
obj2.n=30;
console.log(obj);//应该还是{n:1},

obj2在复制obj的指向地址(值)后,obj2值又被手动改变了, *** 作新地址中的n,和obj就没有关系啦
obj2生成新对象时,就会在栈中创建一个新的存储数据的地址

let obj={n:1};
obj2=obj;
obj={a:1};
console.log(obj2)//{n:1};

创建一个新的对象时,都会在栈新开辟一块空间,存储新的地址,然后从对象变量指向堆中新的地址中的数据

引用: https://blog.csdn.net/qq_36903042/article/details/105267927
https://blog.csdn.net/iFasWind/article/details/112564403

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/3002086.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存