1

Why does calling click() here prints out the value property.

class Button {
  constructor(value) {
    this.value = value;
  }

  click = () => {
    return this.value;
  }
}

let button = new Button("hi")
console.log(button.click()) //hi

And calling click here prints the value property.

function Button2(value){
  this.value = value

  this.click =  () => {
   return this.value
  }

}

let button2 = new Button2("hi")
console.log(button2.click()) //hi

However, calling click() here prints undefined. This is what I would expect to happen for both examples, since arrow functions have no this.

let button2 = {
    value:"hi",
    click: () => { return this.value }
}

console.log(button2.click()) //undefined

When printing all 3 objects in devtools, they look identical.

button, button2, and button3 when logged in devtools:

   {value: "hi", click: ƒ}
            click: () => { return this.value }
            value: "hi"
            __proto__: Object

Why does the first 2 examples print hi but the third prints undefined, even though the objects look the same when printed?

J Bailey
  • 13
  • 3
  • The invocation is only relevant for non-arrow functions – trincot Jul 03 '20 at 17:13
  • Also, as far as "arrow functions do not have a this", open your browser developer tools, go to the console, and type simply: `this`. It will most likely report the window object. So, *everything* has a this. Edit: and in fact for some fun: `window.value = "weee"; var x = { test: () => this.value }; console.log(x.test());` – Taplar Jul 03 '20 at 17:16
  • @Taplar Do you know why the class example provides different results? – J Bailey Jul 03 '20 at 17:19
  • https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback Essentialy, yes – Taplar Jul 03 '20 at 17:21
  • It's less of a 'class' thing, as it is a function thing. Functions change what the context is, by their very nature. When you call a pure function, their `this` is set to whatever they are called off of. – Taplar Jul 03 '20 at 17:22
  • `window.value = "weee"; function test() { return this.value; }; var x = { value: 'my value', test }; console.log(x.test());` <= modified fun. Now instead of an arrow function, we call an actual pure function. And we call the pure function off of an object, so that object will be set as the `this` – Taplar Jul 03 '20 at 17:24
  • And "classes", within the constructs of javascript, are essentially just functions. The whole `class` definition syntax is just syntatical sugar around making those funcitons. – Taplar Jul 03 '20 at 17:25
  • @Taplar I'm still kind of confused why the results are different. Both functions are called in the format object.click() – J Bailey Jul 03 '20 at 17:25
  • Because, remember, arrow functions **do not** change what `this` is. It doesn't matter what you call them off of, or if you invoke them anonmyously, they leave `this` alone. – Taplar Jul 03 '20 at 17:26
  • @Taplar So what is different about the 2 examples I showed? The objects look exactly the same (`button` and `button2`) and they are called in the same way (`button.click()` and `button2.click()`) but one prints the value and the other prints undefined. – J Bailey Jul 03 '20 at 17:29
  • Because remember, classes are essentially just functions in javascript. So if you are operating off of a class instance, you are operating off of a function, and that function will have already altered what `this` is, within the chain of execution – Taplar Jul 03 '20 at 17:32
  • 1
    Most of the discussion here doesn't have anything to do with the actual underlying reason for the difference in Behavior. A property initializer in a class body is executed in the context of the class Constructor. – Aluan Haddad Jul 03 '20 at 17:33

2 Answers2

2

One of the primary concerns of arrow functions is that they inherit the this keyword from the lexical scope they are defined in. Inside your class, this refers to the class instance as you would expect, but in your object declaration, this will refer to the global object window.

@trincot makes a good point that the primary distinction is that one of these is callable and one is not - basically, the class is a function (and you can actually verify this with typeof TestClass === 'function') which will form the lexical scope of the arrow method definition, but the object's lexical scope is just the global scope, so this === globalThis === window.

You can read the Mozilla docs for more information, and this is basically why it heavily recommends against arrow functions for method definitions. (Especially worth noting is that .bind and .apply do not work for arrow functions either, and the this parameter will be ignored.)

class TestClass {
  // test defined inside TestClass instance scope
  // returns false
  test = () => console.log(this === window)
}

TestObject = {
  // object defined in global lexical scope
  // returns true
  test: () => console.log(this === globalThis && this === window)
};

new TestClass().test();
TestObject.test();
Lewis
  • 4,285
  • 1
  • 23
  • 36
  • 1
    Just an opinion, but I rather think think `function`s are hacky. They have dynamic `this` binding so they could double as methods. If JS had manifest methods from the get go, `function` would have the arrow function behavior. – Aluan Haddad Jul 03 '20 at 18:22
  • 1
    @AluanHaddad Ooo, I've never heard of "manifest methods," what are those? Google not producing meaningful results. – Lewis Jul 03 '20 at 18:27
  • 1
    @Christian it was added in ES2015. Ex1: `const o = { m() { return this.x; } }`. Ex2: `class O { m() {return this.x;} }`. I should have said "manifest syntax for methods". I agree with you, he's doing great! – Aluan Haddad Jul 03 '20 at 18:28
1

The reason is that in the first code block, click is a public field. The documentation on MDN specifies:

When initializing fields, this refers to the class constructor.

By consequence, if that initialisation is an arrow function, then the lexical this at the time of execution of that function will be the same this. And it is a given that the this value of the class constructor is the instance.

This principle is further clarified when we represent that first code block without using class syntax:

function Button(value) {
    this.value = value;
    this.click = () => {
        return this.value;
    }
}

All this in this code are the same this (a consequence of "lexical this").

The second code block has a plain old object property click. An object does not define this for the arrow functions that may be defined for its properties. Only functions (function, possibly constructors) define this by the way they are called. Your object is not called (it is not callable), and the way you call an arrow function has no influence on this either. So the this that occurs within the arrow function here, is determined by the larger context, which could be a wrapping function (not represented in your example), and how it is called. Or, by absence of such a context, this is the global object (or in strict mode: undefined).

trincot
  • 317,000
  • 35
  • 244
  • 286