1

什么是执行上下文?

执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。

执行上下文的意义在于,它给出一个抽象模型,可以更简单得了解js的运行机制。同时,执行上下文对理解js内存,闭包,垃圾回收等具有深刻意义,它可以让我们在不是很了解底层代码的情况下去分析内存和执行过程。

执行上下文可以被分为三类:

  • 全局执行上下文:只有一个,在客户端由浏览器创建,一般为window;
  • 函数执行上下文:可以有无数个,只有在函数调用时才会被创建,每次调用函数都会创建一个新的执行上下文;
  • eval执行上下文:指的是在eval中的代码,一般不推荐使用。

这么多执行上下文,可以使用执行栈来进行管理。

执行栈

执行栈,也叫调用栈,它是一个LIFO的结构,用于储存代码在执行期间所需要的上下文。

在代码首次执行时,会将全局执行上下文放入栈底,然后再根据函数调用,向栈顶中压入函数执行上下文。当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。

var a = 'Hello World!';

function first() {  
  console.log('Inside first function');  
  second();  
  console.log('Again inside first function');  
}

function second() {  
  console.log('Inside second function');  
}

first();  
console.log('Inside Global Execution Context');

// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context

image.png

执行上下文的创建

执行上下文的创建分为三个阶段:

  • 确定this指向
  • 创建词法环境
  • 创建变量环境

确定this指向

  • 全局上下文中,this指向全局对象,在浏览器中,this指向window对象,在nodejs中,this指向文件所在的module;
  • 函数上下文中,this的指向需要看函数的调用方式。

创建词法环境

词法环境由两个部分组成:

  • 环境记录:存储定义的变量和函数;
  • 对外部环境的引用:可以访问的外部词法环境

词法环境又可以分为:

  • 全局环境:是一个没有外部环境的词法环境,所以对外部环境的引用为null,拥有全局对象(window)及其关联的属性和方法以及用户自定义全局变量;
  • 函数环境:对外部环境的引用可以是全局环境或者函数环境,用户在函数中定义的变量存储在环境记录当中。

可以看下面的伪代码更加直观:

GobelExectionContext = {           // 全局执行上下文
    LexicalEnvironment: {          // 词法环境
        EnvironmentRecord: {       // 环境记录
            Type: "Object",        // 对象环境记录
            ......
        },  
        outer: <null>                // 对外部环境的引用
    }
}
FunctionExectionContext = {       // 函数执行上下文
    LexicalEnvironment: {         // 词法环境
        EnvironmentRecord: {      // 环境记录
            Type: "Declarative",  // 声明性环境记录
            ......
        }, 
        outer: <GobelEvironment or FunctionEvironment>  // 对外部环境的引用
    }
}

创建变量环境

变量环境也是一个词法环境,所以它有上面词法环境所拥有的属性。

在es6中,变量环境和词法环境的区别在于,前者用于存储var定义的变量,后者用于存储声明的函数和变量(let,const)。

可以看下面例子:

let a = 20;  
const b = 30;  
var c;

function multiply(e, f) {  
    var g = 20;  
    return e * f * g;  
}

c = multiply(20, 30);

执行上下文如下:

GobelExectionContext = {           // 全局执行上下文
    LexicalEnvironment: {          // 词法环境
        EnvironmentRecord: {       // 环境记录
            Type: "Object",        // 对象环境记录
            a: < uninitialized >,
            b: < uninitialized >,
            multiply: < func >
        },  
        outer: <null>               // 对外部环境的引用
    }
    
    VariableEnvironment: {          // 变量环境
        EnvironmentRecord: {        
            Type: "Object",         // 对象环境记录
            c: undefined,  
        }  
        outer: <null>      
    }  
}
FunctionExectionContext = {       // 函数执行上下文
    LexicalEnvironment: {         // 词法环境
        EnvironmentRecord: {      // 环境记录
            Type: "Declarative",  // 声明性环境记录
            Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GobelLexicalEvironment> 
    }
    
    VariableEnvironment: {  
        EnvironmentRecord: {  
            Type: "Declarative",   // 声明性环境记录
            g: undefined,  
        }  
        outer: <GobelLexicalEvironment> 
    }
}

这里可以看出变量提升的原因:变量储存在变量环境中为undefine(使用var声明);变量储存在词法环境中为uninitialized(使用let和const声明),所以在使用let和const声明之前使用变量会报错。

执行上下文的执行

在此阶段完成对变量的赋值,即上面的执行上下文将变成:

GobelExectionContext = {           // 全局执行上下文
    LexicalEnvironment: {          // 词法环境
        EnvironmentRecord: {       // 环境记录
            Type: "Object",        // 对象环境记录
            a: 20,
            b: 30,
            multiply: < func >
        },  
        outer: <null>               // 对外部环境的引用
    }
    
    VariableEnvironment: {          // 变量环境
        EnvironmentRecord: {        
            Type: "Object",         // 对象环境记录
            c: <multiply>,  
        }  
        outer: <null>      
    }  
}
FunctionExectionContext = {       // 函数执行上下文
    LexicalEnvironment: {         // 词法环境
        EnvironmentRecord: {      // 环境记录
            Type: "Declarative",  // 声明性环境记录
            Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GobelLexicalEvironment> 
    }
    
    VariableEnvironment: {  
        EnvironmentRecord: {  
            Type: "Declarative",  // 声明性环境记录
            g: 20,  
        }  
        outer: <GobelLexicalEvironment> 
    }
}
以上内容均为作者个人理解,如有错误,请各位多多指出!

参考资料:
一篇文章看懂JS执行上下文
理解JavaScript 中的执行上下文和执行栈
js没那么简单(1)-- 执行上下文
js预编译


习文
25 声望11 粉丝