What is the relationship between global object enumerators, execution contexts, activation objects, variable objects and this?

What is the relationship between global object enumerators, execution contexts, activation objects, variable objects and this?

  • Comments 9

UPDATE: I am WRONG WRONG WRONG.

Brendan Eich, the original creator of JavaScript, was kind enough to point out at great length that I was WRONG WRONG WRONG in my conclusions earlier today.  Somehow I managed to miss the key section in the spec -- my search-fu is not all its cracked up to be apparently. How embarrassing!

Allow me to correct the offending conclusion.

************

Here's an interesting question that I received earlier this year from a reader. The reader notes that in the Gecko implementation of ECMAScript you can enumerate the properties of the global object and get back a list of global functions but in JScript you cannot. 

************

As I understand JavaScript, global functions should be properties of global; that is, in the global scope chain. But in JScript I have to explicitly define a function as a member of global "this" to be able to iterate it.

var __global__ = this;
function invisibleToIE() {
  alert("IE can't see me");
}
__global__.visibleToIE = function() {
  alert("IE sees me");
}

for (func in __global__) {
  var f = __global__[func];
  if (func.match(/visible/)) {
    f();
  }
}

Shouldn't  function
foo() { }  and  this.foo = function() { } be completely identical? If so, should I then not be able to get "foo" as a property of global this? Do you know of a workaround for this? Is this a bug in JScript? or in Gecko JS? Or is this question up in the air?

************

In fact, JScript/IE gets it wrong and Gecko gets it right.  Explaining why will require some background though.

Let's start by considering a related scenario in which we define a closure. 

function MyObject(){}
function MakeAdder(first) {
  function Adder(second) {return first + second; } 
  return Adder;
}
MyObject.prototype.MakeAdder = MakeAdder;
var x = new MyObject();
var AddTwo = x.MakeAdder(2);
print(AddTwo(3));

When MakeAdder is called, "this" refers to global variable x.  Does declaring Adder automatically add an "Adder" property to x? 
Of course not!  That function is a variable bound to the local scope, not a member bound to the "this" object.

Then what should this do?

function MakeAdder(first) {
  function Adder(second) {return first + second; } 
  for(var yyy in this)
     print(yyy);
  return Adder;
}

Should that print "Adder" as one of the properties of this? Of course not.  As we've just established, Adder is a local variable, not a property of this.

Now just remove the outer function, and we're in exactly the same situation, just in the global scope rather than a function scope.  Declaring functions in global scope has nothing whatsoever to do with changing the "this" object of the global scope.  It changes the variable list of the global scope, sure, but not the "this" object.

Or does it?

Let's take a walk through the relevant sections of the specification.

Section 13 describes the semantics of a function declaration.

The production FunctionDeclaration : function Identifier ( FormalParameterList ) { FunctionBody } is processed for function declarations as follows:
1.      Create a new Function object as specified in section 13.2 with parameters specified by FormalParameterList, and body specified by FunctionBody. Pass in the scope chain of the running execution context as the Scope.
2.      Create a property of the current variable object […] with name Identifier and value Result(1).

What is the "running execution context"?  Basically either the global context or a function.  Section 10 says:

When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context.

What is the "current variable object"?  Section 10.1.3 says:

Every execution context has associated with it a variable object. Variables and functions declared in the source text are added as properties of the variable object. […] For each FunctionDeclaration in the code, in source text order, create a property of the variable object whose name is the Identifier in the FunctionDeclaration, whose value is the result returned by creating a Function object as described in section 13, and whose attributes are determined by the type of code. If the variable object already has a property with this name, replace its value and attributes.

But what exactly is the relationship between an execution context and a variable object?  Section 10.1.6 clears that up:

When control enters an execution context for function code, an object called the activation object is created and associated with the execution context. […] The activation object is then used as the variable object for the purposes of variable instantiation. The activation object is purely a specification mechanism. It is impossible for an ECMAScript program to access the activation object. It can access members of the activation object, but not the activation object itself.

Now, in our example of a global function, there is a single global activation object, which is the global variable object.

