前端基本功(六):javascript为何会存在变量提高(执行上下文、执行上下文堆栈)

1. 什么是执行上下文

当 JavaScript 代码执行一段可执行代码(executable code)时,会建立对应的执行上下文(execution context)。javascript

2. 可执行代码(EC)的类型

  1. 全局代码
  2. 函数代码
  3. Eval代码

3. 执行上下文堆栈

  1. 浏览器里的JavaScript解释器被实现为单线程。这意味着同一时间只能发生一件事情,其余的行文或事件将会被放在叫作执行栈里面排队。JavaScript 引擎建立了执行上下文栈(Execution context stack,ECS)来管理执行上下文。
  2. 当 JavaScript 开始要解释执行代码的时候,最早遇到的就是全局代码,因此初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,咱们用 globalContext 表示它,而且只有当整个应用程序结束的时候,ECStack 才会被清空,因此程序结束以前, ECStack 最底部永远有个 globalContext。
  3. 在你的全局代码中调用一个函数,你程序的时序将进入被调用的函数,并建立一个新的执行上下文,并将新建立的上下文压入执行栈的顶部,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。

4. 调用执行上下文,分为两个阶段

  1. 建立阶段【当函数被调用,但未执行任何其内部代码以前】:
    • 建立做用域链(Scope Chain
    • 建立变量,函数和参数(该过程是有前后顺序的:函数的形参==>>函数声明==>>变量声明
    • 求”this“的值
  2. 激活/代码执行阶段:
    • 初始化变量的值和函数的引用,解释/执行代码。

5. 解释器执行代码的伪逻辑

  1. 查找调用函数的代码。
  2. 执行代码以前,先进入建立上下文阶段:
    • 初始化做用域链
    • 建立变量对象:
      • 建立arguments对象,检查上下文,初始化参数名称和值并建立引用的复制。
      • 扫描上下文的函数声明(而非函数表达式):为发现的每个函数,在变量对象上建立一个属性——确切的说是函数的名字——其有一个指向函数在内存中的引用。若是函数的名字已经存在,引用指针将被重写。
      • 扫描上下文的变量声明:为发现的每一个变量声明,在变量对象上建立一个属性——就是变量的名字,而且将变量的值初始化为undefined。若是变量的名字已经在变量对象里存在,将不会进行任何操做并继续扫描。
    • 求出上下文内部“this”的值。
  3. 激活/代码执行阶段:在当前上下文上运行/解释函数代码,并随着代码一行行执行指派变量的值。

5. 提高(Hoisting)

(function() {
    console.log(typeof foo); // 函数指针
    console.log(typeof bar); // undefined

    var foo = 'hello',
        bar = function() {
            return 'world';
        };
        
    function foo() {
        return 'hello';
    }
}());
复制代码
  1. 为何咱们能在foo声明以前访问它?java

    回想在执行代码以前,先进入建立阶段,咱们知道函数在该阶段就已经被建立在变量对象中。因此在函数开始执行以前,foo已经被定义了。浏览器

  2. Foo被声明了两次,为何foo显示为函数而不是undefined或字符串?bash

    咱们知道,在建立阶段,函数声明是优先于变量被建立的。并且在变量的建立过程当中,若是发现变量对象VO中已经存在相同名称的属性,则不会影响已经存在的属性。所以,对foo()函数的引用首先被建立在活动对象里,而且当咱们解释到var foo时,咱们看见foo属性名已经存在,因此代码什么都不作并继续执行。函数

  3. 为何bar的值是undefined?ui

    bar采用的是函数表达式的方式来定义的,因此bar其实是一个变量,但变量的值是函数,而且咱们知道变量在建立阶段被建立但他们被初始化为undefined,这也是为何函数表达式不会被提高的缘由。this

6. 总结

  1. 执行上下文(EC)分为两个阶段,建立执行上下文和执行代码。
  2. EC建立的过程是由前后顺序的:参数声明 > 函数声明> 变量声明。
相关文章
相关标签/搜索