ECMA-262-3 in detail. Chapter 4. Scope chain.

Read this article in: Russian, Chinese (version 1, version 2), Korean.

As we already know from the second chapter concerning the variable object, the data of an execution context (variables, function declarations, and formal parameters of functions) are stored as properties of the variables object.

Also, we know that the variable object is created and filled with initial values every time on entering the context, and that its updating occurs at code execution phase.

This chapter is devoted one more detail directly related with execution contexts; this time, we will mention a topic of a scope chain.

If to describe briefly and showing the main point, a scope chain is mostly related with inner functions.

As we know, ECMAScript allows creation of inner functions and we can even return these functions from parent functions.

var x = 10;

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

}

foo()(); // 30

Thus, is known that every context has its own variables object: for the global context it is global object itself, for functions it is the activation object.

And the scope chain is exactly this list of all (parent) variable objects for the inner contexts. This chain is used for variables lookup. I.e. in the example above, scope chain of “bar” context includes AO(bar), AO(foo) and VO(global).

But, let’s examine this topic in detail.

Let’s begin with the definition and further will discuss deeper on examples.

Scope chain is related with an execution context a chain of variable objects which is used for variables lookup at identifier resolution.

The scope chain of a function context is created at function call and consists of the activation object and the internal [[Scope]] property of this function. We will discuss the [[Scope]] property of a function in detail below.

Schematically in the context:

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

where Scope by definition is:

Scope = AO + [[Scope]]

For our examples we can represent Scope, and [[Scope]] as normal ECMAScript arrays:

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

The alternative structure view can be represented as a hierarchical object chain with the reference to the parent scope (to the parent variable object) on every link of the chain. For this view corresponds __parent__ concept of some implementations which we discussed in the second chapter devoted variable object:

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

But to represent a scope chain using an array is more convenient, so we will use this approach. Besides, the specification statements abstractly itself (see 10.1.4) that “a scope chain is a list of objects”, regardless that on the implementation level can be used the approach with the hierarchical chain involving the __parent__ feature. And the array abstract representation is a good candidate for the list concept.

The combination AO + [[Scope]] and also process of identifier resolution, which we will discuss below, are related with the life cycle of functions.

Function life cycle is divided into a stage of creation and a stage of activation (call). Let’s consider them in detail.

As is known, function declarations are put into variable/activation object (VO/AO) on entering the context stage. Let’s see on the example a variable and a function declaration in the global context (where variable object is the global object itself, we remember, yes?):

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

At function activation, we see correct (and expected) result – 30. However, there is one very important feature.

Before this moment we spoke only about variable object of the current context. Here we see that “y” variable is defined in function “foo” (which means it is in the AO of “foo” context), but variable “x” is not defined in context of “foo” and accordingly is not added into the AO of “foo”. At first glance “x” variable does not exist at all for function “foo”; but as we will see below — only “at first glance”. We see that the activation object of “foo” context contains only one property — property “y”:

fooContext.AO = {
  y: undefined // undefined – on entering the context, 20 – at activation
};
 

How does function “foo” have access to “x” variable? It is logical to assume that function should have access to the variable object of a higher context. In effect, it is exactly so and, physically this mechanism is implemented via the internal [[Scope]] property of a function.

[[Scope]] is a hierarchical chain of all parent variable objects, which are above the current function context; the chain is saved to the function at its creation.

Notice the important point — [[Scope]] is saved at function creation — statically (invariably), once and forever — until function destruction. I.e. function can be never called, but [[Scope]] property is already written and stored in function object.

Another moment which should be considered is that [[Scope]] in contrast with Scope (Scope chain) is the property of a function instead of a context. Considering the above example, [[Scope]] of the “foo” function is the following:

foo.[[Scope]] = [
  globalContext.VO // === Global
];
 

And further, by a function call as we know, there is an entering a function context where the activation object is created and this value and Scope (Scope chain) are determined. Let us consider this moment in detail.

As it has been said in definition, on entering the context and after creation of AO/VO, Scope property of the context (which is a scope chain for variables lookup) is defined as follows:

Scope = AO|VO + [[Scope]]