Let me just re-emphasize that the variable object is distinct from the "this" object, as noted in section 10.1.7:

There is a this value associated with every active execution context. The this value depends on the caller and the type of code being executed and is determined when control enters the execution context. The this value associated with an execution context is immutable.

Got it?  An execution context contains a "this" object obtained from the caller.  An execution context also contains an activation object which is created fresh every time the context is entered.  The context uses the activation object as its variable object.  New declarations are set as properties on the variable object, not the "this" object -- the "this" object and the activation object are possibly completely different objects.  This explains how it is that new local functions and variables are not bound to the "this" that comes from their caller.

The kicker (which I missed in the earlier version of this article) is section 10.2.1:

[Global] Variable instantiation is performed using the global object as the variable object and using property attributes { DontDelete }.
The [global] this value is the global object.

(My emphasis.)

Aha! For the global context, the global variable object and the global this are the same object. Therefore, it is NOT like the function scope example.  Adding a new function to the global variable object should add a new object to the global this.

So to answer the specific questions:

Shouldn't function foo() { } and  this.foo = function() { }   be completely identical?

In a function scope there is no requirement that they be the same.  One binds a variable in an execution context, the other sets a property on an object.  Those are completely different.  But in the global scope, those two things are the same. Therefore, JScript/IE is broken.

Do you know of a workaround for this?

I do not know of any way to enumerate global functions from within the language.

