6

Here's the code:

for (var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); //prints 9 10 times
        console.log(this.i); //prints 0, 1, 2...9
    }.bind({i:i}), i * 1000);
}

Why do i and this.i refer to different things?

Contrast this to a bit of code executed on the global scope:

var x = 5;
console.log(x);
console.log(this.x);//both will print 5

Here the scope was global, and so was the context. A variable declaration set a property of the same name on the global context. On the other hand, within a function scope, this doesn't happen.

var a = function() {
    var x = 5;
    console.log(x); //5
    console.log(this.x); //undefined
    console.log(i);  //undefined
    console.log(this.i);  //10

}.bind({i: 10});
a();

Even if we pass the global context into the local scope, declaring a variable within the function doesn't set it as a property of the global context.

var a = function() {
    var x = 5;
    console.log(x); //5
    console.log(this.x); //undefined
}.bind(window);
a();
console.log(x); //undefined
console.log(this.x); //undefined

What I'm trying to say is this: in the global scope, a variable declaration modifies the global context. But in a function scope, a variable declaration doesn't modify the function's context, no matter what the context is. Why?

Jay
  • 3,471
  • 4
  • 35
  • 49
  • See this answer for an explanation of how `this` works in js: http://stackoverflow.com/questions/13441307/how-does-the-this-keyword-in-javascript-act-within-an-object-literal/13441628?s=1|3.7781#13441628 – slebetman Jun 18 '15 at 05:27
  • Thanks, but not exactly what I was looking for. Updated my question to make it clearer. – Jay Jun 18 '15 at 05:39
  • It's exactly what you're looking for. See rule 3 of that link. The unstated assumption is that I assume you know the difference between global and local variables. And also the fact that in js, global variables are implemented as attributes of the global object – slebetman Jun 18 '15 at 08:43
  • "in js, global variables are implemented as attributes of the global object". This I knew. I guess I always I implicitly assumed that local variables are implemented as attributes of the local context object, and was surprised when that turned out to not be the case. Thanks – Jay Jun 18 '15 at 16:13
  • There is no such thing as a local object. There is the `arguments` object which is roughly the same idea but works differently. – slebetman Jun 19 '15 at 01:53
  • I didn't say "local object". I said "local context object" by which I meant the object that `this` refers to inside of a function, or "the __object__ that is the __context__ of the __local__ scope". This is contrast to `window` which is the "global context object" (a less roundabout way of saying "the __object__ that is the __context__ of the __global__ scope"). If this is not standard terminology, I apologize profusely. – Jay Jun 19 '15 at 06:23
  • Yes, exactly. There is no such thing as a local context object - it doesn't exist. Sorry if I was being unclear. – slebetman Jun 19 '15 at 06:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/80969/discussion-between-jayraj-and-slebetman). – Jay Jun 19 '15 at 06:27

2 Answers2

2

It helps a lot when you think of global scope being on window. So you can say global runs in the context of window. So really:

var x = 5;
console.log(x);
console.log(this.x);//both will print 5

In the last line, this is window so you are running console.log(window.x).

When you use bind, you change the reference of this inside of the "bound" function. For example:

var x = 10;
function log() {
  console.log(this.x);
}

log(); // logs 10

log.bind({x: 20})()  // logs 20

The bind call has made this within log be a reference to the anonymous object we created with {x: 20}. You could also do this:

var myObject = {x: 50};
log.bind(myObject)(); // logs 50
Cymen
  • 14,079
  • 4
  • 52
  • 72
  • Sorry, same comment as to the other (now-deleted answer). I understand how bind changes context. What I don't understand is why variable declarations change the context object in global scope (by adding the declared var as a property of window), but not in function scope (the function's this object doesn't get a new property). A comment from the other answerer said "This is how JS is designed". If that's really the answer, fine, but if there's a deeper answer than that, I'd like to know – Jay Jun 18 '15 at 18:16
  • @Jayraj What is the default global context? `window`. So `this` will always have a value -- if there isn't an inherent one, it will be global which will be `window`. And if you don't put a `var` when declaring a variable, it is global and it goes on `window`. If you do put a `var` on a variable AND you are in the global scope, it will also go on `window`. Does that part make sense? – Cymen Jun 18 '15 at 18:34
  • 1
    Yes. All that makes sense. But if you put a `var` on a variable and you are in the function scope, the var doesn't go on the function's context (and it doesn't matter what the context actually is; could be `window` could be another object). Just being in the function scope changes the behavior of the var statement; it doesn't modify `this` like it would in global scope (again, even when the context of the function is `window`). You may think I'm being obtuse here, but this seems like incredibly subtle behavior to me. – Jay Jun 18 '15 at 18:55
  • @Jayraj I agree for var in global scope, it is a very subtle and odd behavior. I think it is worth remembering that JavaScript was created in about 7 days in a mad rush to have something to ship. But even with that, there are some very interesting subtle behaviors. In practice, these usually don't case problems -- in fact, globals are a major bad practice in my experience and when using CommonJS, I rarely have to think about `window` and global scope. – Cymen Jun 18 '15 at 19:22
0

Thinking about this a bit more, this behavior makes sense. Thanks to everyone who commented and answered. It helped me think through this thing a bit more.

  1. Starting from basics, in JavaScript, the scope of a variable is limited to the function that it is defined in.
  2. In global scope, a var declaration will set the var as a property of the global context (fine)
  3. If we wanted the same behavior in function scope, the var declaration would modify the function's context object. But this has the following problems:

    a. If the context of a function is some object, every var declaration within the function would get set as a property on the object. So this would happen:

Function has object context:

//if var declarations inside functions modified context
//this doesn't happen in reality
var ctx = {b: 5};
var a = function() {
    var c = 7;
    this.b = 10;
}.bind(ctx);
a();
console.log(ctx.b); //10, as expected
console.log(ctx.c); //7, wtf? I never set this

b. If the context of the function isn't set, its context is the global object. We already know this. But if var declarations within functions were allowed to modify context in the same way that var declarations in global scope do, then functional scope would become meaningless

Function has global context:

function a() {
    var b = 5;
    this.c = 10; //this refers to window. it is this function's context
}
a();
console.log(b); //outputs 5 if function vars could modify context, which means functional scope is dead
console.log(c); //outputs 10, as expected

That's why var works differently in a function and outside of it.

Jay
  • 3,471
  • 4
  • 35
  • 49