ecmascript执行环境-JavaScript 中的执行上下文和变量对象

执行上下文(ExecutionContext)

文章同步到githubjavaScript中的执行上下文和变量对象

JavaScript代码执行的过程包括编译和执行两个阶段。 编译就是通过词法分析建立具体具体的句子树,并将其编译成机器可识别的指令。 在JavaScript代码编译阶段,范围规则已经确定; 在代码执行阶段,或者一旦函数被调用,就会创建一个执行上下文(ExecutionContext),也称为执行环境

在ECMA-262中,有以下定义

当控制器转到ECMA脚本的可执行代码时,控制器进入执行环境。 当前活动的执行上下文逻辑上生成堆栈结构。 该逻辑堆栈的最顶层执行环境称为当前运行的执行环境。 任何时候,当控制器从当前运行的依赖于执行环境的可执行代码切换到与执行环境无关的可执行代码时,都会创建一个新的执行环境。 新创建的执行环境会入栈,成为当前运行的执行环境。

这也是一个具体的概念。 在一段 JavaScript 代码中,会创建多个执行上下文。 执行上下文定义变量或函数可以访问的其他数据。 通过阅读规范和相关文档,可以了解执行上下文(简称EC)主要包括三点,用伪代码表达如下:

EC = {
    this: // 绑定this指向为当前执行上下文, 如果函数属于全局函数,则this指向window
    scopeChain: [] // 创建当前执行环境的作用域链,
    VO: {} // 当前环境的变量对象(Variable Object),每个环境都有一个与之关联的变量对象
}

看下面这段代码:

var a = 1;
function foo() {
    var b = 2;
    function bar() {
        console.log(b)
    }
    bar()
    console.log(a);
}
foo()

总结:栈底始终是全局上下文,栈顶是当前执行上下文

我们再举一个反例,结合浏览器开发者工具,看看哪些文件是在线执行的。

function foo() {
    bar()
    console.log('foo')
}
function bar() {
    baz()
    console.log('bar')
}
function baz() {
    debugger  // 打断点观察执行上下文栈中的情况
}

可以看到baz当前正在执行,所以栈顶是baz的执行上下文,栈底始终是全局上下文

继续执行。 baz函数执行完成后,会从执行上下文栈顶弹出,而bar函数之前的代码会继续执行。 bar函数执行完毕后,bar的执行上下文就会从栈中弹出; 之后,会执行foo函数旁边的代码,然后执行foo函数。 完成后,执行上下文从堆栈中弹出; 最后,从执行上下文中弹出全局上下文,并从堆栈中清除执行上下文。

变量对象(VariableObject):每个执行环境都有一个与之关联的变量对象,这是一个具体的概念。 环境中定义的所有变量和函数都存储在该对象中。 事实上,我们编写的代码很难访问这个对象,但是解析器在处理数据时在幕后使用它们。

当浏览器第一次加载js脚本程序时,默认进入全局执行环境。 这次的全局环境变量对象是window,可以在代码中访问到。

如果环境是一个函数,则使用这个活动对象作为当前上下文的变量对象(VO=AO)。 此时,无法通过代码访问变量对象。 下面主要对活动对象进行说明。

ActivationObject(激活对象) 1.初始化活动对象(以下简称AO)

函数调用时,立即创建当前上下文的活动对象,并将活动对象作为变量对象,通过arguments属性进行初始化,值为arguments对象(传入的形参集合没有任何内容)做赋值操作ecmascript执行环境,并且数组被用作本地环境变量定义的本地部分)

AO = {
  arguments: 
};

参数对象具有以下属性:

function show (a, b, c) {
    // 通过Object.prototype.toString.call()精准判断类型, 证明arguments不同于数组类型
    var arr = [1, 2, 3];
    console.log(Object.prototype.toString.call(arr)); // [object Array]
    console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
    console.log(arguments.length) // 2  传递进来实参的个数
    console.log(arguments.callee === show) // true 就是被调用的函数show自身
    //参数共享
    console.log(a === arguments[0]) // true
    a = 15;
    console.log(arguments[0]) // 15
    arguments[0] = 25;
    console.log(a)  // 25;
    但是,对于没有传进来的参数c, 和arguments的第三个索引是不共享的
    c = 25;
    console.log(arguments[2]) // undefined
    argument[2] = 35;
    console.log(c) // 25
}
show(10, 20);

然后往下走,这是重点,执行环境的代码分为两个阶段进行处理:

单步进入执行环境执行函数的代码2.单步进入执行环境

如果函数被调用,则进入执行环境(上下文),并立即创建一个活动对象,通过arguments属性对其进行初始化,同时扫描执行环境中的所有数组、所有函数声明、所有变量声明,并将它们添加到活动对象(AO)中ecmascript执行环境,并确定 this 的值,然后开始执行代码。

进入执行环境阶段: