Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why doesn't 'this' of an arrow function change inside an nested object literal? [duplicate]

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?

like image 470
jt-wang Avatar asked Jun 05 '16 14:06

jt-wang


People also ask

Why should we not use arrow functions inside of objects?

An arrow function doesn't have its own this value and the arguments object. Therefore, you should not use it as an event handler, a method of an object literal, a prototype method, or when you have a function that uses the arguments object.

Can we use this inside arrow function?

In short, with arrow functions there are no binding of this . In regular functions the this keyword represented the object that called the function, which could be the window, the document, a button or whatever. With arrow functions the this keyword always represents the object that defined the arrow function.

Do arrow functions have their own binding to the this keyword?

An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new. target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors.

Can we write arrow function inside object in JavaScript?

Arrow functions cannot be used to write object methods because, as you have found, since arrow functions close over the this of the lexically enclosing context, the this within the arrow is the one that was current where you defined the object.


3 Answers

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;
like image 169
Johannes H. Avatar answered Oct 08 '22 09:10

Johannes H.


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.

like image 33
T.J. Crowder Avatar answered Oct 08 '22 10:10

T.J. Crowder


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.

like image 1
Robert Moskal Avatar answered Oct 08 '22 09:10

Robert Moskal