以及其任何超类对此上下文都是未知的_「 原创 」理解 JavaScript 中抽象的执行上下文

乍一看,执行上下文这个概念确实抽象,特别是初学 JavaScript 时,若是未深入去了解的情况下,被问及什么是执行上下文,或者说说你对执行上下文的理解的时候,就更显得茫然不知所措了。

先从字面意思入手理解,上下文是由 context 翻译而来,其也有环境、来龙去脉、背景等意思,理解成执行环境或执行过程的来龙去脉,是否会更好理解?也就说,执行上下文是 JavaScript 代码执行的来龙去脉,理解这个过程,将更有利于我们的代码设计,能设计出让观者拍手称其精妙的代码。

再从 JavaScript 中,万物皆对象的层面去理解,将“执行上下文”理解成就是一个个层层嵌套或者相互关联的对象所组成的一个对象,这个对象我们称之为环境,也即所谓的上下文。从这个层面去切入的话,也有利于接下来详细分析的整个过程。

众所周知 JavaScript 是单线程的,但这并不是说 JavaScript 代码是一行一行执行的,JavaScript 是一块一块的执行代码的,而这一块一块的代码,从物理层面上来理解的话,就是这个执行上下文(一个对象)。

在拆解整个过程前,先简单了解下栈数据结构。栈数据结构的特点是:先进后出,后进先出。类似汉诺塔,最先进入的在底部,称之为栈底,反之在最上面的称之为栈顶。

以及其任何超类对此上下文都是未知的_「 原创 」理解 JavaScript 中抽象的执行上下文_第1张图片

执行上下文栈

类型

全局执行上下文

是基础或默认的执行上下文,一个程序只有一个,任何不在函数内部的代码都在全局上下文中。在浏览器中,只有当浏览器关闭或者程序退出的时候,该执行上下文才会被销毁,也就是说在 JavaScript 程序启动后,首先创建了一个全局的执行上下文,且在销毁前一直都处于执行栈的最底层。

// ECS (Execution Context Stack) 执行栈ECS = {    // 全局执行上下文    globalExectionContext}

函数执行上下文

JavaScript 执行流在遇到函数的时候,会为其创建一个函数执行上下文。不论是否重复调用,每遇到一个函数就会创建一个函数执行上下文,然后将其压入执行栈,等待调用执行。

