深入学习JavaScript系列(一)——ES6中的JS执行上下文

2,147 阅读14分钟

“ 本文正在参加「金石计划」 ”

本篇为此系列第一篇,本系列文章会在后续学习后持续更新。

 第一篇:#深入学习JavaScript系列(一)—— ES6中的JS执行上下文

第二篇:# 深入学习JavaScript系列(二)——作用域和作用域链

第三篇:# 深入学习JavaScript系列(三)——this

第四篇:# 深入学习JavaScript系列(四)——JS闭包

第五篇:# 深入学习JavaScript系列(五)——原型/原型链

第六篇: # 深入学习JavaScript系列(六)——对象/继承

第七篇:# 深入学习JavaScript系列(七)——Promise async/await generator

JS执行上下文这三个概念可以说是每一个前端必须会的,但是总是学了忘记,忘了又学,理解不够深刻,之前都是用背面试题的方式去学习,今天打算深入理解一下,参考文章都放在文末。

为什么特意强调es5中呢,是因为在查阅资料中发现,其实执行上下文不同版本解释不一样的

这里我简述一下区别:

三种版本的执行上下文

ES3中的执行上下文:

在ES3中,JavaScript执行上下文是指在代码执行期间创建的环境,其中包含了变量、函数、对象等信息。每个执行上下文都有一个与之关联的变量对象,用于存储该上下文中定义的变量和函数。在ES3中,执行上下文分为三种类型:

  1. 全局执行上下文:在代码执行之前创建,包含全局变量和函数。

  2. 函数执行上下文:在函数被调用时创建,包含函数参数、局部变量和函数。

  3. eval执行上下文:在eval函数被调用时创建,包含eval函数中定义的变量和函数。

在ES3中,执行上下文的创建和销毁是由JavaScript引擎自动管理的,开发者无法手动干预。同时,ES3中的执行上下文机制相对简单,没有ES6中的诸多特性,如块级作用域、let和const关键字等。

ES5中的执行上下文

在ES5中,执行上下文是指JavaScript代码在运行时创建的一个环境,用于管理函数调用、变量声明、作用域链等。每个执行上下文都有一个与之关联的变量对象,用于存储该上下文中定义的变量和函数。在函数执行时,会创建一个新的执行上下文,并将其添加到执行上下文栈中。当函数执行完毕后,该执行上下文会被弹出栈,并销毁。

在ES5中,执行上下文分为三种类型:全局执行上下文、函数执行上下文和eval执行上下文。全局执行上下文是在代码执行之前创建的,而函数执行上下文和eval执行上下文是在函数调用或eval调用时创建的。在ES5中,执行上下文的创建和销毁是由JavaScript引擎自动管理的,开发者无需手动干预。

es2018的执行上下文

在ES2018中,执行上下文被定义为一个抽象的概念,用于描述JavaScript代码在执行时的环境和状态。每当JavaScript代码开始执行时,都会创建一个新的执行上下文,并将其添加到执行上下文栈中。执行上下文栈是一个后进先出(LIFO)的数据结构,用于跟踪当前正在执行的代码的上下文。

执行上下文包含三个重要的组成部分:

  1. 变量环境(Variable Environment):包含了当前执行上下文中的变量、函数声明和函数参数等信息。

  2. 词法环境(Lexical Environment):与变量环境类似,但是还包含了当前执行上下文所处的词法作用域。

3. this 值:指向当前执行上下文所属的对象。

执行上下文的创建过程可以分为两个阶段:

  1. 创建阶段(Creation Phase):在这个阶段,JavaScript引擎会创建变量环境、词法环境和this值,并将它们添加到执行上下文中。同时,JavaScript引擎还会扫描当前执行上下文中的代码,查找所有的函数声明和变量声明,并将它们添加到变量环境和词法环境中。

  2. 执行阶段(Execution Phase):在这个阶段,JavaScript引擎会按照代码的顺序执行当前执行上下文中的代码。在执行过程中,JavaScript引擎会根据需要从变量环境和词法环境中读取变量和函数,并将它们添加到当前执行上下文的作用域链中。

关于es2018的执行上下文已经有大佬写的很详细,可以看文末参考二链接。

区别:

在ES3中,执行上下文被称为“执行环境”,它是一个对象,包含了当前代码执行时所需的所有变量、函数和参数。每个函数都有自己的执行环境,当函数被调用时,它的执行环境就会被创建。

