我是靠谱客的博主 整齐冰淇淋,最近开发中收集的这篇文章主要介绍js的堆栈内存学习,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

浏览器想执行一段js代码,顺序是:
编译器(把代码解析成为浏览器看得懂的结构):
词法解析
AST抽象语法树
构建出浏览器能够执行的代码
引擎(V8 / webkit内核):
变量提升
作用域和作用域链、闭包
变量对象
堆栈内存
GO(全局对象,可以理解为Window)、VO(全局下的变量对象)、AO(函数中形成的变量对象),EC,ECStack

实例解释:
实例1:

let a = 12;
let b = a;
b = 13;
console.log(a); //12

js引擎想要执行代码,一定会创建一个执行栈,即栈内存 => ECStack(Execution Context Stack),执行上下文环境栈。
栈内存(stack):

  1. 执行代码
  2. 存储基本类型的值

EC:执行上下文,某个域下的代码执行都有自己的执行上下文
全局:EC(G) global
函数:EC(…)
把创建的上下文压缩到栈中执行 => 进栈
执行完有的上下文就没用了 => 出栈
有的还要用,会把其压缩到栈底,等下次调用 => 闭包

GO : global object ,在浏览器端(不是node),会把全局对象赋值给window = (xxx:xxx…window.setInterval/setTimeout等内置属性)
a = 12 所有的变量赋值三部曲:

  1. 创建变量:声明 declare lrh 左侧词法解析(如果已经创建过,就不会再创建)
  2. 创建值 :基本值直接在栈中创建和存储即可
  3. 让变量和值关联起来(赋值)定义 defined

变量和值的关联称为指针,在以上代码中,a,b称为变量,值可以变化,而12,13是常量,每个都是单独的个体。
let 声明的变量一定是全局变量,只不过不是全局属性。而const 称为不可变的变量(指针不能改变的变量)。
以上代码解读:
let a = 12 创建变量a,创建值12,并赋值给a
let b = a 创建变量b,赋值为a,即12
b = 13 已经有变量b,不再创建b,修改b的值为13
console.log(a); 因为变量a和变量b都是单独的个体,所以a仍然是12

堆内存(heap)
实例2:

let a = {n:12};
let b = a;
b['n']=13
console.log(a['n'])   //13

由于引用值是复杂的结构,所以特殊处理 => 开辟一个存储对象中键值对(存储函数中代码)的内存空间 => 即:“堆内存”
=> 所有堆内存都有一个可被后续查找的16进制地址
=> 后续关联赋值的时候,是把堆内存地址给予变量操作。
以上代码解读:
let a = {n:12}; 创建变量a , 值是复杂类型,故创建一个堆,地址为AAAFFF000,并且将堆地址AAAFFF000赋值给变量a
let b = a;创建变量b,值是一个变量a,故b的值为a的堆地址AAAFFF000,指向同一个堆
在b[‘n’]=13,修改b中的值,首先到AAAFFF000中查找变量n,并且进行修改,n = 13,
在console.log(a[‘n’])时,因为a和b指向的同一个堆,AAAFFF000中的n已经发生改变,故a[‘n’]是13。

实例3:

let a = {n:12};
let b = a;
b = {n:13};
console.log(a['n'])
VM629:1 12

以上代码解读:
let a = {n:12};首先创建a, 然后a的值是一个复杂类型的值,需要创建一个堆即AAAFFF000,并把这个堆地址赋值给a ,
let b = a; 创建变量b,这时,变量b的值是一个变量a,将a的堆地址AAAFFF000赋值给b,
b={n:13},这时,已经有变量b,故不需要重新创建,值是复杂类型的值,所以需要重新创建一个堆,地址为AAAFFF111,n为13,并且赋值给b
console.log(a[‘n’]) 此时,a和b指向的不是同一个堆地址,值互不影响,故a[‘n’]=12。