High light here is that the activation object is the first element of the Scope array, i.e. added to the front of scope chain:

Scope = [AO].concat([[Scope]]);

This feature is very important for the process of identifier resolution.

Identifier resolution is a process of determination to which variable object in scope chain the variable (or the function declaration) belongs.

On return from this algorithm we have always a value of type Reference, which base component is the corresponding variable object (or null if variable is not found), and a property name component is the name of the looked up (resolved) identifier. In detail Reference type is discussed in the Chapter 3. This.

Process of identifier resolution includes lookup of the property corresponding to the name of the variable, i.e. there is a consecutive examination of variable objects in the scope chain, starting from the deepest context and up to the top of the scope chain.

Thus, local variables of a context at lookup have higher priority than variables from parent contexts, and in case of two variables with the same name but from different contexts, the first is found the variable of deeper context.

Let’s a little complicate an example described above and add additional inner level:

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

For which we have the following variable/activation objects, [[Scope]] properties of functions and scope chains of contexts:

Variable object of the global context is:

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>
};
 

At “foo” creation, the [[Scope]] property of “foo” is:

foo.[[Scope]] = [
  globalContext.VO
];
 

At “foo” activation (on entering the context), the activation object of “foo” context is:

fooContext.AO = {
  y: 20,
  bar: <reference to function>
};
 

And the scope chain of “foo” context is:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:

fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];
 

At creation of inner “bar” function its [[Scope]] is:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];
 

At “bar” activation, the activation object of “bar” context is:

barContext.AO = {
  z: 30
};

And the scope chain of “bar” context is:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:

barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];
 

Identifier resolution for “x”, “y” and “z” names:

- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
-- globalContext.VO // found - 10
 
- "y"
-- barContext.AO // not found
-- fooContext.AO // found - 20
 
- "z"
-- barContext.AO // found - 30

Let’s consider some important features related with Scope chain and [[Scope]] property of functions.

Closures in ECMAScript are directly related with the [[Scope]] property of functions. As it has been noted, [[Scope]] is saved at function creation and exists until the function object is destroyed. Actually, a closure is exactly a combination of a function code and its [[Scope]] property. Thus, [[Scope]] contains that lexical environment (the parent variable object) in which function is created. Variables from higher contexts at the further function activation will be searched in this lexical (statically saved at creation) chain of variable objects.

Examples:

var x = 10;

function foo() {
  alert(x);
}

(function () {
  var x = 20;
  foo(); // 10, but not 20
})();

We see that “x” variable is found in the [[Scope]] of “foo” function, i.e. for variables lookup the lexical (closured) chain defined at the moment of function creation, but not the dynamic chain of the call (at which value of “x” variable would be resolved to 20) is used.

Another (classical) example of closure:

function foo() {

  var x = 10;
  var y = 20;

  return function () {
    alert([x, y]);
  };

}

var x = 30;

var bar = foo(); // anonymous function is returned

bar(); // [10, 20]

Again we see that for the identifier resolution the lexical scope chain defined at function creation is used — the variable “x” is resolved to 10, but not to 30. Moreover, this example clearly shows that [[Scope]] of a function (in this case of the anonymous function returned from function “foo”) continues to exist even after the context in which a function is created is already finished.

In more details about the theory of closures and their implementation in ECMAScript read in the Chapter 6. Closures.

In the examples above we see that function at creation gets the [[Scope]] property and via this property it accesses variables of all parent contexts. However, in this rule there is one important exception, and it concerns functions created via the Function constructor.

var x = 10;
 
function foo() {
 
  var y = 20;
 
  function barFD() { // FunctionDeclaration
    alert(x);
    alert(y);
  }
 
  var barFE = function () { // FunctionExpression
    alert(x);
    alert(y);
  };
 
  var barFn = Function('alert(x); alert(y);');
 
  barFD(); // 10, 20
  barFE(); // 10, 20
  barFn(); // 10, "y" is not defined
 
}
 
foo();
 

