Variable scope and closures

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Grant Wagner

    Variable scope and closures

    Given the following working code:

    function attributes() {
    var attr1 = arguments[0] || '_';
    var attr2 = arguments[1] || '_';
    return (
    function (el1, el2) {
    var value1 = el1[attr1] + el1[attr2];
    var value2 = el2[attr1] + el2[attr2];
    if (value1 > value2) return 1;
    else if (value1 < value2) return -1;
    else return 0;
    }
    );
    }

    var a = [
    { a:'smith', b:'john' },
    { a:'jones', b:'bob' },
    { a:'smith', b:'jane' }
    ];
    a.sort(attribut es('a', 'b'));
    for (var i =0; i < a.length; i++) {
    document.write( a[i].a + ', ' + a[i].b + '<br>');
    }

    My question is, are attr1 and attr2 guaranteed to exist through
    the lifetime of a.sort(attribut es('a', 'b'))?

    As I understand it, the anonymous inner function reference I am
    returning is a property of attributes(). As such, when I return a
    reference to the anonymous inner function, the outer attributes()
    function must continue to exist (as must attr1 and att2) until
    there are no further references to the inner anonymous function.

    As a result, there is no danger of attr1 or attr2 "disappeari ng"
    during the repeated calling of the anonymous inner function.

    Is my explanation basically correct, or am I deluding myself and
    I'm just lucky that the garbage collector hasn't recovered attr1
    or attr2 while the sort is still going on? In other words, is the
    behaviour I'm seeing consistent and predictable, or should I
    change my approach?

    --
    Grant Wagner <gwagner@agrico reunited.com>
    comp.lang.javas cript FAQ - http://jibbering.com/faq

  • Lasse Reichstein Nielsen

    #2
    Re: Variable scope and closures

    Grant Wagner <gwagner@agrico reunited.com> writes:
    [color=blue]
    > Given the following working code:
    >
    > function attributes() {
    > var attr1 = arguments[0] || '_';
    > var attr2 = arguments[1] || '_';
    > return (
    > function (el1, el2) {
    > var value1 = el1[attr1] + el1[attr2];
    > var value2 = el2[attr1] + el2[attr2];
    > if (value1 > value2) return 1;
    > else if (value1 < value2) return -1;
    > else return 0;
    > }
    > );
    > }
    >[/color]
    G
    Grant Wagner <gwagner@agrico reunited.com> writes:
    [color=blue]
    > iven the following working code:
    >
    > function attributes() {
    > var attr1 = arguments[0] || '_';
    > var attr2 = arguments[1] || '_';
    > return (
    > function (el1, el2) {
    > var value1 = el1[attr1] + el1[attr2];
    > var value2 = el2[attr1] + el2[attr2];
    > if (value1 > value2) return 1;
    > else if (value1 < value2) return -1;
    > else return 0;
    > }
    > );
    > }
    >
    > var a = [
    > { a:'smith', b:'john' },
    > { a:'jones', b:'bob' },
    > { a:'smith', b:'jane' }
    > ];
    > a.sort(attribut es('a', 'b'));
    > for (var i =0; i < a.length; i++) {
    > document.write( a[i].a + ', ' + a[i].b + '<br>');
    > }
    >
    > My question is, are attr1 and attr2 guaranteed to exist through
    > the lifetime of a.sort(attribut es('a', 'b'))?
    >
    > As I understand it, the anonymous inner function reference I am
    > returning is a property of attributes(). As such, when I return a
    > reference to the anonymous inner function, the outer attributes()
    > function must continue to exist (as must attr1 and att2) until
    > there are no further references to the inner anonymous function.
    >
    > As a result, there is no danger of attr1 or attr2 "disappeari ng"
    > during the repeated calling of the anonymous inner function.
    >
    > Is my explanation basically correct, or am I deluding myself and
    > I'm just lucky that the garbage collector hasn't recovered attr1
    > or attr2 while the sort is still going on? In other words, is the
    > behaviour I'm seeing consistent and predictable, or should I
    > change my approach?
    >
    > --
    > Grant Wagner <gwagner@agrico reunited.com>
    > comp.lang.javas cript FAQ - http://jibbering.com/faq
    >
    > var a = [
    > { a:'smith', b:'john' },
    > { a:'jones', b:'bob' },
    > { a:'smith', b:'jane' }
    > ];
    > a.sort(attribut es('a', 'b'));
    > for (var i =0; i < a.length; i++) {
    > document.write( a[i].a + ', ' + a[i].b + '<br>');
    > }
    >[/color]
    [color=blue]
    > My question is, are attr1 and attr2 guaranteed to exist through
    > the lifetime of a.sort(attribut es('a', 'b'))?[/color]

    Yes. That's because the *closure* you return contains references
    to its free variables, in this case "attr1" and "attr2". They will
    live as long as the function you return.
    [color=blue]
    > As I understand it, the anonymous inner function reference I am
    > returning is a property of attributes().[/color]

    "Property" is usually used for properties of objects, and that it
    isn't. The function returned is independent of the function
    "attributes " *except* that it contains the variables object of
    the call to attributes in its scope chain.
    [color=blue]
    > As such, when I return a reference to the anonymous inner function,
    > the outer attributes() function must continue to exist (as must
    > attr1 and att2) until there are no further references to the inner
    > anonymous function.[/color]

    To be, painfully, exact:

    The attributes function itself exists, as usual.

    The variables object created when the attributes function was called
    still exists, because it is in the scope chain of the returned
    closure.

    When a function *value* is created (by evaluating a function
    expression or a function declaration), it contains both the
    code of the body of the function *and* the scope chain that
    the function expression/declaration is evaluated in. That means
    that the created function value is "closed" wrt. the values of
    its free variables. That's why it's called a closure.
    [color=blue]
    > As a result, there is no danger of attr1 or attr2 "disappeari ng"
    > during the repeated calling of the anonymous inner function.[/color]

    Correct. Javascript is statically scoped and has first class and
    nested functions.
    [color=blue]
    > Is my explanation basically correct, or am I deluding myself and
    > I'm just lucky that the garbage collector hasn't recovered attr1
    > or attr2 while the sort is still going on? In other words, is the
    > behaviour I'm seeing consistent and predictable, or should I
    > change my approach?[/color]

    It is consistent and predictable, and entirely according to standard.

    Javascript is not C++, local variables are not allocated on a stack
    the dies when the function returns. They live in an object called the
    "variables object" of the function call. That object is not accessible
    programmaticall y (so we don't know how it's *really* implemented), but
    nested functions generate closures that contain references to all
    the variable objects that are in scope.

    Example code:

    function foo() {
    var i = 0;
    var bar = function() { return i; } // refers to variable i, not its value
    i++
    var baz = function() { return i; }
    i++;
    alert([bar(),baz()]); // alerts 2,2, because both refer to the same variable,
    // which has value 2.
    }

    /L
    --
    Lasse Reichstein Nielsen - lrn@hotpop.com
    DHTML Death Colors: <URL:http://www.infimum.dk/HTML/rasterTriangleD OM.html>
    'Faith without judgement merely degrades the spirit divine.'

    Comment

    • Richard Cornford

      #3
      Re: Variable scope and closures

      Grant Wagner wrote:[color=blue]
      > Given the following working code:
      >
      > function attributes() {
      > var attr1 = arguments[0] || '_';
      > var attr2 = arguments[1] || '_';
      > return (
      > function (el1, el2) {
      > var value1 = el1[attr1] + el1[attr2];
      > var value2 = el2[attr1] + el2[attr2];
      > if (value1 > value2) return 1;
      > else if (value1 < value2) return -1;
      > else return 0;
      > }
      > );
      > }
      >
      > var a = [
      > { a:'smith', b:'john' },
      > { a:'jones', b:'bob' },
      > { a:'smith', b:'jane' }
      > ];
      > a.sort(attribut es('a', 'b'));
      > for (var i =0; i < a.length; i++) {
      > document.write( a[i].a + ', ' + a[i].b + '<br>');
      > }
      >
      > My question is, are attr1 and attr2 guaranteed to exist through
      > the lifetime of a.sort(attribut es('a', 'b'))?[/color]

      Should be, that is what ECMA 262 3rd edition says should happen.
      [color=blue]
      > As I understand it, the anonymous inner function
      > reference I am returning is a property of attributes().[/color]

      Actually the anonymous function is not a property of anything.
      [color=blue]
      > As such, when I return a reference to the anonymous inner
      > function, the outer attributes() function must continue to
      > exist[/color]

      No, I think it would be possible to delete the reference to the outer
      function from the global object and have it garbage collected while the
      inner function is being used without impact on the inner function (or
      attr1 and att2).
      [color=blue]
      > (as must attr1 and att2) until
      > there are no further references to the inner anonymous function.[/color]

      The outer function local variables must continue to exist while the
      inner anonymous function exists, but they are not located where you seem
      to think.

      This becomes a matter of scope chains, [[scope]] properties and
      "variable" objects, and the structures that they form in ECMAScript.

      The outer function has a [[scope]] property that refers to a scope chain
      that should only contain the global object. When the outer function is
      called an "execution context" is entered and that execution context is
      assigned a scope chain of its own. That chain is defined by the chain
      referred to by the outer function's [[scope]] property, with a new
      "variable" object placed at the top of the chain.

      Function formal parameters, local variables and named inner function
      definitions (but not anonymous function expressions) result in
      correspondingly named properties of the "variable" object being created,
      and assigned values (though not immediately in the case of local
      variables).

      When a function object is created (as would be the result of an
      anonymous function expression) it is assigned a [[scope]] property that
      refers to the scope chain of the "execution context" in which it was
      created. So for your anonymous inner function, its [[scope]] property
      refers to a chain consisting of the variable object from the "execution
      context" of the call to the outer function in which it was created
      (with its attr1 and att2 properties), followed by the global object.

      Thus is created a structure of objects through which the anonymous inner
      function object indirectly refers to the variable object from the
      execution context in which it was created, with the properties named
      attr1 and att2. The "variable" object cannot be garbage collected until
      the inner function object is destroyed and its [[scope]] property freed.

      Each call to a function creates a new "execution context" with a new
      "variable" object, so not only are attr1 and att2 preserved but each
      anonymous inner function created by a call to the outer function will
      have access to a different set of attr1 and att2 properties on a
      different "variable" object.

      <URL: http://jibbering.com/faq/faq_notes/closures.html >
      [color=blue]
      > As a result, there is no danger of attr1 or attr2
      > "disappeari ng" during the repeated calling of the anonymous
      > inner function.[/color]

      None.
      [color=blue]
      > Is my explanation basically correct, or am I deluding myself and
      > I'm just lucky that the garbage collector hasn't recovered attr1
      > or attr2 while the sort is still going on? In other words, is the
      > behaviour I'm seeing consistent and predictable, or should I
      > change my approach?[/color]

      The behaviour you are observing is defined in ECMA 262 3rd edition and
      so should be consistent across all ECMAScript (3rd ed) implementations .
      And with the exception of the really poor script implementation in
      Konqueror 2 (labelled "experiment al" and now abandoned) closures seem to
      work 100% consistently across browsers that are likely to still be in
      use (version 4 era onwards).

      Richard.


      Comment

      • Grant Wagner

        #4
        Re: Variable scope and closures

        Richard Cornford wrote:
        [color=blue]
        > The behaviour you are observing is defined in ECMA 262 3rd edition and
        > so should be consistent across all ECMAScript (3rd ed) implementations .
        > And with the exception of the really poor script implementation in
        > Konqueror 2 (labelled "experiment al" and now abandoned) closures seem to
        > work 100% consistently across browsers that are likely to still be in
        > use (version 4 era onwards).[/color]

        Thanks Richard (and Lasse) for the explanation.

        Obviously the test case I posted isn't the complete code. "attributes ()"
        actually returns a function for sorting 1, 2 or 3 attributes. It also pads
        the attribute values prior to concatenation, so you never end up comparing
        "123SmithJo hn" to "345678Lastname Bob". You compare "123 Smith John" to
        "345678Lastname Bob" (well, I actually pad each individual value to 256
        characters).

        I wrote the code, debugged and tested it and then realized I had no idea why
        it was working. Now I do.

        Thanks again.

        --
        Grant Wagner <gwagner@agrico reunited.com>
        comp.lang.javas cript FAQ - http://jibbering.com/faq

        Comment

        Working...