4

I am using Babel to transpile an ES2015 class:

class Foo {
    constructor(foo) {
        this.foo = foo;
    }

    sayFoo() {
        console.log(this.foo);
    }
}

This class works exactly as I expect if I say something like fooVar.sayFoo(). However, if I try to treat the method like a typical JavaScript function and pass it as a parameter (element.click(fooVar.sayFoo)), when the method actually runs its lexical this is the event object, not the instance of Foo, so the this.foo is undefined.

Instead, since I specified an instance of Foo, I expected fooVar.sayFoo to be bound to the value of fooVar. I have to write an extra wrapper function for this to work as I expect.

I tried to look through the ES6 spec but couldn't figure out the scoping rules. Is this strange scoping behavior correct according to spec, or is it a bug somewhere (e.g., in Babel)?

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • 1
    There's no magic happening there, the `this` value of a function depends on the execution context, i.e. how it's called, not how it's defined, always has, nothing to do with ES2015. When referencing a function like that, the element is set as the `this` value. – adeneo Jan 28 '16 at 00:39
  • 4
    You don't have to write an extra wrapper function, you can use bind https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind `element.click(fooVar.sayFoo.bind(fooVar))` – Joseph Young Jan 28 '16 at 00:40
  • @adeneo As the entire "class" system is new, I expected the expression `fooVar.sayFoo` to already be bound to `fooVar`--the alternative that Joseph posted seems to me to ought to have been the automatic behavior. I'll just chalk this up to more JavaScript idiosyncrasy; thanks. – chrylis -cautiouslyoptimistic- Jan 28 '16 at 00:46
  • Class or no class doesn't matter, Babel probably transpiles that into an object literal anyway, but execution context is still what sets the `this` value, anything else would break the internet, just think of all the event handlers with referenced functions that would stop working. – adeneo Jan 28 '16 at 00:48
  • Binding is expensive, doing it automatically for every function, even when you don't need it, would slow down your code with no way to optimize it. – loganfsmyth Jan 28 '16 at 00:52
  • The answer to your question seems to be: Because the spec says so, or, because someone decided to do it so. Browsers can optimize prototypes, auto-binding would kind of defeat the purpose of having prototypes I guess. – Felix Kling Jan 28 '16 at 00:58
  • 1
    The class system isn't really that new, I think it's mostly syntactic sugar on top of the old prototype stuff. – Barmar Jan 28 '16 at 00:59
  • I'm voting to close this question as off-topic because it is asking about language specification design decisions. – Felix Kling Jan 28 '16 at 00:59
  • @loganfsmyth Automatic binding doesn't need to be expensive if it were actually supported by the language. A half-competent JIT would be able to treat most method calls the same as now, so overhead would mostly only exist in cases where an explicit bind() call would already have been necessary. Some patterns would need to change, but the JavaScript community doesn't seem to have a problem with rebuilding the world from scratch every few years. Automatic binding simply doesn't exist because the motivation was not to redesign the object model from scratch, but only to sugar the existing one. – Jeremy Jan 28 '16 at 01:00
  • @FelixKling I'll dispute closure. I stated a clearly answerable question: "Is this the correct behavior, even though it seems weird?" I'm not asking for an opinion from the language designers. – chrylis -cautiouslyoptimistic- Jan 28 '16 at 01:09
  • OK. The title seems to ask for something else. – Felix Kling Jan 28 '16 at 01:10
  • @Barmar I understand that, but one naive approach would expect the sugar underlying `instance.method` to return a function already bound to the instance. – chrylis -cautiouslyoptimistic- Jan 28 '16 at 01:10
  • @chrylis The problem is that `instance.method` already has a meaning, so changing that would be an incompatibililty. They could have added a new syntax that does what you want. – Barmar Jan 28 '16 at 01:11

1 Answers1

4

Is this the correct behavior, even though it seems weird?

Yes. Methods are more or less syntactic sugar for functions assigned to prototype properties.

Only arrow functions treat this differently.

Instead of writing a wrapper function, however, you can explicitly bind the instance to the method:

element.click(fooVar.sayFoo.bind(fooVar));

See also How to access the correct `this` / context inside a callback?

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143