从ECMAScript规范深度分析JavaScript(五):Scope chain 作用域链

本文深入剖析JavaScript中的Scope Chain(作用域链),它涉及函数生命周期的创建和激活阶段,以及如何影响变量查找。作用域链是由函数的[[Scope]]属性构成,保存了所有父级变量对象的集合,用于标识符解析。闭包是与[[Scope]]紧密相关的概念,即使在函数创建的上下文结束之后,[[Scope]]依然存在。此外,通过Function构造函数创建的函数其[[Scope]]只有全局对象。作用域链还包括全局和eval上下文的特殊情况,以及with和catch语句对作用域链的影响。了解这些概念对于理解JavaScript执行机制至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文译自Dmitry Soshnikov的《ECMA-262-3 in detail》系列教程。其中会加入一些个人见解以及配图举例等等,来帮助读者更好的理解JavaScript。

前言

我们都知道,在变量对象的两章《从ECMAScript规范深度分析JavaScript(二):变量对象(上)》《从ECMAScript规范深度分析JavaScript(三):变量对象(下)》中讲解了变量对象,执行期上下文数据(变量,函数声明,函数形参)作为变量对象的属性存储,并且,我们知道变量对象在每次进入上下文时被创建并且填充初始值,在代码执行阶段更新这些值。然而我们如何访问这些值呢?
本篇,我们将更进一步探讨和执行期上下文相关的的细节,就是作用域链(Scope chain)。

定义

如果想简洁的描述并且展示重要的点,作用域链和内部函数关系比较大。
我们知道,ECMAScript允许创建内部函数,并且我们甚至可以从父函数中将它们返回:

    var x = 10;
     
    function foo() {
   
   
       
      var y = 20;
       
      function bar() {
   
   
        alert(x + y);
      }
       
      return bar;
     
    }
     
    foo()(); // 30

因此,了解到每个上下文都有自己的变量对象:全局上下文是全局对象本身,函数是激活对象。

对于内部上下文来讲,作用域链实际上是所有(父级)变量对象的集合,这个链用于变量查找。 即在上述的例子中,“bar”上下文的作用域链包含AO(bar),AO(foo)和VO(global)。我们来详细检验一下这个理论。

我们从定义开始,再通过例子来进一步深入讨论。

作用域链是一个和执行期上下文相关的变量对象链,用来标志符解析时的变量查找。

函数上下文的作用域链在函数调用时创建,并且由激活对象和这个函数的内部[[Scope]]属性组成。我们接下来会详细讨论函数的[[Scope]]属性
上下文的形式:

    activeExecutionContext = {
   
   
        VO: {
   
   ...}, // or AO
        this: thisValue,
        Scope: [ // Scope chain
          // list of all variable objects
          // for identifiers lookup
        ] 
    };

Scope的定义是:

    Scope = AO + [[Scope]]

在我们的示例中可以将Scope和[[Scope]]展示为普通的ECMAScript数组:

    var Scope = [VO1, VO2, ..., VOn]; // scope chain

我们还可以通过在每个链的链接上指向父作用域(这里指父变量对象)将结构视图展示为层级对象链,这对应我们之前在变量对象中讨论的一些实现__parent__的概念

    var VO1 = {
   
   __parent__: null, ... other data}; -->
    var VO2 = {
   
   __parent__: VO1, ... other data}; -->
    // etc.

但是,使用一个数组来表示一个作用域链更方便一些,所以我们将使用这种方法。此外,规范声明抽象为“a scope chain is a list of objects”,而在实现层面上可以使用通过__parent__特性的层级链的方法。数组是抽象表示列表概念的很好的选择。

AO+[[Scope]]的结合以及标识符解析的过程,我们将在下面讨论,这和函数的生命周期相关。

函数的生命周期

函数生命周期可以分为创建阶段和激活阶段(调用阶段),让我们详细讨论它们

1、函数创建阶段

众所周知,函数声明在进入上下文阶段被放到变量/激活对象(VO/AO),让我们来看一下全局上下文中一个变量和函数声明的例子(这里变量对象就是全局对象自身):

    var x = 10;
      
    function foo() {
   
   
      var y = 20;
      alert(x + y);
    }
      
    foo(); // 30

在函数激活时,我们看到正确的(预期的)结果- 30。然而,这有一个非常重要的特性。

在此之前,我们只讨论了当前上下文的变量对象,这里我们看到,y变量在foo中定义(这意味着在foo上下文的AO中),但是变量x没有在foo上下文中定义并且也没有被添加到foo的AO中。乍一看上去变量x根本不存在于foo函数,但我们接下来会看到,这只是也只是乍一看上去不存在。我们看到f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值