JS执行 调用栈

v8解析js代码后,不会直接运行,会先编译,生成上下文后,再运行。

  1. 编译代码,生成全局上下文
  2. 进入函数后,创建运行上下文,函数运行结束后,运行上下文被销毁
  3. 进入eval后,创建运行上下文。

运行前,首先会将全局上下文压入调用栈。执行代码期间,如果遇到变量赋值等操作会改变一下全局上下文中的变量环境。如果遇到函数,会创建函数的运行上下文,并压入调用栈。函数内的过程和全局过程是一次递归。函数运行结束后,上下文销毁,出栈。直至全局代码运行完。

调用栈可能会爆栈。当递归次数比较多的时候,通常会上万,这时候调用栈会溢出。实验表明,即使是尾递归再当前的v8版本也会爆栈,再Safari中不会,应该是被优化成了迭代。

ES6中的箭头函数不会创建自己的执行上下文

作用域链

代码中出现相同的变量,JS引擎如何来决定用哪一个呢?

如下代码:

function foo() {
	console.log(title);
}
function bar() {
	var title = 'bar';
	foo();
}
var title = 'global';
bar();
 

输出结果为: global;

JS执行 调用栈中讲到,js的执行环境由调用栈来控制。不妨站在这个角度看下。

  1. 首先进入全局上下文,从头开始运行,函数定义、变量定义,变量赋值
  2. bar()调用,创建bar的执行上下文,变量定义、赋值
  3. foo()调用,创建foo的执行上下文,这时候,需要打印出title的值。
  4. foo执行完毕,销毁上下文。bar执行完毕,销毁上下文。全局执行环境,销毁上下文。

可见,title并没有直接取调用栈最上买呢的bar的上下文中的变量环境,而是取了global的值。

这是因为调用栈中,变量环境中都有一个outer的引用,用来指向外部执行环境,如果变量没有在当环境中找到,那么就会取外部执行环境来取。这个过程叫做作用域链,那为啥这个外部环境不是bar呢?

这是因为js的 作用域链是由词法作用域决定的,而这个词法作用域和运行时是没有任何关系的。看下面的代码:

function bar() {
	function foo() {
		console.log(title);
	}
	var title = 'bar';
	foo();
}
var title = 'global';
bar();
 

输出 bar

之前一直知道js的作用域,但是在分析代码运行的时候,从来没有站在调用栈的角度去思考问题。