Arguments in arrow function explained

Arguments in arrow functions featured image

ECMAScript 6 has an amazing feature called Arrow Function. It allows writing a function expression faster and smarter than traditional way (using function keyword). Worth to know, it’s not a one to one replacement for standard functions.

Everyone knows that the arrow function doesn’t have own this. More precisely: arrow function doesn’t have the own lexical scope. It uses an immediately enclosing scope instead. What does it mean?

var sumMany = function () {
    return [].reduce.call(arguments, function (a, b) {
      return a + b;
    }, 0);
};

console.log(sumMany(1,2,3)); // 6

Please notice, that this function use arguments variable, which is the reference to the array of arguments provided to lexical scope of the sumMany function. Let’s try something similar in ES6.

const sumMany = () => [].reduce.call(arguments, (a, b) => a + b, 0);

console.log(sumMany(1,2,3)); // Uncaught ReferenceError: arguments is not defined

Unfortunately, it doesn’t work. We used an arguments variable as before but in this case, this variable is bound to enclosing scope. In this case, it doesn’t have defined arguments. According to the ECMAScript 2015 Language Specification:

Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. Typically this will be the Function Environment of an immediately enclosing function.

And this is ok! Because arrow function wasn’t designed to be a substitute of a traditional function expression. Probably you have a dozen of places in your code where its behavior was desirable. Just think about all these .bind(this) in your code. Looks familiar?

function UserService() {
    this.findAll = function () {
        return Promise.resolve({data: [{ name: "Jake" }]});
    };
}

function UserController(UserService) {
    this.users = [];

    UserService.findAll().then(function (response) {
        this.users = response.data;
    });

    this.printNames = function () {
        this.users.forEach(function (user) {
            console.log(user.name);
        });
    };
}

There is a mistake in above code, take a look at the usage of findAll function. The nested function has different scope, so instead of assign users to the controller, it assigns theirs to the global object, e.g. Window. To fix it, we should bind a nested function to the enclosing scope.

function UserController(UserService) {
    this.users = [];

    UserService.findAll().then((function (response) {
        this.users = response.data;
    }).bind(this));

    /// ...
}

Or pass reference to correct this, e.g.

function UserController(UserService) {
    var self = this;
    this.users = [];

    UserService.findAll().then(function (response) {
        self.users = response.data;
    });

    /// ...
}

Both of them look hacky and tricky. If you use an ES6, you’ll have an easy way to handle it. By using an arrow function you’ll make your code more understandable.

function UserController(UserService) {
    this.users = [];

    UserService.findAll().then(response => this.users = response.data);

    this.printNames = () => {
      this.users.forEach(user => console.log(user.name));
    };
}

Wherever you need to provide a simple callback function, you may apply arrow functions. They are also very helpful when you want to write the code in a functional style. Ultimately, let’s fix our sumMany implementation

const sumMany = function () {
  return [].reduce.call(arguments, (a, b) => a + b, 0);
};

console.log(sumMany(1,2,3)); // 6

Keep in mind when you have to use a function literal instead of an arrow function. It’s very helpful and saves a lot of time.

Resources

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.