执行环境,变量对象及作用域链

1 概念

首先来看一下 javascript 高级程序设计中的概念

执行环境:执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,执行环境中定义的所有变量和函数都保存在这个对象中。
作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问

2 执行环境

执行环境,顾名思义,是执行的时候才会被创建的环境。如果代码没有执行,则不会创建执行环境。执行环境又分为全局执行环境和函数执行环境。

首先,有一个全局执行环境,根据 ECMAScript 实现所在的宿主环境不同,表示执行环境的对象也不同。在 web 浏览器中即 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。全局执行环境直到应用程序退出,例如关闭网页或浏览器才会被销毁。

每个函数也有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入环境栈(也叫调用栈)中。当函数执行后,栈将其环境弹出,把控制权交给之前的执行环境。

javascript 函数的执行有一个调用栈,当调用一个函数的时候这个函数就会被加入到调用栈中。拿之前的 浅析 javascript 中 this 的指向 ( 箭头函数 ) 关于调用位置的那段代码来举例

function baz(){
    // 当前调用栈是:global->baz
    var a = 1;
    console.log( "baz" );
    bar();
}

function bar(){
    // 当前调用栈是:global->baz -> bar
    console.log( "bar" );
    foo();
}

function foo(){
    // 当前调用栈是 global->baz -> bar -> foo
    console.log( "foo" )
}

baz(); 

上述代码首先会创建一个全局的执行环境,变量对象即该环境中包含的变量和函数,在这里是 bazbarfoo

然后 baz() 执行,这个时候,baz() 的被压入环境栈,成为当前的执行环境,此时 baz() 中的变量对象变为函数执行时的活动对象为 arguments(每个函数执行时都会创建)和 a。

执行 bar()。这个时候,bar() 被压入环境栈,成为当前执行环境。以此类推

3 变量对象

我们知道了变量对象是执行环境中包含定义的所有变量和函数,那么它的创建过程是怎么样的呢,来看一个例子

console.log(foo); // undefined
console.log(bar()); // 2

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

那么为什么是这样呢?

首先变量对象创建的时候若为函数,则会先创建 arguments 对象。然后将函数声明(这里与函数表达式区别)添加到变量对象中,最后将变量添加到变量对象当中。但是此时变量的值全都是 undefined。只有执行到赋值语句以后,变量才有值。

函数表达式即 var 声明的函数,这样的函数被当做变量,在赋值之前也不能进行操作,不存在函数声明提升的过程。

什么是活动对象呢?

未进入执行环境之前,变量对象中的属性都不能访问!但是进入执行环境之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。所以活动对象实际就是变量对象在真正执行时的另一种形式。

4 作用域链

那么什么是作用域链呢

对于每一个执行环境,都会创建一个与之关联的作用域链。每个执行环境的作用域链的前端,始终都是该执行环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局执行环境是不存在的)。作用域链中的下一个变量来自于包含(外部)环境,即环境栈中的上一个执行环境。而再下一个变量对象则来自于下一个包含环境。这样,一直延续到全局执行环境。全局执行环境的变量对象始终都是作用域链的最后一个对象。

怎么理解呢

还是以上面那个例子,首先创建了全局的执行环境,这个执行环境包含一个指向作用域链的指针,用代码表示如下

globalExecutionContext = { // 全局执行环境
    scopeChain: ScopeChain // 作用域链
}
ScopeChain = { // 作用域链
    0: globalVaribalObject // 全局变量对象
}
globalVaribalObject = {
    baz: function(){...},
    bar: function(){...},
    foo: function(){...}
}

全局执行环境包含作用域链,作用域链里面包含了全局执行环境下的变量对象,这个变量对象里面包含了那个函数声明。

接下来,当执行环境进入到 baz() 中,

bazExecutionContext = { // baz 执行环境
    scopeChain: ScopeChain // 作用域链
}
ScopeChain = { // 作用域链
    1:bazActivationObject, // bar 活动对象
    0: globalVaribalObject // 全局变量对象
}
bazActivationObject = {
    arguments:[],
    a: 1
}
globalVaribalObject = { // 全局变量对象
    baz: bazExecutionContext, // 这里变为执行环境
    bar: function(){...},
    foo: function(){...}
}

首先,当前执行环境变为 baz(),然后创建该执行环境的作用域链,该作用域链的前端是当前函数的活动对象即 baz 的活动对象,函数进入执行环境时首先创建 arguments 对象,然后将函数声明及变量添加到活动对象中。

作用域链的下一个变量对象来自包含环境,此处即全局的变量对象。

全局变量对象中包含 baz(),并且 baz 此时是执行环境,因此全局变量对象的 baz 指向 baz 的执行环境。以此类推。

5 总结

可以把执行环境,变量对象及作用域链都看作对象

首先执行环境创建时会创建作用域链,作用域链中又包含当前的变量对象(活动对象)和包含环境的变量对象。

变量对象中包含的是环境中定义的变量和函数。

猜你喜欢

转载自blog.csdn.net/zhang6223284/article/details/81736416