function func1() {    // ...    console.log('函数执行上下文');}// 执行栈ECS = {    // 函数执行上下文    func1ExectionContext,    // 全局执行上下文    globalExectionContext}

Eval 函数执行上下文

eval 函数的作用是将传入的字符串当作 JavaScript 脚本来执行。官方都建议我们不要使用这个危险函数,改用更加安全的 window.Function,这里就不讨论这一块内容了,而其所创建的执行上下文与函数执行上下文是一样的,为何单独形成一个类别,主要是 eval 函数作用域的不同。它是根据调用时所处的环境来确定作用域的。

以及其任何超类对此上下文都是未知的_「 原创 」理解 JavaScript 中抽象的执行上下文_第2张图片

执行过程示意图

创建

创建执行上下文,主要干了三件事:

this 值的绑定

创建词法环境

创建变量环境

this 值的绑定

this 值的指向,要根据执行上下文来确定,这里说的都是在浏览器中的情况:

如果是在全局执行上下文中,this 指向的就是 window 对象

如果是在函数执行上文中,this 的指向主要取决于函数是如何被调用的,也就是说如果调用该函数的是一个引用对象,那 this 指向的就是这个引用对象,如果不是的话,this 就可能指向 window 对象或者在严格模式下的 undefined 了。

let mine = {    name: function() {        console.log('my name is makeit.vip')    }}// 这个 this 指向的是 mine// 因为 name 是被 mine 这个引用对象多调用mine.name()const name = mine.name// 这里 this 指向的就是 window 对象了// 因为它并没有被其它引用对象调用name()

词法 / 变量环境

词法 / 变量环境,这两个带“环境”字眼的词汇,听起来似乎挺高级的。

先说说词法环境(Lexical Environments),按照官方定义中的说法,意思就是用 ECMAScript 规定好的词法(比如 FunctionDeclaration),采用嵌套结构的方式来定义标识符与变量和函数之间的关联

还是从对象层面来理解吧,有一个对象,它叫“词法环境”,规范中,为这个对象定义了两个 key 值,分别为 Environment Record(环境记录),outer Lexical Environment(外部词法环境),在这两个 key 值下又规定了其它更具体的 key 值,也就是属性,以便在执行解析时找到相应的属性,并快速知晓其作用,且能根据此来调用 / 执行某项操作。

需要注意 OuterLexicalEnvironment 外部词法环境引用,与上下文类别有关,全局执行上下文没有外部词法环境引用,即为 null,而函数执行上下文要根据函数本身分情况而论,可能外部词法环境引用为全局环境,也可能是包含了此函数的外部函数环境,但不会为 null。

FEC = {    LE: {        ER: {...},        OLE: {Global / Outer Function Environment}    }}

环境记录(EnvironmentRecord)又细分了声明式环境记录(Declarative Environment Record)和对象环境变量(Object Environment Record)。DER 主要记录的是在作用域内声明定义的各种常量 / let / class / function 等,OER 记录的就是对象。

FEC = {    LE: {        ER: {            DER: {...},            OER: {...}        },        OLE: {G / OFE}    }}

再往下就更具体的词法规范了,像声明式环境记录 DER 又可继续细分为 函数环境记录 / 模块环境记录 等等,这里就先不细说更具体的了。回过头继续看变量环境(Variable Environment),变量环境也是词法环境,唯一不同的是,词法环境存储的是函数声明和变量(let 和 const)绑定,变量环境只是用来存储变量(var)绑定。举个简单的例子:

const hello = 'hello'let world = 'world'var helloWorldfunction sayHello() {    return `${hello} ${world}`}helloWorld = sayHello()// 执行上下文大概长这模样ECS = {    FEC: {        THIS: ,        LE: {            ER: {                DER: {                    type: 'Declarative'                }            }        },        OLE:     },    GEC: {        THIS:         LE: {            ER: {                OER: {                    type: 'object',                    hello: ,                    world: ,                    sayHello:                 }            },            OLE:         },        VE: {            ER: {                OER: {                    type: 'Object',                    helloWorld:                 }            },            OLE:         }    }}

注意 hello / world 两个变量的定义,在 LE 中,默认是为 Uninitialized,而 helloWorld 这个变量,在 VE 中是 Undefined,也就是说前两个变量是未初始化的,不能访问,不然会导致出错,而后一个 helloWorld 变量是初始化了,且默认值为 undefined,这就是为何在用 let / const 声明的变量,在未定义前是不能访问的,而可以在用 var 声明的变量前调用该变量

执行 / 销毁

所有的准备工作都已经完成,接下来就是执行,执行完弹出上下文执行栈,销毁,过程就这么结束啦。

小结

执行上下文可以理解为执行环境,分为全局、函数、Eval 函数执行上下文,全局执行上下文肯定处于执行栈的栈底,每个函数都会对应创建一个函数执行上下文,不论该函数是否重复调用。创建执行上下文中,主要干了三件事,分别为 this 的绑定、创建词法环境、创建变量环境this 值的绑定与执行上下文类别有关,全局执行上下文 this 指向的就是 window 对象,而函数执行上下文的 this 指向,要根据函数被调用的情况来区分,如果函数被引用对象调用,则 this 值会被设置成该引用对象,否则 this 会被设置成 window 对象或在严格模式下被设置成 undefined。词法环境和变量环境可以理解成是对变量/函数声明的规范约束以及存储,两者唯一的区别就是词法环境用于存储 let / const 变量和函数声明,而变量环境仅用于存储 var 变量。另外有个变量声明提升问题,也就是说 var 变量在准备阶段就已完成初始化操作并赋予 undefined 值,而 let / const 变量声明在执行上下文创建阶段还是处于未初始化状态的。

其它

若有不正之处,还望指正 ~

你可能感兴趣的:(此上下文中不允许函数定义。)