Let's decompose a "traditional anonymous function" down to the simplest "arrow function" step-by-step:
Note: Each step along the way is a valid "arrow function".
// Traditional Anonymous Function
function (a){
return a + 100;
}
// Arrow Function Break Down
// 1. Remove the word "function" and place arrow between the argument and opening body bracket
(a) => {
return a + 100;
}
// 2. Remove the body braces and word "return" -- the return is implied.
(a) => a + 100;
// 3. Remove the argument parentheses
a => a + 100;
Copy to Clipboard
The { braces } and ( parentheses ) and "return" are required in some cases.
For example, if you have multiple arguments or no arguments, you'll need to re-introduce parentheses around the arguments:
// Traditional Anonymous Function
function (a, b){
return a + b + 100;
}
// Arrow Function
(a, b) => a + b + 100;
// Traditional Anonymous Function (no arguments)
let a = 4;
let b = 2;
function (){
return a + b + 100;
}
// Arrow Function (no arguments)
let a = 4;
let b = 2;
() => a + b + 100;
Copy to Clipboard
Likewise, if the body requires additional lines of processing, you'll need to re-introduce braces PLUS the "return" (arrow functions do not magically guess what or when you want to "return"):
// Traditional Anonymous Function
function (a, b){
let chuck = 42;
return a + b + chuck;
}
// Arrow Function
(a, b) => {
let chuck = 42;
return a + b + chuck;
}
Copy to Clipboard
And finally, for named functions we treat arrow expressions like variables:
// Traditional Function
function bob (a){
return a + 100;
}
// Arrow Function
let bob = a => a + 100;
As stated previously, arrow function expressions are best suited for non-method functions. Let's see what happens when we try to use them as methods:
'use strict';
var obj = { // does not create a new scope
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log(this.i, this);
}
}
obj.b(); // prints undefined, Window {...} (or the global object)
obj.c(); // prints 10, Object {...}
Copy to Clipboard
Arrow functions do not have their own this. Another example involving Object.defineProperty():
The call, apply and bindmethods are NOT suitable for Arrow functions -- as they were designed to allow methods to execute within different scopes -- because Arrow functions establish "this" based on the scope the Arrow function is defined within.
For example call, apply and bindwork as expected with Traditional functions, because we establish the scope for each of the methods:
// ----------------------
// Traditional Example
// ----------------------
// A simplistic object with its very own "this".
var obj = {
num: 100
}
// Setting "num" on window to show how it is NOT used.
window.num = 2020; // yikes!
// A simple traditional function to operate on "this"
var add = function (a, b, c) {
return this.num + a + b + c;
}
// call
var result = add.call(obj, 1, 2, 3) // establishing the scope as "obj"
console.log(result) // result 106
// apply
const arr = [1, 2, 3]
var result = add.apply(obj, arr) // establishing the scope as "obj"
console.log(result) // result 106
// bind
var result = add.bind(obj) // establishing the scope as "obj"
console.log(result(1, 2, 3)) // result 106
Copy to Clipboard
With Arrow functions, since our add function is essentially created on the window (global) scope, it will assume this is the window.
// ----------------------
// Arrow Example
// ----------------------
// A simplistic object with its very own "this".
var obj = {
num: 100
}
// Setting "num" on window to show how it gets picked up.
window.num = 2020; // yikes!
// Arrow Function
var add = (a, b, c) => this.num + a + b + c;
// call
console.log(add.call(obj, 1, 2, 3)) // result 2026
// apply
const arr = [1, 2, 3]
console.log(add.apply(obj, arr)) // result 2026
// bind
const bound = add.bind(obj)
console.log(bound(1, 2, 3)) // result 2026
Copy to Clipboard
Perhaps the greatest benefit of using Arrow functions is with DOM-level methods (setTimeout, setInterval, addEventListener) that usually required some kind of closure, call, apply or bind to ensure the function executed in the proper scope.
Traditional Example:
var obj = {
count : 10,
doSomethingLater : function (){
setTimeout(function(){ // the function executes on the window scope
this.count++;
console.log(this.count);
}, 300);
}
}
obj.doSomethingLater(); // console prints "NaN", because the property "count" is not in the window scope.
Copy to Clipboard
Arrow Example:
var obj = {
count : 10,
doSomethingLater : function(){
// The traditional function binds "this" to the "obj" context.
setTimeout( () => {
// Since the arrow function doesn't have its own binding and
// setTimeout (as a function call) doesn't create a binding
// itself, the "obj" context of the traditional function will
// be used within.
this.count++;
console.log(this.count);
}, 300);
}
}
obj.doSomethingLater();
The yield keyword may not be used in an arrow function's body (except when permitted within functions further nested within it). As a consequence, arrow functions cannot be used as generators.
Arrow functions can have either a "concise body" or the usual "block body".
In a concise body, only an expression is specified, which becomes the implicit return value. In a block body, you must use an explicit return statement.
var func = x => x * x;
// concise body syntax, implied "return"
var func = (x, y) => { return x + y; };
// with block body, explicit "return" needed