4

Why does o.foo() print the global object to the console?

let o = {
  foo: () => console.log(this),
  bar() { console.log(this); }
};

o.foo(); // Global object / undefined
o.bar(); // o

I thought the equivalent of the arrow function might be something like this (but it's not):

let o = {
    foo: function() {
        console.log(this);
    },
    bar() {   
        console.log(this);
    }
};

o.foo(); // o
o.bar(); // o
ashuvssut
  • 1,725
  • 9
  • 17
Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • 2
    I assume you derived the equivalence from [my answer](http://stackoverflow.com/a/29945519/218196). Note the big difference: I declared `self` *outside* the arrow function, you are declaring it *inside* the arrow function. that's not how it works. The equivalent would be `var self = this; let o = {foo: console.log(self); };`. – Felix Kling Apr 29 '15 at 20:51
  • @FelixKling Yes. I thanks for clarifying that. That was my misunderstanding (which I realised after writing the question). – Ben Aston Apr 29 '15 at 20:53

1 Answers1

6

Arrow functions retain the this of the surrounding execution context from when they were declared. They don't change this all the time, like normal methods do.

In your example, there is no execution context surrounding foo, so this is undefined. This is the same behavior as a function declared using the function keyword at the same scope and called in the same way. You can test that with:

let foo = () => { return this; }
console.log(foo() === undefined);

From the spec at 14.2.16:

Any reference to arguments, super, or this within an ArrowFunction must resolve to a binding in a lexically enclosing environment. Typically this will be the Function Environment of an immediately enclosing function.

(emphasis mine)

Interestingly, when that arrow function is present in the global scope, the BabelJS transpiler simply outputs

"use strict";

var foo = function foo() {
  return undefined;
};

as if that were the only correct behavior. Reading the spec, it doesn't seem quite as strict, but this does seem like the right thing to do.

It appears that you can end up with the global object as this if you manage to run ES6 code using arrows without modules. Per the spec at 10.2.1:

  • Global code is strict mode code if it begins with a Directive Prologue that contains a Use Strict Directive (see 14.1.1).
  • Module code is always strict mode code.

So it is possible to get ES6 code in a non-strict context. If that happens, then this will use the classical fallback and be set to window (in the spec at 9.2 as an undefined [[ThisMode]]).

In this example, there is no immediately enclosing function, thus no lexical scope to pick up, so this ends up undefined.

In your second example, the capture on this doesn't make a difference:

let o = {
    foo: function() {
        var self = this;
        console.log(self);
    },
    bar() {
        
        console.log(this);
    }
};

The var self statement is within the function, so it won't do anything. If you were to do:

let o = {
    foo: function() {
        var self = this;
        return function() {
            console.log(self);
        }
    }(),
    bar() {
        
        console.log(this);
    }
};

then it will have the behavior you expect (roughly), although this will still be undefined (or the global object) because you're not within a lexical environment to be captured.

If you were to use

class Foo {
  bar() {
    let baz = () => { return this; }
  }
}

then bar could be transpiled down to

function bar() {
  var _this = this;

  var baz = function baz() {
    return _this;
  };
}

which does what you want. That only works because there is a surrounding context to be captured.

ssube
  • 47,010
  • 7
  • 103
  • 140
  • "In this example, there is no immediately enclosing function, thus no lexical scope to pick up, so this ends up undefined." ...or the global object in sloppy mode? – Ben Aston Apr 29 '15 at 19:34
  • I do not think "function scope" is a well-defined term in the spec. You mean `[[Environment]]` internal property? – Ben Aston Apr 29 '15 at 19:36
  • I can't actually reproduce the second behavior on my machine, but that's probably because Babel simply transpiles `let foo = () => { return this; }` straight to `var foo = function foo() { return undefined; };` (no joke). That makes me think that this is a sufficiently-well-defined behavior to rely on. – ssube Apr 29 '15 at 19:37
  • Replaced "function scope" with the correct terminology and added some info about the second example. – ssube Apr 29 '15 at 19:46
  • "In your example, there is no execution context surrounding foo" I know what you mean, but the global execution context surrounds it (if run in the global scope). – Ben Aston Apr 29 '15 at 19:53
  • @BenAston I don't think "lexical environment"/"execution context" allows that, actually. It clearly lists the context as being null if the function is at the script-level (i.e., just part of a script body, not on a prototype). "If the context is evaluating the code of a Script or Module, the value is null." – ssube Apr 29 '15 at 19:56
  • No, there is a global execution context. What you might be referring to is the `null` `[[Outer]]` property on the `[[LexicalEnvironment]]` of the global execution context? Happy to be corrected. – Ben Aston Apr 29 '15 at 19:57
  • @BenAston yknow, I'm honestly not sure. I don't see anything helpfully clear in the spec and nobody in [the JS SO chat](http://chat.stackoverflow.com/rooms/17/javascript) has volunteered an answer yet. – ssube Apr 29 '15 at 20:05
  • OK, there are a couple of issues with my earlier comment, but the thrust remains correct I believe. Step 3 of http://people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-scriptevaluation is the creation of said context. It is not called the global execution context in the spec though. – Ben Aston Apr 29 '15 at 20:14
  • 1
    FWIW, bable converts `this` to `undefined` because it assumes that every file is a *module*. Modules are always strict, and their `this` value is always `undefined`. `this` will refer to `window` if the arrow function is declared at the top level of a *script*. – Felix Kling Apr 29 '15 at 20:43