ES6: methods versus callbacks

[2015-10-01] esnext, dev, javascript
(Ad, please don’t block)

There is a subtle difference between an object with methods and an object with callbacks.

An object whose properties are methods  

The this of a method is the receiver of the method call (e.g. obj if the method call is obj.m(···)).

For example, you can use the WHATWG streams API as follows:

let surroundingObject = {
    surroundingMethod() {
        let obj = {
            data: 'abc',
            start(controller) {
                ···
                console.log(this.data); // abc (*)
                this.pull(); // (**)
                ···
            },
            pull() {
                ···
            },
            cancel() {
                ···
            },
        };
        let stream = new ReadableStream(obj);
    },
};

That is, obj is an object whose properties start, pull and cancel are methods. Accordingly, these methods can use this to access object-local state (line *) and to call each other (line **).

An object whose properties are callbacks  

The this of an arrow function is the this of the surrounding scope (lexical this). Arrow functions make great callbacks, because that is the behavior you normally want for callbacks (real, non-method functions). A callback shouldn’t have its own this that shadows the this of the surrounding scope.

If the properties start, pull and cancel are arrow functions then they pick up the this of surroundingMethod() (their surrounding scope):

let surroundingObject = {
    surroundingData: 'xyz',
    surroundingMethod() {
        let obj = {
            start: controller => {
                ···
                console.log(this.surroundingData); // xyz (*)
                ···
            },

            pull: () => {
                ···
            },

            cancel: () => {
                ···
            },
        };
        let stream = new ReadableStream(obj);
    },
};
let stream = new ReadableStream();

If the output in line * surprises you then consider the following code:

let obj = {
    foo: 123,
    bar() {
        let f = () => console.log(this.foo); // 123
        let o = {
            p: () => console.log(this.foo), // 123
        };
    },
}

Inside method bar(), f and o.p work the same, because both arrow functions have the same surrounding lexical scope, bar(). The latter arrow function being surrounded by an object literal does not change that.

Further reading  

Chapter “Callable entities in ECMAScript 6” in ”Exploring ES6”.