tags:

views:

41

answers:

2

Consider this script:

function Obj(prop) {
    this.prop = prop;
}

var NS = {
    strings: ['first','second','third'],
    objs: [],
    f1: function() {
        for (s in this.strings) {
            var obj = new Obj(this.strings[s]);
            obj.f2 = function() {
                alert(obj.prop);
            }
            this.objs.push(obj);
        }
    }
}

NS.f1();
NS.objs[0].f2(); // third
NS.objs[1].f2(); // third
NS.objs[2].f2(); // third

Not exactly the expected output, however when I update to this:

function Obj(prop) {
    this.prop = prop;
}

var NS = {
    strings: ['first','second','third'],
    objs: [],
    f1: function() {
        for (s in this.strings) {
            var obj = new Obj(this.strings[s]);
            this.wire(obj); // replaces previous function def
            this.objs.push(obj);
        }
    },
    wire: function(obj) {
        obj.f2 = function() {
            alert(obj.prop);
        } // exact same code and function def as the first example
    }
}

NS.f1();
NS.objs[0].f2(); // first
NS.objs[1].f2(); // second
NS.objs[2].f2(); // third

This seems to work, and I've no idea why. Can anybody enlighten me? Thanks

+1  A: 

Check out http://jibbering.com/faq/notes/closures/.

It will explain the "something else" and probably all you'll ever want to know about how JavaScript variables and scopes work. JavaScript uses "execution context" closures, not "lexical variable" closures (edit: it is still a lexical binding, just not necessarily as expected -- see below).

In the first example, the same obj was bound three times or rather, the same obj property (edit: the specification doesn't require this, but calling it a property is one way to explain it) of the single bound execution context is shared!

var does not "declare" a variable (edit: it is an annotation that applies to an entire scope and is not affected by {}'s, excepting the following) and function is how one can introduce new scopes -> new execution contexts (which is why the 2nd example works as expected). New scopes are only introduced with function (or eval/similar).

Happy coding.

pst
Thanks, much appreciated. That'll be a decent read on the way home from work
Matty F
+1  A: 

f1

   ...only has a single obj in a single closure which is assigned 3 times

f2

   ...has ultimately 3 objs in three closures (plus the one that f1 also has) which are each assigned once

One funny thing about JS that may help: the following two functions do the same thing:

function a1() {
  var a,b,c;

  a = 1;
  b = 2;
  c = 3;
}

function a2() {
  a = 1;
  b = 2;
  c = 3;

  var a,b,c;
}

The var declaration always operates at function-level scope regardless of where in the function or how deeply nested it is in inner blocks.

DigitalRoss
Do you mean there are 3 wire functions in memory, each holding a reference to a different obj?
Matty F
Yes, well, 3 closures created out of wire() instances.
DigitalRoss
Wow, something to be wary of. What if I had 200 objects? Would this begin to affect memory and performance?
Matty F
@Matty F At least in theory, it is very possible to accidentally keep alive "temporary" data this way, yes. However it really depends on what the objects are as to whether it would affect performance in any tangible way. I do not know what optimizations, if any, modern JS engines apply to detect/release "dead" variables (who's execution context is still active). No JS engine could perform an optimization like this around an `eval` though. You may be interested in the `delete` operator.
pst