Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript执行上下文与执行栈 #5

Open
xchunzhao opened this issue Apr 28, 2019 · 0 comments
Open

JavaScript执行上下文与执行栈 #5

xchunzhao opened this issue Apr 28, 2019 · 0 comments

Comments

@xchunzhao
Copy link
Owner

JS引擎执行过程

  • 语法分析
  • 预编译阶段
  • 执行阶段

语法分析

JS脚本代码块加载完成后,会进入语法分析阶段。该阶段主要作用是:

分析该js脚本代码块的语法是否正确,如果出现不正确,则向外抛出一个语法错误(SyntaxError),停止该js代码块的执行,然后继续查找并加载下一个代码块;如果语法正确,则进入预编译阶段

预编译阶段

js代码块通过语法分析阶段后,语法正确则进入预编译阶段。在分析预编译阶段之前,我们先了解一下js的运行环境,运行环境主要有三种:

  • 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象
  • 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
  • Eval执行上下文:不建议使用,会有安全、性能问题
函数调用栈

JS每进入一个不同环境都会创建一个新的执行上下文(Execution Context),在一段JS程序中会创建多个执行上下文,JS引擎会以栈的形式对执行上下文进行处理,形成函数调用栈(Call Stack)

试想当JavaScript解释需要执行的代码时,最先遇到的是全局代码。所以初始化的时候需要向函数调用栈压入一个全局执行上下文。

那么以下的代码,函数调用栈应该是个什么样子?

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();
//伪代码
ECStack.push(globalContext);
ECStack.push(fun1Context);
ECStack.push(fun2Context);
ECStack.push(fun3Context);
exec fun3();
ECStack.pop(); // enter fun2 ec
ECStack pop(); // enter fun1 ec
执行上下文

执行上下文可以简单的理解为执行环境,与运行环境相对应。

根据winter大大在专栏中所述:


执行上下文在ES3中,包含三个部分:

  • scope: 作用域,也经常被叫做作用域链
  • variable object: 变量对象,用于存储变量的对象
  • this value: this值

在ES5中,改进了命名方式:

  • lexical environment: 词法环境,当获取变量时使用。
  • variable environment: 变量环境,当声明变量时使用。
  • this value: this值

另外在ES2018中,执行上下文加入了更多的东西。

  • lexical environment:词法环境,当获取变量或this值使用
  • variable environment:变量环境,当声明变量时使用
  • code evaluation state: 用于恢复代码执行位置
  • Function: 执行的任务是函数时使用,表示正在执行的函数
  • ScriptOrModule: 执行的任务是脚本或者模块时使用,表示正在执行的代码。
  • Realm: 使用的基础库或内置对象实例
  • Generator: 仅生成器上下文有这个属性,表示当前生成器。

从规范可知,ES3、ES5在执行环境的创建阶段存在差异,尽管ES3的一些规范以及被废弃,但掌握ES3创建执行环境的过程依然有助于理解javascript深层次的概念。


首先来看看ES3的执行上下文

  • 基本概念

VO是创建阶段产生的,用来存放执行环境中可被访问但是不能被删除的函数标识符、形参、变量声明等。而活动对象AO是在执行过程中创建的。

  • 执行细节
    • 创建arguments对象(只在函数执行上下文中有)
    • 扫描上下文的函数声明(不包括函数表达式),为发现的每一个函数,在变量对象上创建一个属性——函数名,一个指向函数的引用。如果函数名存在,引用指针会被覆盖。
    • 扫描上下文的变量声明(不包括let const),为发现的每个变量声明,在变量对象上创建一个属性——变量名,并将变量值初始化为undefined。如果变量名字存在,将不会做任何操作。

举一个简单的例子:

function foo(i) {
    var a = 'hello';
    var b = function privateB() {

    };
    function c() {

    }
}

foo(22);

执行上下文过程的伪代码:

// 创建阶段
fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}
// 激活阶段
fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

接下来主要探讨ES5中的执行上下文

直接看伪代码可能更加直观:

ExecutionContext = {
    ThisBinding = <this value> //确定this
    LexicalEnvironment = { ... } //词法环境
    VariableEnvironment = { ... } //变量环境
}

  • This Binding
    • 全局执行上下文中,this指向全局对象,在浏览器中指向window,而在node环境中指向这个文件的module对象。此外,如果设置了严格模式use strict,指向undefined

    • 函数执行上下文中,this的值取决于函数的调用方式。

  • 词法环境(Lexical Environment)
    • 环境记录: 存储变量和函数声明的实际位置
      • 声明性环境记录:存储变量、函数和参数, 但是主要用于函数 、catch词法环境。 注意:函数环境下会存储arguments的值
      • 对象环境记录: 主要用于with 和全局的词法环境
    • 对外部环境的引用:可以访问其外部词法环境

对外部环境的引用关系到作用域链。后面再做分析。

  • 变量环境(Variable Environment)

变量环境也是一个词法环境,因此具有上面定义的词法环境所有属性。主要的区别在于词法环境用于存储函数声明和变量(let和const)绑定,而变量环境仅用于存储变量(var)绑定。


最后,通过对javascript运行机制的介绍,对一些javasript高级概念有了更深的认识,特别是对一些云里雾里的概念区别有了更深刻的认识。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant