29

I'm trying out ES6 and want to include a property inside my function like so

var person = {
  name: "jason",

  shout: () => console.log("my name is ", this.name)
}

person.shout() // Should print out my name is jason

However, when I run this code console only logs my name is. What am I doing wrong?

hippietrail
  • 13,703
  • 15
  • 87
  • 133
thank_you
  • 10,362
  • 17
  • 85
  • 173

5 Answers5

42

Short answer: this points at the nearest bound this - in the code provided this is found in the enclosing scope.

Longer answer: Arrow functions bind their this when they are created do not have this, arguments or other special names bound at all - when the object is being created the name this is found in the enclosing scope, not the person object. You can see this more clearly by moving the declaration:

var person = {
  name: "Jason"
};
person.shout = () => console.log("Hi, my name is", this);

And even more clear when translated into a vague approximation of the arrow syntax in ES5:

var person = {
  name: "Jason"
};
var shout = function() {
  console.log("Hi, my name is", this.name);
}.bind(this);
person.shout = shout;

In both cases, this (for the shout function) points to the same scope as person is defined in, not the new scope that the function is attached to when it is added to the person object.

You cannot make arrow functions work that way, but, as @kamituel points out in his answer, you can take advantage of the shorter method declaration pattern in ES6 to get similar space savings:

var person = {
  name: "Jason",
  // ES6 "method" declaration - leave off the ":" and the "function"
  shout() {
    console.log("Hi, my name is", this.name);
  }
};
kip
  • 1,122
  • 6
  • 10
Sean Vieira
  • 140,251
  • 31
  • 286
  • 277
30

Agreed with @Sean Vieira - in this case this is bound to the global object (or, as pointed out in the comment, more generally to an enclosing scope).

If you want to have a shorter syntax, there is another option - enhanced object literals support short syntax for property functions. this will be bound as you expect there. See shout3():

window.name = "global";

var person = {
    name: "jason",

    shout: function () {
        console.log("my name is ", this.name);
    },
    shout2: () => {
        console.log("my name is ", this.name);
    },
    // Shorter syntax
    shout3() {
        console.log("my name is ", this.name);
    }
};

person.shout();  // "jason"
person.shout2(); // "global"
person.shout3(); // "jason"
kamituel
  • 30,600
  • 3
  • 71
  • 91
16

The accepted answer is excellent, concise, and clear but i will elaborate a little on what Sean Vieira said:

Arrow functions do not have this arguments or other special names bound at all.

Because the arrow function doesn't have a "this", it uses the parent's "this". "this" always points to the parent, and the parent of the person object is Window (if you're in a browser).

To prove it run this in your console:

var person = {
    name: "Jason",
    anotherKey: this
}
console.log(person.anotherKey)

You'll get the Window object.

I find this to be a really helpful way to think about it. It isn't quite the full story, as what the "this" of an object literal is is another discussion.

Tenbrink
  • 336
  • 4
  • 10
2

Here the value of this inside of the function is determined by where the arrow function is defined not where it is used.

So this refers to global/window object if not wrapped in other namespace

A.B
  • 17,478
  • 3
  • 28
  • 54
0

The problem is that (MDN)

An arrow function expression [...] lexically binds the this value.

Arrow functions capture the this value of the enclosing context.

Therefore, the value of this in that function will be the value of this where you create the object literal. Probably, that will be window in non-strict mode and undefined in strict mode.

To fix it, you should use a normal function:

var person = {
  name: "jason",
  shout: function(){ console.log("my name is ", this.name) }
}
person.shout();
Oriol
  • 225,583
  • 46
  • 371
  • 457