9

I found that 'this' keyword seems always point to global in using arrow function inside a nested object literal.

According to other questions, the following snippet can be explained as an arrow function's 'this' is defined in lexical context.

var c = 100;
var a = {c:5 , fn: () => {return this.c;} };
console.log(a.c); //100

However, I cannot understand the following code (nested object literal):

var c = 100;

var a = {
    c: 5,
    b: {
        c: 10,
        fn: ()=> {return this.c;}
    }
}

console.log(a.b.fn());// still 100, why not 5?

I mean, if consider from the lexical context aspect, shouldn't the 'this' in a.b.fn point to a?

Why, no matter how many levels the object is nested, do all of the 'this' instances point to window or global?

jt-wang
  • 93
  • 1
  • 7
  • 4
    It points to the current scope. Objects don't change scope, only functions change scope. So the `this` inside of the arrow function will refer to the nearest function it is inside. In your case, that is just the top level. – mash Jun 05 '16 at 14:46
  • 1
    @mash: `this` and scope are largely unrelated, and `this` only rarely "refers to ... [a] function." But the gist of the comment is, of course, correct. – T.J. Crowder Jun 05 '16 at 14:56
  • Just for the record: arrow functions change scope just as any function. They just bind `this`, `arguments`, `super` and `new.target` lexically instead of defining their own variablesd that shadow the outer ones. – Johannes H. Jun 05 '16 at 15:01
  • @JohannesH.: Just for the record :-), arrow functions don't bind `this` at all. (Or more accurately: The environment record created when we call them doesn't.) Which is why they inherit the `this` of the execution context in which they're created: They close over it, exactly like closing over anything else in that context (e.g., variables). – T.J. Crowder Jun 05 '16 at 15:04
  • @TJCrowder Correct. However this behaviour is still called "lexical binding" in almost all JS references, including MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions (first paragraph) – Johannes H. Jun 05 '16 at 15:05
  • 1
    Related: [Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?](http://stackoverflow.com/q/34361379/218196) – Felix Kling Jun 05 '16 at 15:41

3 Answers3

6

The only expression in JavaScript that changes scope is a function and, as of ES6, blocks (note that an object literal is not a block, despite having curly braces around it). This means: everything that is not inside a function is in global scope.

In global scope, this refers to the global object (window in case of browsers). The only thing that changes scope is the arrow function (yes, they DO change scope!) - but it binds this lexically (which means, it uses the this from the outer scope), so it's still the global object.

If you want this to refer to the aobject, use an IIFE instead of an object literal:

var c = 100;

var a = new function () {
    this.c = 5;
    this.b = {
        c: 10,
        fn: ()=> {return this.c;}
    }
}()

alert(a.b.fn()) // 5;

Or, to bind bto this:

var c = 100;

var a = {
    c : 5,
    b : new function () {
        this.c = 10;
        this.fn = ()=> {return this.c;}
    }()
}

alert(a.b.fn()) // 10;

Alternatively, to bind this to b, you can also use a regular function instead of the arrow function:

var c = 100;

var a = {
    c: 5,
    b: {
        c: 10,
        fn: function () {return this.c;}
    }
}

alert(a.b.fn()) // 10;
Johannes H.
  • 5,554
  • 1
  • 18
  • 39
  • "*The only expression in JavaScript that changes scope is a function*" - nope. Blocks do as well. – Bergi Jun 05 '16 at 15:07
  • @Bergi I agree with you and that's why I was confused about `this` in nested object literals. – jt-wang Jun 05 '16 at 15:08
  • @jt-wang: but object literals aren't blocks :-) – Bergi Jun 05 '16 at 15:09
  • They do as of ES6, yes. However block scoping only affects variables declared with `let` and has no influence on `var`, so it's half-half. However, I will change my wording to indicate that. – Johannes H. Jun 05 '16 at 15:10
  • 4
    Beware conflating *scope* and *execution context*. They're related, but very different. `this` relates to execution contexts, not scope. – T.J. Crowder Jun 05 '16 at 15:11
  • @Bergi I don't think it is exactly a duplicated question of "Methods in ES6 objects: using arrow functions" because this question focuses on the behavior of `this` in a nested object literal, and that question doesn't explain it. – jt-wang Jun 05 '16 at 15:23
5

It's the same as this where the object initializer is. So in both of your examples, it's the same as this where your var a = ... line is. this never changes within a given execution context, and object initializers don't create a new execution context; only functions and eval do that. In your examples, the only time a new execution context is created is when you call fn, and since fn is an arrow function, it closes over the this in the execution context where it was created (which happens to be the global execution context in your examples).

The reason you see 100 for this.c in your example code is that this at global scope (in loose mode) refers to the global object, and var variables at global scope become properties of the global object, and so this.c is the c global variable.

If you put all of that code in a scoping function, like this:

(function() { // Or `(() => {`, doesn't matter in this case
    var c = 100;

    var a = {
        c: 5,
        b: {
            c: 10,
            fn: ()=> {return this.c;}
        }
    }
    console.log(a.b.fn());// still 100, why not 5?
})();    

...this.c would be undefined, because although this would still refer to the global object, c would no longer be a global variable (and thus a property of the global object).

If you want this inside fn to refer to b in the expression a.b.fn(), then you don't want an arrow function there, you want a normal function; you'd get 10 (the value of a.b.c), not 5 (the value of a.c).

Of course, as this is a one-off object and fn closes over a, you can also just make fn's body return a.c; or return a.b.c; depending on which c you want.

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • What about the second code snippet? The `b` is initialed inside `a`, but why does `a.b.c` points to global's c instead of `a.c`? – jt-wang Jun 05 '16 at 14:50
  • 1
    @jt-wang: I was talking about the second snippet. :-) `this.c` refers to the `c` global in that example for the same reason as the first: `this` is still what it was as of the `var a` line. `this` never changes within an execution context, and an object initializer doesn't create a new execution context (only functions and `eval` do). – T.J. Crowder Jun 05 '16 at 14:54
1

Another way to think about this is that there is no notion of a new "this" scope inside the literal.

A function, on the other hand, would introduce a new scope.

Robert Moskal
  • 19,576
  • 6
  • 57
  • 76