被占用的堆,不能被销毁。不被占用,空闲的时候就会被销毁。
若想销毁a的值AAAFFF000,直接a = 0 ,这样会销毁AAAFFF000,但是却会在栈中占用内存,故可以使用a = null 空对象指针
null让变量指向空指针,不会开辟内存,取消对堆的引用,实现内存回收。与undefined(声明未赋值)不同,null是意料之中,undefined是意料之外。具体在创建变量使用null还是undefined,取决于后期给它赋不赋值,若后期要赋值,就是用null,不赋值就使用undefined。
null还有一个作用:通过把指针指向一个空指针,实现对某一个内存进行释放。

两道面试题:

let a = {
	n:10
};
let b = a;
b.m = b = {
	n:20
};
console.log(a);   //{n: 10, m: {…}}
console.log(b)   //{n: 20}

解析下上面代码:
创建变量a,值是复杂类型,创建一个堆,AAAFFF000,并且赋值给a
创建变量b,值指向变量a,即与变量a指向同一个堆,AAAFFF000,也可以理解为b = a =AAAFFF000 (内容:{n:10})
b.m = b = {n:20},这种连等,正常逻辑是创建两个变量,因为b是已经有的,不需要创建,指向同一个值,在这里的值是复杂类型,创建堆,地址为AAAFFF111,即b.m和b这两个变量的值都是AAAFFF111,b,m = b= {…},是从左到右的,这是js运算符的优先级,同样等级下,js从左到右运行。b.m先等于这个AAAFFF111。
等级操作顺序可参考地址:js运算符等级

若两个对象同时指向一个堆内存(对象),那么对这个堆的修改是同步的,因为a和b刚开始是指向同一个,所以b.m = a.m,会新建一个变量m,并且会将堆AAAFFF111(内容是{n:20}),赋值给m,即a = {n:10,m:{n:20}},b的值修改为AAAFFF111,即{n: 20}
a和b是无实质性联系,但是值是相关联的。若写a.m.x = 1000;两个堆中都会有改变
关于连等,可以参考文章:https://blog.csdn.net/qiphon3650/article/details/78860973

let x = [12,23];
function fn(y) {
	y[0] = 100;
	y = [100];
	y[1] = 200;
	console.log(y);
}
fn(x);  
console.log(x); 

解析以上代码:
ECStack 执行环境栈
EC(Global)全局执行上下文。
GO 全局对象,有的对象与全局对象不同,则会保存在VO中。
VO(变量对象):存储当前上下文中的变量。
首先创建一个变量x,创建它的值,因为是数组,是引用类型的值,创建的是堆,可为AAAFFF000(内容为:0:12,1:23,length:2),并赋值给变量x,即x= AAAFFF000
创建一个变量函数fn,值是函数类型,语法中使用关键字function,是引用类型的值,所以保存在堆中AAAFFF111(
函数的堆中存储内容:
代码字符串(函数特性):“y[0] = 100,y = 100…”
存储键值对(对象):length = 1 形参个数,name = ‘fn’ , prototype:…原型 …等
所以fn的值就是堆AAAFFF111
)
然后执行fn(x) ⇒ 即fn(AAAFFF000),函数执行的目的就是执行函数中的内容。
当执行全局上下文时,遇到函数,因为每一个函数执行都会形成一个全新的执行上下文在此称为EC(FN),供它自己内部使用,所以,会将没有执行完的全局上下文EC(G)压缩到ECStack栈的底部,把新的东西即函数的执行上下文EC(FN)进栈执行。
在这个函数的上下文EC(FN)中,函数执行过程形成的变量对象,叫做AO,就是活动的变量对象。会执行如下操作:

  1. 初始化内置的实参集合:arguments= {0:AAAFFF000}(类数组集合,所以用{},传的实参是x,即堆AAAFFF000)
  2. 创建形参变量并给他赋值 :y = AAAFFF000
  3. 代码执行
    上述1和2部之间,在非严格模式下会建立映射机制,严格模式下不会,而且ES6箭头函数中没有arguments实参集合。

关于映射机制的实例理解:
非严格模式:

