The question is directed at people who have thought about code style in the context of the upcoming ECMAScript 6 (Harmony) and who have already worked with the language.
In addition to the great answers so far, I'd like to present a very different reason why arrow functions are in a certain sense fundamentally better than "ordinary" JavaScript functions. For the sake of discussion, let's temporarily assume we use a type checker like TypeScript or Facebook's "Flow". Consider the following toy module, which is valid ECMAScript 6 code plus Flow type annotations: (I'll include the untyped code, which would realistically result from Babel, at the end of this answer, so it can actually be run.)
export class C {
n : number;
f1: number => number;
f2: number => number;
constructor(){
this.n = 42;
this.f1 = (x:number) => x + this.n;
this.f2 = function (x:number) { return x + this.n;};
}
}
Now see what happens when we use the class C from a different module, like this:
let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!
As you can see, the type checker failed here: f2 was supposed to return a number, but it returned a string!
Worse, it seems that no conceivable type checker can handle ordinary (non-arrow) JavaScript functions, because the "this" of f2 does not occur in the argument list of f2, so the required type for "this" could not possibly be added as an annotation to f2.
Does this problem also affect people who don't use type checkers? I think so, because even when we have no static types, we think as if they're there. ("The first parameters must be a number, the second one a string" etc.) A hidden "this"-argument which may or may not be used in the function's body makes our mental bookkeeping harder.
Here is the runnable untyped version, which would be produced by Babel:
class C {
constructor() {
this.n = 42;
this.f1 = x => x + this.n;
this.f2 = function (x) { return x + this.n; };
}
}
let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!
I prefer to use arrow functions at all times where access to local this
is not needed, because arrow function do not bind their own this, arguments, super, or new.target.
Arrow functions were created to simplify function scope
and solving the this
keyword by making it more simpler. They utilize the =>
syntax, which looks like an arrow.
Note: It does not replace the existing functions. If you replace every function syntax with arrow functions, its not going to work in all cases.
Let's have a look at the existing ES5 syntax, If the this
keyword were inside an object’s method (a function that belongs to an object), what would it refer to?
var Actor = {
name: 'RajiniKanth',
getName: function() {
console.log(this.name);
}
};
Actor.getName();
The above snippet would refer to an object
and print out the name "RajiniKanth"
. Let's explore the below snippet and see what would this point out here.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
this.movies.forEach(function(movie) {
alert(this.name + " has acted in " + movie);
});
}
};
Actor.showMovies();
Now what about if the this
keyword were inside of method’s function
?
Here this would refer to window object
than the inner function
as its fallen out of scope
. Because this
, always references the owner of the function it is in, for this case — since it is now out of scope — the window/global object.
When it is inside of an object
’s method — the function
’s owner is the object. Thus the this keyword is bound to the object. Yet when it is inside of a function, either stand alone or within another method, it will always refer to the window/global
object.
var fn = function(){
alert(this);
}
fn(); // [object Window]
There are ways to solve this problem in our ES5
itself, let us look into that before diving into ES6 arrow functions on how solve it.
Typically you would, create a variable outside of the method’s inner function. Now the ‘forEach’
method gains access to this
and thus the object’s
properties and their values.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
var _this = this;
this.movies.forEach(function(movie) {
alert(_this.name + " has acted in " + movie);
});
}
};
Actor.showMovies();
using bind
to attach the this
keyword that refers to the method to the method’s inner function
.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
this.movies.forEach(function(movie) {
alert(this.name + " has acted in " + movie);
}.bind(this));
}
};
Actor.showMovies();
Now with ES6
arrow function, we can deal with lexical scoping
issue in a simpler way.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
this.movies.forEach((movie) => {
alert(this.name + " has acted in " + movie);
});
}
};
Actor.showMovies();
Arrow functions
are more like function statements, except that they bind
the this to parent scope
. If the arrow function is in top scope
, this
argument will refer to window/global scope
, while an arrow function inside a regular function will have its this argument the same as its outer function.
With arrow
functions this
is bound to the enclosing scope
at creation time and cannot be changed. The new operator, bind, call, and apply have no effect on this.
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`
asyncFunction(o, function (param) {
// We made a mistake of thinking `this` is
// the instance of `o`.
console.log('param === this?', param === this);
});
}
};
o.doSomething(); // param === this? false
In the above example, we lost the control of this. We can solve the above example by using a variable reference of this
or using bind
. With ES6, it becomes easier in managing the this
as its bound to lexical scoping
.
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
var o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`.
//
// Because this arrow function is created within
// the scope of `doSomething` it is bound to this
// lexical scope.
asyncFunction(o, (param) => {
console.log('param === this?', param === this);
});
}
};
o.doSomething(); // param === this? true
Inside an object literal.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
getName: () => {
alert(this.name);
}
};
Actor.getName();
Actor.getName
is defined with an arrow function, but on invocation it alerts undefined because this.name
is undefined
as the context remains to window
.
It happens because the arrow function binds the context lexically with the window object
... i.e outer scope. Executing this.name
is equivalent to window.name
, which is undefined.
Object prototype
The same rule applies when defining methods on a prototype object
. Instead of using an arrow function for defining sayCatName method, which brings an incorrect context window
:
function Actor(name) {
this.name = name;
}
Actor.prototype.getName = () => {
console.log(this === window); // => true
return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined
Invoking constructors
this
in a construction invocation is the newly created object. When executing new Fn(), the context of the constructor Fn
is a new object: this instanceof Fn === true
.
this
is setup from the enclosing context, i.e the outer scope which makes it not assigned to newly created object.
var Message = (text) => {
this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');
Callback with dynamic context
Arrow function binds the context
statically on declaration and is not possible to make it dynamic. Attaching event listeners to DOM elements is a common task in client side programming. An event triggers the handler function with this as the target element.
var button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // => true
this.innerHTML = 'Clicked button';
});
this
is window in an arrow function that is defined in the global context. When a click event happens, browser tries to invoke the handler function with button context, but arrow function does not change its pre-defined context. this.innerHTML
is equivalent to window.innerHTML
and has no sense.
You have to apply a function expression, which allows to change this depending on the target element:
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // => true
this.innerHTML = 'Clicked button';
});
When user clicks the button, this in the handler function is button. Thus this.innerHTML = 'Clicked button'
modifies correctly the button text to reflect clicked status.
References: https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/