JavaScript执行机制之执行栈与执行过程


title: js执行栈与执行过程

执行栈与执行过程

一、执行栈的概念

由于 javascript 是单线程,只能在主线程上运行。

所以为了管理同一线程上的多个执行上下文(函数),出现了 执行栈 的概念,它也被叫做 调用栈

特点:后进先出(LIFO)的结构。

作用:存储在代码执行期间的所有执行上下文。

LIFO: last-in, first-out,类似于向乒乓球桶中放球,最先放入的球最后取出)

img2

js在首次执行的时候,会创建一个全局执行上下文并推入栈中。

每当有函数被调用时,引擎都会为该函数创建一个新的函数执行上下文然后推入栈中。

当栈顶的函数执行完毕之后,该函数对应的执行上下文就会从执行栈中推出,然后上下文控制权移到下一个执行上下文。

比如下面的一个例子🌰:

var a = 1; // 1. 全局上下文环境

function bar (x) { // 2. 函数上下文环境
    console.log('bar')
    var b = 2;
    fn(x + b); // 3. 闭包函数上下文环境
}

function fn (c) {
    console.log(c);
}

bar(3);
// bar
// 5

如下图:

context1

执行上下文栈的变化

先来看看下面两段代码, 在执行结果上是一样的, 那么它们在执行的过程中有什么不同吗?

var scope = "global";
function checkScope () {
	var scope = "local";
	function fn () {
		return scope;
	}
	return fn();
}
checkScope();
var scope = "global"
function checkScope () {
	var scope = "local"
	function fn () {
		return scope
	}
	return fn;
}
checkScope()();

答案是 执行上下文栈的变化不一样。

在第一段代码中, 栈的变化是这样的:

ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();

可以看到fn后被推入栈中, 但是先执行了, 所以先被推出栈;


而在第二段中, 栈的变化为:

ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();

由于checkscope是先推入栈中且先执行的, 所以在fn被执行前就被推出了.

二、执行过程

VO/AO

接下来要介绍两个概念:

  • VO(变量对象), 也就是variable object, 创建执行上下文时与之关联的会有一个变量对象,该上下文中的所有变量和函数全都保存在这个对象中。

  • AO(活动对象), 也就是``activation object`,进入到一个执行上下文时,此执行上下文中的变量和函数都可以被访问到,可以理解为被激活了。

活动对象和变量对象的区别在于:

  • 变量对象(VO)是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。
  • 当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。

上面似乎说的比较难理解😢, 没关系, 我们慢慢来看.

执行过程

首先来看看一个**执行上下文(EC)**被创建和执行的过程:

  1. 创建阶段:
  • 创建变量、参数、函数arguments对象;

  • 建立作用域链;

  • 确定this的值.

  1. 执行阶段:

变量赋值, 函数引用, 执行代码.

进入执行上下文

在创建阶段, 也就是还没有执行代码之前

此时的变量对象包括(如下顺序初始化):

  1. 函数的所有形参(仅在函数上下文): 没有实参, 属性值为undefined;
  2. 函数声明:如果变量对象已经存在相同名称的属性,则完全替换这个属性;
  3. 变量声明:如果变量名称跟已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性

一起来看下面的例子🌰:

function fn (a) {
	var b = 2;
	function c () {};
	var d = function {};
	b = 20
}
fn(1)

对于上面的例子, 此时的AO是:

AO = {
	arguments: {
		0: 1,
		length: 1
	},
	a: 1,
	b: undefined,
	c: reference to function c() {},
	d: undefined
}

可以看到, 形参arguments此时已经有赋值了, 但是变量还是undefined.

代码执行

到了代码执行时, 会修改变量对象的值, 执行完后AO如下:

AO = {
	arguments: {
		0: 1,
		length: 1
	},
	a: 1,
	b: 20,
	c: reference to function c() {},
	d: reference to function d() {}
}

在此阶段, 前面的变量对象中的值就会被赋值了, 此时变量对象处于激活状态.

总结

  • 全局上下文的变量对象初始化是全局对象, 而函数上下文的变量对象初始化只有Arguments对象;

  • EC创建阶段分为创建阶段和代码执行阶段;

  • 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值;

  • 在代码执行阶段,会再次修改变量对象的属性值.

后语

参考文章:

《聊一聊javascript执行上下文》

《木易杨前端进阶-JavaScript深入之执行上下文栈和变量对象》

霖呆呆的blog

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值