jussi.mattila
jussi.mattila

Reputation: 237

Explain effect of ES6 class constructor and arrow functions

I'm currently learning JS and ES6 in particular. I'm having trouble understanding why my code with class constructor and arrow functions doesn't work without a few changes.

Here is where I started, an ES6 module exporting this flux-like dispatcher object.

// RiotControl dispatcher formatted as ES6 module.
// https://github.com/jimsparkman/RiotControl

var dispatcher = {
  stores: [],
  addStore: function(store) {
    this.stores.push(store);
  }
};

['on','one','off','trigger'].forEach(function(api){
  dispatcher[api] = function() {
    var args = [].slice.call(arguments);
    this.stores.forEach(function(el){
      el[api].apply(null, args);
    });
  };
});

export default dispatcher

I wanted to make a class out of this code and originally ended up with:

// RiotControl dispatcher formatted as ES6 class module.
// https://github.com/jimsparkman/RiotControl

export default class {
  constructor() {        
    this.stores = []    
    this.addStore = store => {
      this.stores.push(store);
    }

    ['on','one','off','trigger'].forEach(fn => {
      this[fn] = () => {
        var args = [].slice.call(arguments)
        this.stores.forEach(function(el){
          el[fn].apply(null, args)
        })
      }
    })
  }
}

For reasons unknown to me, this doesn't work.

  1. The first .forEach(...) results in Uncaught TypeError: Cannot read property 'forEach' of undefined, as if the array is not defined.
  2. var args = [].slice.call(arguments) results in args being a zero length array instead of actually, umm, having the arguments.

To get the code working, I changed it to this:

// RiotControl dispatcher formatted as ES6 class module.
// https://github.com/jimsparkman/RiotControl

export default class {
  constructor() {        
    this.stores = []    
    this.addStore = store => {
      this.stores.push(store);
    }

    var api = ['on','one','off','trigger']
    api.forEach(fn => {
      this[fn] = function() {
        var args = [].slice.call(arguments)
        this.stores.forEach(function(el){
          el[fn].apply(null, args)
        })
      }
    })
  }
}

Thus, the errors were fixed by

  1. declaring an array and calling .forEach on that and
  2. using a regular callback function instead of the arrow function.

Please explain why the forEach with an inline array fails and why slicing the arguments list fails from inside the arrow function.

Also, bonus question, why is this in ´this.stores.foreach` bound to my object instance and not e.g. the event that causes the function to be called?

Upvotes: 2

Views: 822

Answers (1)

Felix Kling
Felix Kling

Reputation: 816364

Please explain why the forEach with an inline array fails and why slicing the arguments list fails from inside the arrow function.

The code is interpreted as follows:

this.addStore = store => { ... }['on','one','off','trigger'].forEach(...)
// which becomes
this.addStore = store => { ... }['trigger'].forEach(...)

I.e. you are trying to access the trigger property of a function, which of course does not exist. Use a semicolon after the function definition to explicitly terminate the assignment expression:

this.addStore = store => {
  this.stores.push(store);
};
['on','one','off','trigger'].forEach(...);

Also, bonus question, why is this in this.stores.foreach bound to my object instance and not e.g. the event that causes the function to be called?

this is not bound here. What this refers to depends on how the function is called, which you shouldn't show.


var args = [].slice.call(arguments) results in args being a zero length array instead of actually, umm, having the arguments.

In arrow functions, both this and arguments are lexically scoped. I.e. arrow functions don't have their own arguments object.

Upvotes: 1

Related Questions