理解JavaScript中的执行上下文

代码寻幽者
• 阅读 1177

什么是执行上下文?

执行上下文是当前 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

理解JavaScript中的执行上下文

执行上下文的创建

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

  • 确定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预编译

点赞
收藏
评论区
推荐文章
Souleigh ✨ Souleigh ✨
4年前
JS - 从执行上下文的角度来理解闭包
今天看到一篇关于闭包的文章,里面有这样一句话“就我而言对于闭包的理解仅止步于一些概念,看到相关代码知道这是个闭包,但闭包能解决哪些问题场景我了解的并不多”,这说的不就是我么,每每在面试中被问及什么是闭包,大部分情况下得到的答复是(至少我以前是)A函数嵌套B函数,B函数使用了A函数的内部变量,且A函数返回B函数,这就是闭包。而往往面试官想要听到的并不是这样的
巴拉米 巴拉米
4年前
bind、call、apply 区别?如何实现一个bind?
一、作用call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向那么什么情况下需要改变this的指向呢?下面举个例子var name"lucy";const obj{    name:"martin",    say:function (){        co
Python进阶者 Python进阶者
3年前
手把手教会你JavaScript引擎如何执行JavaScript代码
JavaScript在运行过程中与其他语言有所不一样,如果不理解JavaScript的词法环境、执行上下文等内容,很容易会在开发过程中产生Bug,比如this指向和预期不一致、某个变量不知道为什么被改了,等等。所以今天我们就来聊一聊JavaScript代码的运行过程。大家都知道,JavaScript代码是需要在JavaScript引擎中运行
御弟哥哥 御弟哥哥
4年前
彻底理解js的作用域链
在之前的文章(https://www.helloworld.net/p/G4dFV7tALU4J)中我已经介绍了执行上下文的变量对象。在这一篇文章我要介绍执行上下文的作用域链了。执行上下文.作用域链(scopechain)作用域链与变量对象有着密不可分的关系,因为作用域链就是变量对象的数组!其中第
御弟哥哥 御弟哥哥
4年前
彻底理解js的执行上下文,以及变量对象
在js中,执行上下文(ExecutionContext)是非常重要的一种对象,它保存着函数执行所需的重要信息,其中有三个属性:变量对象(variableobject),作用域链(scopechain),this指针(thisvalue),它们影响着变量的解析,变量作用域和函数this的指向。上下文栈(ExecutionContextS
Stella981 Stella981
3年前
Javascript解析机制 执行机制
HTML5学堂:在学习JavaScript过程中,我们需要了解事件的机制是怎么执行的?本文将会提到JavaScript事件机制的解析,希望对大家有帮助!javascript解析的过程主要分为两个阶段,分别是编译与执行阶段。在编译期,javascript解释器将完成对javascript代码的预处理,即将javascript代码转换为字节码。在执行
Stella981 Stella981
3年前
Spring Bean垃圾回收
覆盖对象的finalizefinalize()并不是必须要执行的,它只能执行一次或者0次。_SpringBean垃圾回收肯定是在关闭Spring上下文之后._Rumenz.javapackagecom.rumenz;publicclassRumenz{publicv
Stella981 Stella981
3年前
JavaScript 性能优化技巧分享
JavaScript作为当前最为常见的直译式脚本语言,已经广泛应用于Web应用开发中。为了提高Web应用的性能,从JavaScript的性能优化方向入手,会是一个很好的选择。本文从加载、上下文、解析、编译、执行和捆绑等多个方面来讲解JavaScript的性能优化技巧,以便让更多的前端开发人员掌握这方面知识。什么是高性能的JavaScr
Stella981 Stella981
3年前
PostgreSQL
内存上下文背景:需要经常处理大量以指针传值的查询,存在内存泄漏的问题,直到查询结束才能收回内存。所以实现了新的内存管理机制内存上下文(MemoryContext)内存上下文通俗解释:一个内存上下文相当于一个进程环境,进程环境间不互相影响,pgSQL提供了在内存上下文进行内存操作的函数:pallloc、pfree、repalloc
Wesley13 Wesley13
3年前
(ES5版)深入理解 JavaScript 执行上下文和执行栈
!(https://oscimg.oschina.net/oscnet/9223e9dacee346f68780d67ac0877f7d.png)译者序最近在研究JavaScript基础性的东西,但是看到对于执行上下文的解释我发现有两种,一种是执行上下文包含:scope(作用域)、variableobj
Stella981 Stella981
3年前
JavaScript:垃圾收集机制
  JavaScript具有自动垃圾收集机制。也就是说,执行环境会负责管理代码执行过程中使用的内存。开发人员不必关心内存分配和回收问题。  垃圾收集机制的原理:找到不再继续使用的变量,然后进行释放其占用的内存。所以,垃圾收集器会按照固定的时间间隔(或代码执行中设定的收集时间)持续执行这一操作。  垃圾收集器会跟踪哪些变量有用哪些变量没用,对没用的变量