There are two major differences.
The first:
function handleKeyDown(e) {
doStuff(e.keyCode);
}
Here you're using a function declaration (or function statement); in the case of arrow function:
const handleKeyDown = (e) => {
doStuff(e.keyCode);
}
You're using a function expression. In fact, they're called arrow function expressions (since there is not an "arrow function declaration", their nature is to be an expression).
So to be clearer, the arrow function version it's more like:
const handleKeyDown = function(e) {
doStuff(e.keyCode);
}
So the first difference is function declarations vs function expression, ( and therefore the difference between statement and expression: Javascript: difference between a statement and an expression?)
For what concerns the functions, function declarations are hoisted where function expressions are not. That's meaning that a code like that:
handleKeyDown({keyCode: 13});
function handleKeyDown(e) {
doStuff(e.keyCode);
}
Is perfectly valid (and running), where a code like that:
handleKeyDown({keyCode: 13});
const handleKeyDown = (e) => {
doStuff(e.keyCode);
}
Will raise an exception because handleKeyDown
is not initialized yet.
Another difference between function expression and function declarations was that a function expression it was anonymous (unnamed). This is not valid anymore since the js engine is now able to determined the name associated with them:
const foo = () => {}
const bar = function() {}
console.log(foo.name) // "foo"
console.log(bar.name) // "bar"
However, the difference between arrow functions and function expressions, in this case, is that you can override it:
const bar = function baz() {};
console.log(bar.name) // baz
It's important to notice, however, that the name baz
is scoped to the function's body block. It means is not available outside:
const bar = function baz() {
console.log("my name is:", baz.name)
}
console.log(bar.name) // "baz"
bar() // "my name is: baz"
baz() // ReferenceError: bar is not defined
This can be useful when you want to have a recursive function without pollute the outer scope with it. Common examples are setTimeout:
setTimeout(function foo() {
doStuff();
let time = calcNewTime();
setTimeout(foo, time)
}, 1000);
or a RequestAnimationFrame:
requestAnimationFrame(function render() {
updateCoordinates();
renderGameScene();
requestAnimationFrame(render);
});
Said that, the second major difference using arrow functions expressions instead of functions (expression or statement) is the nature of arrow functions itself. MDN has a neat list:
- Does not have its own bindings to this or super, and should not be used as methods.
- Does not have
arguments
(object), or new.target keywords.
- Not suitable for
call
, apply
and bind
methods, which generally rely on establishing a scope.
- Can not be used as constructors.
- Can not use
yield
, within its body.
The reason why they're not suitable for call
, apply
and bind
is because they use the contextual object of the enclosing scope when defined; basically they don't have a this
on their own. It's well seen when you use them as event handlers:
const obj = {
handleClick() {
console.log("I clicked ", this)
}
}
button.addEventListener("click", obj.handleClick);
button.click(); // "HTMLButtonElement"
Where:
const obj = {
handleClick: () => {
console.log("I clicked ", this)
}
}
button.addEventListener("click", obj.handleClick);
button.click(); // "Window", assuming you execute this code in a browser
To sum up, where there is definitely an area of overlapping between every way to define a callable function, each of them has its own characteristics to known in order to use them correctly, and avoid possible nasty bugs.