# 执行上下文

当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”, 就叫做"执行上下文(execution context)"。然后会将函数执行上下文压入执行上下文栈。

# 执行上下文

执行上下文是 JavaScript 执行一段代码时的运行环境。每个执行上下文,都有重要的属性:

  • 变量环境。
  • 词法环境。
  • 可执行代码。
  • 作用域链。
  • this。

# 变量提升。

foo();
myName;
var myName = 'heihei';
function foo() {
	console.log('嘿嘿');
}

按照我们的正常的逻辑来说,这其实是会报错的.

  1. 执行函数 foo ,找不到 foo 就报错。
  2. 执行 myName ,找不到就报错。

所以我们可以得出一个结论。

  1. 变量定义之前就使用它,该值为 undefined。
  2. 函数定义之前使用它,会正常执行函数。

变量提升:

是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。

但是变量和函数在代码的位置是不会变的,而是在编译阶段被JavaScript 引擎放入内存中。

# 变量环境。

当进入执行上下文时,这时候还没有执行代码。

变量对象会包括:

  1. 函数的所有形参 (如果是函数上下文)

    • 由名称和对应值组成的一个变量对象的属性被创建。
    • 没有实参,属性值设为 undefined.
  2. 函数声明。

    • 由名称和对应值组成一个变量对象的属性被创建。
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性。
  3. 变量声明

    • 由名称和对应值 (undefined),组成一个变量对象的属性被创建。
    • 如果变量名称根已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

js 会把声明以外的代码编译为字节码(可执行的代码)。

# 词法环境

在 ES6 之前,ES 的作用域只有两种:全局作用域和函数作用域。

变量提升所带来的问题:

  1. 变量容易在不被察觉的情况下被覆盖掉
  2. 本应销毁的变量没有被销毁

ES6 是如何解决变量提升带来的缺陷,ES6 引入了 let 和 const 关键字。

function foo(){ 
	var a = 1
	let b = 2 
	{
		 let b = 3 
		 var c = 4 
		 let d = 5 
		 console.log(a) 
		 console.log(b) 
	 } 
	 console.log(b) 
	 console.log(c) 
	 console.log(d)
} 
foo()

接下来我们就来一步步分析上面这段代码的执行流程。

第一步是编译并创建执行上下文 在词法环境内部,维护了一个小型栈结构, 栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶,当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。

变量查找过程

# 作用域链

函数在创建的时候有一个内部属性 [[scope]] ,里面保存所有父变量对象到其中, [[scope]] 就是所有父变量对象的层级链,但是并不是完整的作用域链子。

在每个执行上下文的变量环境中, 会将活动对象添加到作用链的前端。

Scope = [当前函数作用域].concat([[scope]])

当一段代码使用了一个变量时,JavaScript 引擎首先会在“当前的执行上下文”中查找该变量。

如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找。

如果在 bar 函数或者 foo 函数中使用了外部变量,JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为作用域链。

foo 函数调用的 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?

这是因为词法作用域(看词法作用域章节)。

# 执行阶段

JavaScript 引擎开始执行“可执行代码”,按照顺序一行一行地执行。

看一个更加完整的变量环境。

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};
  b = 3;
}

foo(1);

在进入执行上下文之后:

代码执行

# 最终的函数的生命周期

  1. foo 函数被创建,保存作用域到内部属性 [[scope]].

  2. foo 函数被执行,创建 foo 函数执行上下文,foo 函数执行上下文被压入执行上下文栈。

  3. foo 并不会立即执行,先会复制函数 [[scope]] 创建作用域链.

  4. 引擎把变量的声明部分和函数的声明部分。来收集变量环境和词法环境。

  5. 将环境压入 foo 作用域链顶端.

  6. 生成可执行代码。

  7. 开始执行函数,修改变量环境、词法环境的属性值。

  8. 返回后函数执行完毕,函数从上下文栈中弹出。