执行上下文

120 阅读5分钟

  执行上下文发挥着什么作用?    

  • JavaScript 里面的代码应该是顺序执行的吧?(你也许会这样认为)  
  • 但一个很简单的例子就可以说明,JavaScript 代码在执行时实际上会发生一些微妙的变化:
   num = 3; 
   var num; 
   console.log(num);
  • num 变量在声明之前就被赋值,居然没有报错???    
  • 这是因为 var num;num = 3; 是属于代码运行的两个不同阶段的任务—— 编译阶段执行阶段。 编译发生在执行之前,所以 var num; 先被执行,num = 3 便不被报错了。   
  • 声明语句(var num)最先被执行好比将声明语句 “移动” 至当前作用域的顶端,这个过程被称作提升。而提升与执行上下文的关系则十分密切(见后文)。

  执行上下文的概念    

  • 当一段 JavaScript 代码在运行的时候,它实际上是运行在执行上下文中。而下列三种代码都会创建相应的执行上下文:

  • 全局执行上下文:它是为运行代码主体而创建的执行上下文,也就是说它是为那些存在于函数之外的任何代码而创建的。

  • 函数执行上下文:每个函数会在执行的时候创建自己的执行上下文。

  • Eval 函数执行上下文:使用 eval() 函数也会创建一个新的执行上下文。 eval() 函数会将传入的字符串当做 JavaScript 代码进行执行

   console.log(eval('2 + 2')); // 4

  执行上下文的创建    

  • 创建执行上下文有明确的几个步骤:1. 确定 this,即经常用到的 this 绑定。
    1. 创建 词法环境(LexicalEnvironment) 组件。
    2. 创建 变量环境组件(VariableEnvironment) 组件。

    确定 this      

  • 在全局执行上下文中,this 总是指向全局对象。例如:浏览器环境下 this 指向 window 对象。        在函数执行上下文中,this 的值取决于函数的调用方式,如果被一个对象调用,那么 this 指向这个对象。否则 this 一般指向全局对象 window 或者 undefined (严格模式)。
    var name = "window"; 
    let hello = {
        name:"hello",
        helloThis: function() {
            console.log(this.name);
        } 
     }
    // window -> hello -> helloThis,由 hello 调用
    hello.helloThis(hello); 
    let ht = hello.helloThis;   
    ht(); //window -> helloThis,由 window 调用

    创建词法环境组件 

  • 词法环境是一个包含标识符变量映射的结构,同时,它还保存对父级词法环境的引用。
  • 这里的标识符表示变量(函数)的名称,变量是对实际对象(包括函数类型对象)或原始值的引用。如:var name = 1;。标识符是 name,引用是 1。      
  • 词法环境由环境记录器与对外部环境的引用两个组件组成。
    • 环境记录器:存储变量和函数声明的实际位置
    • 外部环境的引用:实际上就是对外部或者说是父级词法环境的引用。这对理解闭包是如何工作的尤为重要。
    • 词法环境就是在JavaScript 引擎创建一个执行上下文时,创建的用来存储变量和函数声明的环境,它可以使代码在执行期间,访问到存储在其内部的变量和函数,而在代码执行完毕之后,从内存中释放掉。(通过 var 定义的变量,存在于变量环境。)

    创建变量环境      

变量环境与词法环境十分相似。在 ES6 中,词法环境和变量环境的明显不同就是前者被用来存储函数声明和变量(let/const)的绑定,而后者只用来存储 var 变量的绑定。

  解决疑问    

  • 问: 为什么变量提升和执行上下文关系密切?    
  • 答: 执行上下文创建时,var 定义的变量会被设置为 undefined,而 let/const 定义的变量则未设置任何值(未初始化)。所以,如果在声明之前访问 var 变量将得到值 undefined,访问 let/const 定义的变量就会提示Cannot access ' ' before initialization
  console.log(a) 
  console.log(b) 
  console.log(c) 
  var a = 1 
  let b = 2 
  const c = 3
  • 问: 闭包是什么?    
  • 答: 闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。    
  • 问: 闭包的原理是什么?    
  • 答: 闭包是由函数以及声明该函数的词法环境组合而成的。词法环境存储着父级词法环境(作用域)的引用。

  执行上下文的执行

  • 在执行上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。    
  • 值得注意的是,let 定义的变量(只有 let )如果未赋值,此阶段将赋值为 undefined

  执行上下文的管理(执行栈)    

  • 既然每个函数执行时都会产生相应的执行上下文。如果多的上下文该如何管理呢?    
  • 执行栈存储着所有执行上下文,并遵循着后进先出的原则:
    var say = function(){
        hello(); 
    }
    var hello = function(){
        console.log("Hello,world!"); 
    }
    say();
  1. 创建全局执行上下文,压入执行栈中。
  2. 调用了 say函数,创建 say函数的执行上下文,并压入执行栈中。
  3. 进入 say函数 内部,调用了 hello函数,创建 hello函数的执行上下文,并压入执行栈中。
  4. hello函数执行完了,将 hello 移出执行栈
  5. say函数执行完了,将 say 移出执行栈