function fn(x,y){
	console.log(x,y,arguments);
	arguments[0] = 100;
	y = 200;
	console.log(x,y,arguments);
}
fn(10,20); 
//function fn(x,y){
	console.log(x,y,arguments);
	arguments[0] = 100;
	y = 200;
	console.log(x,y,arguments);
}
fn(10,20);
//VM166:2 10 20 Arguments(2) [10, 20, callee: ƒ, Symbol(Symbol.iterator): ƒ]
//VM166:5 100 200 Arguments(2) [100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]

严格模式:

"use strict";
function fn(x,y){
	console.log(x,y,arguments);
	arguments[0] = 100;
	y = 200;
	console.log(x,y,arguments);
}
fn(10,20);
//VM258:2 10 20 Arguments(2) [10, 20, callee: (...), Symbol(Symbol.iterator): ƒ]
//VM258:5 10 200 Arguments(2) [100, 20, callee: (...), Symbol(Symbol.iterator): ƒ]

故当EC(FN)函数的上下文中代码执行时,会继续执行的操作:
已经将全局变量x告诉给函数中的私有变量y,此时x和y是有关系的。
当执行y[0]=100; 即 AAAFFF000[0] = 100;此时的AAAFFF000中的内容:[0:100,1:23,length:2]
y = [100]; y是已经有的变量,值是一个复杂的值,创建一个新的堆内存,可以称为BBBFFF000,内容为[0:100,length:1];并且将这个堆BBBFFF000赋值给y,此时私有的y和全局的x是无关的。
继续执行:y[1]=200 ,BBBFFF000中是没有索引为1的,所以需要插入一条1:200,length也需要修改,所以BBBFFF000的内容:[0:100,1:200,length:2],也就是这里要打印的y的值:[0:100,1:200,length:2…]
此时,当前的函数上下文的代码就执行完毕,且没有被外部占用,所以就会执行出栈。之前被压缩的全局上下文会自动继续执行。即执行全局上下文的代码:console.log(x),此时的x:[0:100,1:23,length:2]

再来一个例子:

var x = 10;
~ function(x) {
	console.log(x);
	x = x || 20 && 30 || 40;
	console.log(x)
}();
console.log(x);

代码解读:
在ECStack栈中,有EC(G),里面是全局的执行上下文的对象,也就是VO:x = 10;
往下执行,看到了自执行函数执行(创建+执行),会形成私有作用域
创建一个堆AAAFFF000自执行函数:
内容为:代码字符串“console.log(x);…”
键值对:name:"",
length:1, (形参个数)

然后执行函数,会形成一个私有执行上下文,即EC(自执行函数)
里面有AO活动对象:
传递的实参个数 arguments=()
形参赋值 x = undefined
代码执行:
输出的x为undefined
赋值:x = x || 20 && 30 || 40
“逻辑与” 与“逻辑或”:
“逻辑或”:A || B 如果A为真,返回A,否则返回B
“逻辑与”:A && B 如果A为真,返回B,否则返回A
&& 优先级高于 ||
常用场景:
ES6赋值初始值: function fn(x = 0 ){}
传统方式:

							function fn(x){
								if(typeof x === 'undefined'){
											x = 0
								}
									//或者直接写
									x = x || 0;
							}
								fn && fn() 
								//等价于:
								typeof fn ==='function' ? fn() : null

因为逻辑与优先级高于逻辑或,所以:x = x || 20 && 30 || 40中,会首先计算20 && 30,20为真,返回30
所以赋值操作为x = x || 30 || 40;
因为x是undefined,为假,所以返回30,赋值操作变成:x = 30 || 40; 30 为真,所以就返回30,故x=30
所以在函数的执行上下文中,x打印的值为30
函数执行完后会出栈
全局的上下文继续执行,全局中有x,console.log(x) 值为10。
此demo结束。

在执行上下文里面,所有在变量对象中的变量都是私有的,包括形参,和在对象中声明的。

let x = [1,2],
	y = [3,4];
~ function (x) {
	x.push('A');
	x = x.slice(0);
	x.push('B');
	x = y ;
	x.push('C');
	console.log(x,y);
}(x);
console.log(x,y);