As we see, for “barFn” function which is created via the Function constructor the variable “y” is not accessible. But it does not mean that function “barFn” has no internal [[Scope]] property (else it would not have access to the variable “x”). And the matter is that [[Scope]] property of functions created via the Function constructor contains always only the global object. Consider it since, for example, to create closure of upper contexts, except global, via such function is not possible.

Also, an important point at lookup in scope chain is that prototypes (if they are) of variable objects can be also considered — because of prototypical nature of ECMAScript: if property is not found directly in the object, its lookup proceeds in the prototype chain. I.e. some kind of 2D-lookup of the chain: (1) on scope chain links, (2) and on every of scope chain link — deep into on prototype chain links. We can observe this effect if define property in Object.prototype:

function foo() {
  alert(x);
}
 
Object.prototype.x = 10;
 
foo(); // 10
 

Activation objects do not have prototypes what we can see in the following example:

function foo() {
 
  var x = 20;
 
  function bar() {
    alert(x);
  }
 
  bar();
}
 
Object.prototype.x = 10;
 
foo(); // 20
 

If activation object of “bar” function context would have a prototype, then property “x” should be resolved in Object.prototype because it is not resolved directly in AO. But in the first example above, traversing the scope chain in identifier resolution, we reach the global object which (in some implementation but not in all) is inherited from Object.prototype and, accordingly, “x” is resolved to 10.

The similar situation can be observed in some versions of SpiderMokey with named function expressions (abbreviated form is NFE), where special object which stores the optional name of function-expression is inherited from Object.prototype, and also in some versions of Blackberry implementation where activation objects are inherited from Object.prototype. But more detailed this features are discussed in Chapter 5. Functions.

Here is not so much interesting, but it is necessary to note. The scope chain of the global context contains only global object. The context with code type “eval” has the same scope chain as a calling context.

globalContext.Scope = [
  Global
];
 
evalContext.Scope === callingContext.Scope;
 

In ECMAScript there are two statements which can modify scope chain at runtime code execution phase. These are with statement and catch clause. Both of them add to the front of scope chain the object required for lookup identifiers appearing within these statements. I.e., if one of these case takes place, scope chain is schematically modified as follows:

Scope = withObject|catchObject + AO|VO + [[Scope]]

The statement with in this case adds the object which is its parameter (and thus properties of this object become accessible without prefix):

var foo = {x: 10, y: 20};
 
with (foo) {
  alert(x); // 10
  alert(y); // 20
}
 

Scope chain modification:

Scope = foo + AO|VO + [[Scope]]

Let us show once again that the identifier is resolved in the object added by the with statement to the front of scope chain:

var x = 10, y = 10;
 
with ({x: 20}) {
 
  var x = 30, y = 30;
 
  alert(x); // 30
  alert(y); // 30
}
 
alert(x); // 10
alert(y); // 30
 

What happened here? On entering the context phase, “x” and “y” identifiers have been added into the variable object. Further, already at runtime code executions stage, following modifications have been made:

  • x = 10, y = 10;
  • the object {x: 20} is added to the front of scope chain;
  • the met var statement inside with, of course, created nothing, because all variables have been parsed and added on entering the context stage;
  • there is only modification of “x” value, and exactly that “x” which is resolved now in the object added to the front of scope chain at second step; value of this “x” was 20, and became 30;
  • also there is modification of “y” which is resolved in variable object above; accordingly, was 10, became 30;
  • further, after with statement is finished, its special objects is removed from the scope chain (and the changed value “x” – 30 is removed also with that object), i.e. scope chain structure is restored to the previous state which was before with statement augmentation;
  • as we see in last two alerts: the value of “x” in current variable object remains the same and the value of “y” is equal now to 30 and has been changed at with statement work.

Also, a catch clause in order to have access to the parameter-exception creates an intermediate scope object with the only property — exception parameter name, and places this object in front of the scope chain. Schematically it looks so:

try {
  ...
} catch (ex) {
  alert(ex);
}
 

Scope chain modification:

var catchObject = {
  ex: <exception object>
};
 
Scope = catchObject + AO|VO + [[Scope]]

After the work of catch clause is finished, scope chain is also restored to the previous state.