Behind the scenes the global variable object is actually implemented as a JScript object, and the global this object is implemented by the host.  Therefore JScript they are different objects, and there is no way to peek behind the scenes and get ahold of the variable object itself within the language.  However the language host is capable of enumerating the global variable object if it wants to.  If you are implementing your own script host, call GetScriptDispatch and that will give you back our internal pointer to the global variable object.  (This is how Internet Explorer implements being able to call JScript functions from a VBScript engine on the same page, but that's another story.)

Is this a bug in JScript? or in Gecko JS? Or is this question up in the air?

It is a bug in JScript, or IE.  I'm not sure which yet. 

I will follow up with the Sustaining Engineering team and see if we can get this fixed in a future version of IE.  However, I wouldn't hold my breath waiting if I were you. 

Thanks for the interesting question and valuable feedback.

 

  • Sorry, but the analysis in this blog entry is just wrong. First, the "current variable object" that is referenced here:

    2. Create a property of the current variable object […] with name Identifier and value Result(1).

    (ECMA-262 Edition 3, section 13, second step for first production) is the variable object for the execution content in which the FunctionDeclaration is being executed. It's not the variable object associated with activations of that function.

    Second, |this| binding is completely specified by ECMA-262 for all function syntax. Follow these parts of the spec (my comments in [brackets]):

    <<<<<spec citation starts here>>>>>

    10.2.1 Global Code

    * The scope chain is created and initialised to contain the global object and no others.
    * Variable instantiation is performed using the global object as the variable object and using property attributes { DontDelete }.
    * The this value is the global object.

    [Right here, the claim in this blog entry that "There is no requirement that [function foo(){} and this.foo = function(){}] be the same" can be seen to be false for function declarations and statements in global code.

    From the second bulleted item, it's also clear that JScript has a bug if it instantiates declared function identifiers as variables with the DontEnum attribute. Declared functions result in enumerable properties of their declarations' variable object in ECMA-262.]

    10.2.3 Function Code
    . . .
    * The caller provides the this value. If the this value provided by the caller is not an object (including the case where it is null), then the this value is the global object.

    [the caller provides the this value, and any non-object this value is replaced with a reference to the global object.]

    11.2.3 Function Calls

    The production CallExpression : MemberExpression Arguments is evaluated as follows:

    1. Evaluate MemberExpression.
    2. Evaluate Arguments, producing an internal list of argument values (section 11.2.4).
    3. Call GetValue(Result(1)).
    4. If Type(Result(3)) is not Object, throw a TypeError exception.
    5. If Result(3) does not implement the internal [[Call]] method, throw a TypeError exception.
    6. If Type(Result(1)) is Reference, Result(6) is GetBase(Result(1)). Otherwise, Result(6) is null.
    7. If Result(6) is an activation object, Result(7) is null. Otherwise, Result(7) is the same as Result(6). 8. Call the [[Call]] method on Result(3), providing Result(7) as the this value and providing the list Result(2) as the argument values.
    9. Return Result(8).

    [Step 8 binds this to the base object of the reference by which the function is denoted in the call expression, censoring activation objects in Step 7. Gecko's JS implementation follows this step.

    Just to finish off any doubt about this binding of a function called by its unqualified name -- foo() instead of window.foo() or this.foo() at top-leve:]

    10.1.4 Scope Chain and Identifier Resolution

    Every execution context has associated with it a scope chain. A scope chain is a list of objects that are searched when evaluating an Identifier. When control enters an execution context, a scope chain is created and populated with an initial set of objects, depending on the type of code. During execution within an execution context, the scope chain of the execution context is affected only by with statements (section 12.10) and catch clauses (section 12.14).

    During execution, the syntactic production PrimaryExpression : Identifier is evaluated using the following algorithm:

    1. Get the next object in the scope chain. If there isn't one, go to step 5.
    2. Call the [[HasProperty]] method of Result(1), passing the Identifier as the property.
    3. If Result(2) is true, return a value of type Reference whose base object is Result(1) and whose property name is the Identifier.
    4. Go to step 1.
    5. Return a value of type Reference whose base object is null and whose property name is the Identifier.

    The result of evaluating an identifier is always a value of type Reference with its member name component equal to the identifier string.

    [In short, global code of the form foo() calling a declared function foo whose declaration was processed correctly according to the spec will bind this to the global object, which was the variable object at the time the function declaration was processed.]

    <<<<<spec citation ends here>>>>>

    I believe my ECMA TG1 colleague from MS, Rok Yu, knows the ECMA-262 spec pretty well; you might want to talk to him.

    /be
  • This code shows that it is a bug in JScript:


    function test(){};
    for (var i in window) if (i == "test") alert(i);
    alert("test" in window);


    You can't enumerate "test" but it is a member of the the global object. You can change "this" for "window" and you still get the same bug.
  • Good heavens.

    Thanks Brendan, you are totally correct. I don't know how I missed that when I read over the spec.

    I'll rewrite this tomorrow.
  • I wrote:

    Right here, the claim in this blog entry that "There is no requirement that [function foo(){} and this.foo = function(){}] be the same" can be seen to be false for function declarations and statements in global code.

    but of course there's one difference, which does not have anything to do with which object binds to this, or in which object foo is bound: in the declared function case, the function identifier is instantiated as a variable with attributes {DontDelete}. Simply assigning to this.foo sets no such attribute.

    An obscure case in ECMA-262: variable instantiation in eval code does not use DontDelete. I don't recall the rationale, but it makes a kind of common sense: you can't delete declared vars unless they're computed by eval (in which case they might be conditional, and should be deletable).

    /be
  • I've recently stumbled across this (http://www.jibbering.com/faq/faq_notes/closures.html) article. It...
  • Is here any workaround? My JS library want to register all user's objects with names, matched by pattern, on start-up. In Gecjo-based browsers enumeration of global object works. In IE here is a bug. But, may be, here is some woraround?

  • At least I can confirm that it is a pure IE thingy. Run the JavaScript with cscript from command line and it is possible to enumerate the functions of the global object.

  • Erik,

    Like Jörg, I tried your script to enumerate the properties of the __global__ object from cscript and it worked.

    However from ASP (classic) it does not appear that you can enumerate the properties of the global "this" object.

    Is that a limitation of the ASP JScript host?

    After poking around for a few hours, i couldn't find any answers.

  • The error is Microsoft JScript runtime error: Object doesn't support this property or method and it is thrown on the line:

    __global__.visibleToIE = function() {

    Response.Write("IE sees me");

    }

    So it appears you cannot set properties on the global "this" object in ASP JScript either.

Page 1 of 1 (9 items)