代码解析:
在执行栈中,执行全局的上下文EC(G),首先创建VO(变量对象):
创建变量x,创建值,是引用类型,创建堆AAAFFF000,内容为:[0:1,1:2,length:2],并且将堆AAAFFF000赋值给x
let x = [1,2],
y = [3,4];等价于let x ; let y;
而还有一种操作:
let x= y = 10;等价于let x=10;y;是无let的
所以在此时let 了一个y ,而y的值也是引用类型:创建堆AAAFFF111,内容为:[0:3,1:4,length:2];并且赋值给y,y = AAAFFF111;
在遇到函数时,会创建新的上下文并执行,即EC(自执行函数),会将全局上下文压缩至栈底,先执行函数的上下文:
(x)将全局的变量x当做实参传给函数
会创建AO(活动对象):
传递的实参个数: arguments:{0:AAAFFF000} 传了一个实参,即全局的x,也就是堆AAAFFF000
形参赋值(在当前函数执行上下文中创建的,在变量对象中声明的变量都是自己私有的)
function(x)=>在当前自己函数中创建的变量,不过赋的值是和全局下的x同一个堆 ,即AAAFFF000,所以这个x 是私有变量,
执行代码:
x.push(‘A’); //push 向数组末尾追加
x此时是AAAFFF000,想末尾追加,AAAFFF000的内容为:[0:1,1:2,2:“A”,length:3]
x = x.slice(0); //slice()是从索引n开始找到索引为m(不含m)的值,把找到的新数组返回 ,在此slice(0)从索引0开始到数组的末尾,实现数组浅克隆,把数组的每一项内容完封不动拷贝一份,返回一个新数组,创建一个新的堆BBBFFF000,内容为[0:1,1:2,2:“A”,length:3].并且将堆BBBFFF000赋值为x,x的值不再是堆AAAFFF000,而是堆BBBFFF000
x.push(‘B’); 在堆BBBFFF000中末尾加入3:“B”,此时BBBFFF000的内容为:[0:1,1:2,2:“A”,3:“B”,length:4]
x = y; x是私有的,但在AO中没有y,y不是私有的,就要去查找上级作用域的y,在全局的执行上下文EC(G)中看到有y,y的值是AAAFFF111,所以x = 堆AAAFFF111;即{0:3,1:4,length:2};
x.push(‘C’); 堆AAAFFF111中推入数据C,AAAFFF111的内容变为:[0:3,1:4,2:“C”,length:3];
console.log(x,y);
在此输出的私有x值为AAAFFF111,内容为[0:3,1:4,2:“C”,length:3];控制台输出的是这种格式[3,4,“C”,length:3]
全局y值为:AAAFFF111,内容为[0:3,1:4,2:“C”,length:3];控制台输出的是这种格式[3,4,“C”,length:3]
函数执行完,出栈,继续执行全局执行上下文, console.log(x,y);在此的x,y都是全局的,值分别是:
全局的x:AAAFFF000,[1,2,“A”,length:3]
全局的y :AAAFFF111,[3,4,“C”,length:3]
在现在所有的堆内存中,AAAFFF000和AAAFFF111被x,y占用着,而BBBFFF000没有被占用,浏览器会在空闲的时候,把所有不被占用的堆内存,进行释放和销毁。这就是谷歌浏览器的“垃圾回收机制”(也就是:“内存释放机制”)。
IE浏览器的机制:当前堆被占用一次,记数字1,再被占用一次,数字累加;取消占用,数字减一,一直减到0,则销毁。(IE记着记着可能会记错,导致内存泄漏)

关于&&的使用,在函数中传值回调函数
function fn(callback) {
//默认认为传就是函数,要不然就不传
callback && callback()
}

函数也是变量,和let,var创建的变量本质是一样的,区别为存储的值是个函数类型的值
创建函数:
function fn() {}
let fn = function() {} //函数表达式
xxx.onclick = function(){}
xxx.addEventListener(‘click’,function(){});
(function(){})() 自执行函数,可以支持有返回值
let a = (function(x){
return {}
}){100}
~function(){}()
+/-/!function(){}

最后

以上就是整齐冰淇淋为你收集整理的js的堆栈内存学习的全部内容,希望文章能够帮你解决js的堆栈内存学习所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部