At this stage, we have considerate almost all general concepts concerning execution contexts and related with them details. Further, according to plan, — detailed analysis of function objects: types of functions (FunctionDeclaration, FunctionExpression) and closures. By the way, closures are directly related with the [[Scope]] property discussed in this article, but about it is in appropriate chapter. I will be glad to answer your questions in comments.


Translated by: Dmitry A. Soshnikov.
Published on: 2010-03-21

Originally written by: Dmitry A. Soshnikov [ru, read »]
Originally published on: 2009-07-01

Tags: , , ,

 
 
 

25 Comments:

  1. Gravatar of denisdeng denisdeng
    21. April 2010 at 21:06

    This article is voer:
    ECMA-262-3 详细分析.第四章. 作用域链


  2. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    21. April 2010 at 22:57

    @denisdeng

    Okey, well done, Denis; added the link to this translation.


  3. Gravatar of justin justin
    4. May 2010 at 19:18

    [”x” is resolved to 20, but not to 30. ]
    I think your meas is [“ x” is resolved to 10, but not to 30. ]


  4. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    4. May 2010 at 19:29

    @justin

    I think your meas is [“ x” is resolved to 10, but not to 30. ]

    Yep, it was a typo; fixed.

    Dmitry.


  5. Gravatar of justin justin
    7. May 2010 at 11:05

    For examples we represent Scope, and [[Scope]] as normal ECMAScript arrays.

    you means is :
    we can represent Scope, and [[Scope]] as normal ECMAScript arrays.

    right?

    I don’t know how to understand the “for examples”.
    Always felt like something wildly shortcomings.


  6. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    7. May 2010 at 13:42

    @justin

    you means is :
    we can represent Scope, and [[Scope]] as normal ECMAScript arrays.

    Yes. I have rewritten this description and added alternative representation of the scope chain (with involving the __parent__ concept). I also moved this paragraph a bit higher (earlier) in the article.

    Dmitry.


  7. Gravatar of justin justin
    8. May 2010 at 12:49

    thanks!

    another question:

    starting from the deepest context and up to the top bypassing the scope chain.

    I don’t know how to understand the “bypassing the scope chain.”.

    I think you means is :”starting from the deepest context and up to the top the scope chain.“,no “bypass”.

    right?


  8. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    8. May 2010 at 13:05

    @justin

    I don’t know how to understand the “bypassing the scope chain.”.

    Yes, a confusing word; I simply removed it.

    Dmitry.


  9. Gravatar of justin justin
    10. May 2010 at 16:30

    exists until the function exists

    may be you means is “exists until the function exits

    [[Scope]] contains as one of its objects that lexical environment (parent variable object) in which function is created.

    It was too bad to understand, could you explain to me?


  10. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    10. May 2010 at 16:58

    @justin

    may be you means is “exists until the function exits

    Yep, it was a typo. Now it is: “exists until the function object is destroyed”. Which means, that a function’s [[Scope]] (list of parent VOs) will be removed when the function itself will be removed.

    [[Scope]] contains as one of its objects that lexical environment (parent variable object) in which function is created.

    It was too bad to understand, could you explain to me?

    I removed the part with “as one of its objects”. Now it should be clearer. If you carefully read about [[Scope]], you should know that this internal function’s property stores a VO of the context in which a function is created.

    Having this code:

    var foo;
    function bar() {}

    the [[Scope]] of the “bar” function is:

    bar.[[Scope]] = { // [[Scope]] is a global object
      foo: undefined,
      bar: <reference to function>,
      __parent__: null
    };

    E.g. for inner function “baz” the [[Scope]] is the following:

    var x = 10;
    function foo() {
      var bar;
      function baz() {}
    }
    

    And we have:

    baz.[[Scope]] = {
      bar: undefined
      __parent__: foo.[[Scope]] // global
    };

    Alternative and more convenient representation of the [[Scope]] isn’t as separate objects with __parent__, but as an array. And we use this approach during this chapter. So the [[Scope]] — is a list which contains all parent variable objects (see 10.1.4).

    For our “baz” function its internal [[Scope]] array is:

    baz.[[Scope]] = [
    
      // foo's VO i.e. AO of foo's context
      {
        bar: undefined,
        baz: <reference to function>
      },
    
      // global VO
      {
        x: 10
      }
    
    ];

    So you see that “baz.[[Scope]]” “contains that (parent) variable object in which function “baz” is created, i.e. the AO of foo’s context.

    And then, when the “baz” will be activated, the scope chain of the baz’s context (a real scope chain which is used for identifier resolution lookup) is:

    Scope(baz) = AO(baz) + baz.[[Scope]];

    I.e. Scope(baz) is being created every time at baz’s activation and is related with the execution context. And the baz.[[Scope]] is created only once — at baz’s creation and related with the baz function.

    Dmitry.


  11. Gravatar of justin justin
    10. May 2010 at 17:14

    @Dmitry
    Thank you very much !
    more clearly now, I had also get confused by “as one of its objects”.

    other:

    bypassing the scope chain in identifier resolution

    maybe your mean is “traversing the scope chain …”


  12. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    10. May 2010 at 17:53

    @justin

    maybe your mean is “traversing the scope chain …”

    Yep, it makes sense; I changed.

    Dmitry.


  13. Gravatar of ju350213 ju350213
    18. November 2011 at 21:20

    the sentence “a catch clause that the parameter-exception becomes accessible to it creates new object with the only one property — exception parameter name ” is difficult for me to understand. is [that the parameter-exception becomes accessible to it] a attributive clause
    to qualify [a catch clause]???? forgive my poor english.
    I have another question:do the prototype property store in vo??


  14. Gravatar of ju350213 ju350213
    18. November 2011 at 21:31

    I mean the implicit __proto__ property you mentioned in the article javascript: the core


  15. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    19. November 2011 at 15:46

    @ju350213

    Thanks, I’ve rewritten a bit this sentence; hope now it sounds easier.

    I have another question:do the prototype property store in vo??
    I mean the implicit __proto__ property you mentioned in the article javascript: the core

    It’s better to put questions related to other articles in their comments. But, on the question — no, implicit [[Prototype]] property (which is __proto__ on figures) is the property of objects.

    However, in some implementations, e.g. SpiderMonkey (Firefox), the global object (which is VO of the global context) inherits from Object.prototype. And in this case your sentence makes sense, since the global object is the VO there and has __proto__.

    P.S.: I also fixed typos in lexical environment article, thanks.

    Dmitry.


  16. Gravatar of ju350213 ju350213
    20. November 2011 at 05:44

    Thanks.I know what you said,but what i want to know is where the implicit [[Prototype]] property of objects is stored.in my opinion,all program execution information are stored in execution context stack,so i can’t associate the two things(scope chain and prototype chain).is memory block which is used to store execution context stack information separated from memory block used to store object’s propertys?? In short, i want to know the relationship between the two things(scope chain and prototype chain) in detail.Can you explain it deeply? thanks!


  17. Gravatar of bird bird
    26. October 2012 at 03:58

    @dmitry
    hi, about this:
    At “foo” activation (on entering the context), the activation object of “foo” context is:

    fooContext.AO = {
      y: 20,
      bar: 
    };
    

    Why at foo activation is on entering the context, not the code execution ?


  18. Gravatar of kerry chen kerry chen
    20. March 2013 at 02:55

    I think you have an expression error.

    some kind of 2D-lookup of the chain: (1) on scope chain links, (2) and on every of scope chain link — deep into on prototype chain links

    As you say:

    Activation objects do not have prototypes

    Therefore lookup would not deep into prototype chain on every activation object, but only deep into the prototype chain of VO(globalContext) which itself is global object inherited from Object.prototype


  19. Gravatar of raziq raziq
    3. October 2013 at 22:04

    ok this is some code

    function myFunc(){
       var myvar = 8;
           function myFunc2(num){
               alert(myvar+num);
           }
    
       myFunc2(2);
    
    }
    
    myFunc();

    i want to clear my mind so pls correct me if am wrong

    i have read allot of articles in stack overflow already but i want to know i understand it well or should i read more.

    to my understanding what happens behind the scene is thatin global execution context there it creates function object with the namemyFunc` and its [[scope]] property assigned to global variable object.

    and when i call myFunc it creates its own execution context and activation object where all of the function’s arguments and function declaration is initialized before any line by line code execution.

    when inner function object is created it’s internal [[scope]] property is assigned the value of its outer execution context’s variable object + global variable object so every function creates its own execution context but before that every function’s internal [[scope]] property is assigned first.

    i have read allot of articles in stack overflow already but i want to know i understand it well or should i read more.


  20. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    4. October 2013 at 21:03

    @raziq, yes, this is correct understanding. The [[Scope]] is set at function creation. And when the function is executed, a new execution context (with the activation object that stores local data) is spawned.


  21. Gravatar of Oleg Oleg
    3. December 2013 at 09:30

    Спасибо за статьи, они классные! А теперь комментарий. По сути AO и VO одинаковы, но вы их различаете в статьях так же как и стандарт наверное, и правильно обосновываете это различие, а именно тем, что в случае с вызовом функции, при формировании VO к нему добавляются ” object and are added”, но это не единственное различие, и поэтому вы акцентируете внимание на то, что доступ к глобальному VO может происходить и через window, а к AO доступа нет, т.е. мы не можем создавать переменные в AO без использования var, в случае с VO глобального объекта мы можем window[‘name’] = …, но потом вы указали, что в некоторых реализаций через внутреннее свойство __parent, мы можем получить доступ к AO. Для того чтобы мне запомнить правила мне необходимо понять почему они такие, и вот мне не хватило двух этих различий между AO и VO чтобы их различать, они не были для меня существенные, но потом я представил такую картину, если бы мы имели официальный доступ к AO, тогда я бы мог создавать переменные в функции динамически, на лету, и это можно сделать сейчас через eval(“var …”) , но тогда как бы при этом работал механизм static scope, можно ли его назвать таким в ECMA-262-3, который имеет место быть только в процессе семантического анализа текста программы, то есть на стадиях предкомпиляции. Поэтому наверное eval(“var …”) запрещают в следующих стандартах в strict mode, чтобы была возможность оптимизации closure, например в процессе семантического анализа функции видно, что в функции фигурируют некоторые свободные переменные которые замыкаются на другие, которые в свою очередь известно где объявляются, тогда возможно заранее подготовить структуры данных для быстрого доступа к ним, а не содержать и просматривать всю цепочку вверх для их обнаружения уже в процессе вызова функции.
    Вот пример простой функции

    function f(t) {
       t && eval(t); 
       return a;
    }

    Здесь идентификатор “а” в зависимости от t и контекста исполнения может возвращать разные значения как в dynamic scope.

    var a = 0;
    f(); // =>; 0
    f('var a = 1');// =>; 1
    
    function Z() 
    {
     var a = 2;
     f(); //=>; 2
    }

    И вы тоже потом об этом пишите.
    Вот еще цитата из следующих ваших статей
    “The word “static” relates to ability to determine the scope of an identifier during the parsing stage of a program.”
    В данном случае такой возможности нет у парсера. Почему у меня возникли такие вопросы, мне просто интересно насколько вызов функции производителен, если при каждом вызове просматривать цепочку вверх, предполагая, что там где-то будет находиться переменная, то какой же это вызов будет мощный, плюс с каждой функцией содержать копию цепочки, в верху которой, например, сидит необходимое значение.
    Вот еще пример:

    var x = 10;
    function f(t) {
       t && eval(t); 
       if (--x !== 0)
          f();
       else
          f.F = function () {return a};
    }
    f('var a = 0');
    f.F() // =>; ReferenceError: a is not defined
    
    var x = 10;
    function f(t) {
       var a = 1;
       if (--x !== 0)
          f();
       else
          f.F = function () {return a};
    }
    
    f.F() // =>; Ok, 1
    
    function f(t) {
       t && eval(t); 
       return function() { return a;}
    }
    f('var a = 1')() //=>; Ок, 1

    В первом случае не увидел ‘a’, наверное подумал слишком глубоко, во втором увидел ‘var a’ явно стоит, и подумал ок тут ты сам этого хочешь, получи, а в последнем случае сhrome подумал, но тут не очень глубоко, можно поплавать. Почему во втором так, мне ясно, а вот первый и последний оставляет вопросы, ведь в обоих случаях, когда создается [[Scope]] для функций там по цепочке вверх можно добраться до ‘a’, а в первом случае этого не происходит?


  22. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    4. December 2013 at 22:53

    @Oleg,

    VO и AO, по сути, мало чем отличаются (только идеологически), фактически, оба они — лишь хранилища переменных контекста. Только AO – в случае контекстов функций, и предварительно наполняется параметрами функции и объектом arguments. Во всем остальном — это тот же VO.

    Вы правильно заметили, что нельзя создать свойство AO напрямую, но eval, да, это сделать может.

    В строгом режиме eval запускается в отдельной песочнице (создается временное хранилище переменных).

    function f(t) {
       t && eval(t); 
       return a;
    }

    Здесь идентификатор “а” в зависимости от t и контекста исполнения может возвращать разные значения как в dynamic scope.

    var a = 0;
    f(); // =>; 0
    f('var a = 1');// =>; 1
    
    function Z() 
    {
     var a = 2;
     f(); //=>; 2
    }

    Последний пример с функций Z неверный (поэтому, все-таки, это не классический динамический scope). Функция f создана в глобальном контексте, и поэтому может видеть лишь свой AO, либо глобальный контекст (информация о том, какие scope может видеть функция записана при ее создании и определена лексически), но никак не контекст Z, из которого она запускается (как было бы при классическом динамическом scope). Значением последнего запуска будет 0 — из глобального контекста, т.к. код t не передан и a не создается в локальном AO функции f.

    В первом случае не увидел ‘a’, наверное подумал слишком глубоко

    Переменная a не создается в AO f при рекурсивных вызовах (т.к. Вы не передаете код в этом случае — f();. Соответственно, значение f.F = создержит функцию, которая создана при последней рекурсивной итерации, где переменной a нет. Передайте код при рекурсивных вызовах — f(t), и все заработает: глубина тут не влияет.

    Во втором случае var a = 1; всегда явно определяется в AO f и не зависит от кода t.

    В третьем случае — переданный код t нормально исполняется, и поэтому создается a, которая становится так же видна.


  23. Gravatar of Hong Hong
    15. July 2014 at 17:09

    Hi Dmitry,

    In your demo below, the final identifier resolution for ‘x’, ‘y’, ‘z’, is based on Scope property of functionConext right?

    That is AO + [[Scope]]. While for example in Closure section, it turns [[Scope]] of function to resolve variable?

    ————————————————————
    Let’s a little complicate an example described above and add additional inner level:

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

    Identifier resolution for “x”, “y” and “z” names:

    	- "x"
    	-- barContext.AO // not found
    	-- fooContext.AO // not found
    	-- globalContext.VO // found - 10
    	- "y"
    	-- barContext.AO // not found
    	-- fooContext.AO // found - 20
    	- "z"
    	-- barContext.AO // found - 30

    ———————————————————–

    In Closure section, in following example, you said “x” variable is found in the [[Scope]] of “foo” function. I am so confused. Please help me.

    ———————————————————–
    Examples:

    	var x = 10;
    	 
    	function foo() {
    	  alert(x);
    	}
    	 
    	(function () {
    	  var x = 20;
    	  foo(); // 10, but not 20
    	})();

    We see that “x” variable is found in the [[Scope]] of “foo” function, i.e. for variables lookup the lexical (closured) chain defined at the moment of function creation, but not the dynamic chain of the call (at which value of “x” variable would be resolved to 20) is used.
    ———————————————————–

    Please help, waiting for your reply.

    Regards.


  24. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    16. July 2014 at 22:51

    @Hong it’s always AO + [[Scope]] lookup. The [[Scope]] of foo is [{x: 10}], the AO of foo is empty — {} (except implicit arguments object, etc). So, x is found in the [[Scope]].


  25. Gravatar of Hong Hong
    20. July 2014 at 20:58

    @Dmitry, after reading this article the second time, I got it. Thanks for your help.


Leave a Reply

Code: For code you can use tags [js], [text], [ruby] and other.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>