在ES5中,执行上下文被称为“执行上下文”,它是一个抽象的概念,用于描述代码执行时的环境。每个函数都有自己的执行上下文,它包含了当前函数的变量、函数和参数。在ES5中,执行上下文被分为三种类型:全局执行上下文、函数执行上下文和eval执行上下文。

在ES2018中,执行上下文被称为“词法环境”,它是一个抽象的概念,用于描述代码执行时的环境。每个函数都有自己的词法环境,它包含了当前函数的变量、函数和参数。在ES2018中,词法环境被分为两种类型:全局词法环境和函数词法环境。此外,ES2018还引入了块级作用域,每个块级作用域都有自己的词法环境。

注:下面写的是目前主流学习的执行上下文 es6版本的

执行上下文

1 执行上下文的概念

执行上下文是评估和执行JavaScript代码的环境的抽象概念,js代码在运行的时候都是在执行和执行上下文中运行的

简单理解就是当前代码的执行环境,注意是执行环境。

2 执行上下文的类型

全局执行上下文:一个程序中只有 一个 全局上下文,任何不在行数内部的代码,都属于全局上下文,也就意味着this的指向是window对象(浏览器环境下)

函数执行上下文: 函数上下文是在函数被调用的时候创建的,函数上下文可以有 很多个,调用一次函数生成一个,执行顺序是函数被调用的顺序。

Eval函数执行上下文:日常开发不会用到,是在执行eval函数内部的代码市会有自己的执行上下文。

3 如何创建执行上下文

创建时间:

  1. 全局执行上下文:在代码执行之前创建,在JavaScript代码开始执行时创建的。当浏览器加载JavaScript文件时,它会首先创建全局执行上下文,然后开始执行代码。全局执行上下文包含了全局变量、函数和其他的JavaScript代码。

  2. 函数执行上下文:在函数被调用时创建,当函数被调用时,JavaScript引擎会创建一个新的执行上下文,并将其压入执行上下文栈中。在函数执行完毕后,该执行上下文会被弹出栈。每个函数都有自己的执行上下文,它包含了函数内部的变量、函数参数、函数声明等信息。

  3. Eval 执行上下文:在 eval() 函数被调用时创建,它代表了 eval() 函数的环境,包括 eval() 函数中定义的变量、函数等。

总之,执行上下文是在代码执行之前创建的,它们代表了代码执行时的环境,包括变量、函数、参数等

创建过程

主要有两个阶段:(1)创建阶段和(2)执行阶段

创建阶段——会发生三件事情

  1. this值的决定,就是this绑定
  2. 创建词法环境组件
  3. 创建变量环境组件

整体过程

  1. 创建变量对象(Variable Object):JavaScript引擎会创建一个变量对象,用于存储当前执行上下文中的变量、函数声明和形参等信息。

  2. 建立作用域链(Scope Chain):JavaScript引擎会建立一个作用域链,用于解析变量和函数的作用域。

  3. 确定this指向:JavaScript引擎会确定当前执行上下文中的this指向。

  4. 执行代码:JavaScript引擎会执行当前执行上下文中的代码,包括变量赋值、函数调用、条件语句、循环语句等。

  5. 返回执行结果:JavaScript引擎会返回执行结果,如果是函数调用,则将返回值存储在调用栈中,等待下一次执行。

4 this绑定

在全局执行上下文中,this对象指向全局对象(浏览器环境指的是window) 在函数执行上下文中,this取决于该函数是否被调用,且是被什么调用,如果是一个引用对象调用,那么this会被设置成那个对象,(这里在后续深入学习this调用时更改为:this的指向,是在函数被调用的时候确定的在函数执行过程中,this一旦被确定,就不可更改了严格模式下:如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined,非严格模式下指向全局,是有点绕 在后续的this文章中我会仔细解释

如果函数没有被调用,那么此时this的值会被设置成全局对象或者underfined(严格模式下,如果函数没有被绑定到任何对象上,函数执行上下文中的this会被设置为undefined。这是因为在这种情况下,函数没有任何上下文可依赖,因此this被设置为undefined。)

let foo = {
  baz: function() {
  console.log(this);
  }
}

foo.baz();   // 'this' 引用 'foo', 因为 'baz' 被
             // 对象 'foo' 调用

let bar = foo.baz;

bar();       // 'this' 指向全局 window 对象,因为
             // 没有指定引用对象 如果是严格模式指向underfined

5 词法环境

JavaScript执行上下文中的词法环境是一个存储变量和函数的数据结构,它与当前执行的代码块相关联。它包含了当前代码块中定义的所有变量和函数以及它们的作用域链,作用域链是一个指向父级词法环境的指针,它允许代码块访问外部作用域中的变量和函数。当代码块执行完毕时,它的词法环境会被销毁,其中的变量和函数也会被释放。

重点:一种存储变量,函数,作用域链的数据结构,允许访问外部变量和函数,执行完毕后被销毁

主要由两个组件组成: 环境记录器和外部环境的引用

  1. 环境记录器是存储变量和函数声明的地方
  2. 外部环境引用是指通过作用域链可以访问父级词法环境

词法环境有两种类型:

  • 全局环境(在全局执行上下文中)是没有外部环境引用的词法环境。全局环境的外部环境引用是 null。它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且 this的值指向全局对象。
  • 函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。

环境记录器也有两种类型(如上!):

  1. 声明式环境记录器存储变量、函数和参数。
  2. 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系。

6 变量环境

变量环境本质上也是一个词法环境,是一个存储变量和函数的地方,它是一个JavaScript引擎在执行代码时创建的一个内部数据结构。变量环境包含了当前执行上下文中的所有变量、函数和参数,以及它们的值和引用。当JavaScript引擎执行代码时,它会根据当前执行上下文的类型创建一个对应的变量环境,以便在执行过程中能够正确地访问和操作变量和函数。在函数执行时,每个函数都会创建一个新的变量环境,用于存储该函数的局部变量和参数。在全局执行上下文中,变量环境包含了所有全局变量和函数。

那变量环境和词法环境有什么区别呢?

词法环境是指在代码*编写期间定义的环境,它包含了当前代码所在的作用域链和变量对象。词法环境是静态的,一旦定义就不会改变。

变量环境是指在代码执行期间创建的环境,它包含了当前代码执行时所需的所有变量和函数。变量环境是动态的,随着代码执行而改变。

因此,词法环境和变量环境的区别在于它们的创建时机和作用范围。词法环境是在代码编写期间创建的,作用范围是静态的;而变量环境是在代码执行期间创建的,作用范围是动态的。

7 代码题:

题目一:

console.log(a);   // 这里会打印出什么?
var a = 20;

// undefined 因为变量提升了但赋值没有提升

题目二:


function foo() {

    console.log('foo1');

}

foo();  // foo2

function foo() {

    console.log('foo2');

}

foo(); // foo2

// js中的代码并非一行一行执行,而是一段段执行,同时分为准备阶段和执行阶段,
// 准备阶段存在变量提升和函数提升,然后才执行,第二个foo()在准备阶段被提升了
// 重点 函数会提升 提升之后就覆盖了

题目三:

var message = 'global'
function foo(){
    console.log(message)
}
function bar (){
    var message = 'Bar'
    foo()
}
bar() // global
// 执行foo()的时候创建了一个新的执行上下文,会根据作用于调用message,
// 往上寻找到第一个message,也就是全局
// bar中声明的message,是在bar这个作用域内
// 在bar中调用foo()这里只是执行调用,实际我们需要找的是在解释阶段生成的foo的父级作用域,
// 所以foo的父级作用域是全局,

题目四:

function f1() {
    console.log('听风是风');
};
f1(); //echo
​
function f1() {
    console.log('echo');
};
f1(); //echo
// 同上 函数提升

题目四:

var f1 = function () {
    console.log('听风是风');
};
f1(); //听风是风var f1 = function() {
    console.log('echo');
};
f1(); //echo
// 这两段代码的区别是上一个是函数执行栈,下一个是变量提升 但是赋值没有提升 

注:在看执行上下文的题目时候重点关注解释阶段和执行阶段,变量以及作用域是在解释阶段就生成了,执行阶段只负责执行,所以拿到一个题目之前先从上到下过一遍解释阶段,然后在执行。

8 总结

执行上下文是js中基础的概念,闭包,作用域等都由此展开,所以我也把他作为深入学习JavaScript系列的第一章, 通过文章学习了执行上下文的相关概念,值得反复研读。

在熟悉了执行上下文的概念,类型和创建,以及词法环境,变量环境原理之后,就可以继续下面的学习了。

看到这里的同学都很厉害,就顺手给我点个赞吧。

下一章: # 深入学习JavaScript系列(二)——作用域和作用域链

参考一:# [译] 理解 JavaScript 中的执行上下文和执行栈

参考二:# ES2018 最新 【译】理解Javascript中的执行上下文和执行栈

参考三:# JavaScript深入之执行上下文栈

参考四:# js基础知识零基础理解:执行上下文

# JS进阶 | 深入执行上下文-作用域链