Arrow function usage, lexical binding of "this"
Arrow functions with their ultra lean syntax are a natural replacement for their traditional anonymous counterparts. To explicitly invoke an arrow function, as with a regular anonymous function, you would assign it to a variable first:
var weightonmars = earthweight => earthweight * 0.38 weightonmars(100) // returns 38
You can also invoke arrow functions immediately upon their
definition using the Immediately Invoked Function Expressions pattern (IIFEs).
Simply wrap the entire arrow function in parenthesis and call the function using
()
at the end:
( earthweight => earthweight * 0.38 )(100) // returns 38
Here is another example:
var trianglearea = ( (b, h) => { // returns 50 var area = (b * h) / 2; return area; })(20, 5)
The IIFEs pattern is used commonly with regular anonymous functions to instantly create a local scope for all variables and methods contained inside it, so they do not pollute the global namespace.
Calling arrow functions like regular functions can be useful, but the real appeal is in using them in places where anonymous functions normally reign, as call back functions inside event handlers, prebuilt JavaScript methods and inside your own functions. For example, many JavaScript Array methods take an anonymous function as an argument to handle the returned result. Replacing that with an arrow function makes for a much more succinct affair:
// sort array numerically and descending var ages = [25, 8, 7, 41]; var oldfirst = ages.sort( (a, b) => b - a );
// sort array numerically and descending var ages = [25, 8, 7, 41]; var oldfirst = ages.sort( function(a, b){return a - b} );
The first version as you can see is a lot
more compact. Continuing with the array theme, the following capitalizes the
first character of each array element using
Array.map()
and an arrow function as callback:
// capitalize first character of each array element value var names=["george", "Edwin", "jane", "mary"]; var result = names.map( name => { var modified = name.charAt(0).toUpperCase() + name.substr(1); return modified // return name with first letter capitalized })
And finally, just to convince you of the natural pairing between JavaScript Array methods and arrow functions, the following uses both to sort an array of object literals based on a particular object literal property, in this case, the birthday field so the objects are sorted by birthdays (oldest to youngest):
var employees=[ {name:"George", birthday:"March 12, 1978"}, {name:"Edward", birthday:"June 2, 1950"}, {name:"Christine", birthday:"December 20, 2000"}, {name:"Sarah", birthday:"April 30, 1945"} ]; var oldestfirst = employees.sort( (a, b) => { var dateA = new Date(a.birthday); var dateB = new Date(b.birthday); return dateA-dateB //sort by birthdate oldest first });
- Lexical binding of the this
object inside Arrow
Functions
Arrow functions are just like regular anonymous functions, with
one major exception- the behaviour of the this
object inside the
function. In a regular function, the value of "this
" is context based- call the
function inside a link, and "this
" points to the link's object; call it inside
setInterval()
, and "this
" points to the global window object etc. This behaviour
of traditional functions while robust is often unnecessary and a source of
errors for developers not appreciating fully how "this
" works or simply through
carelessness. For example, the following example attempts to call the start()
method of a custom object to increment its counter property by 1 every second,
though it fails due to incorrect assumption of the "this
" object
reference:
var countup = { counter: 0, start:function(){ setInterval(function(){ this.counter++; // **INCORRECT**- doesn't increment countup's counter property }, 1000); } }; countup.start();
In the above the code ths.counter
fails to properly reference
the counter property of the countup
object, though the error may not be so
obvious to spot. The coder either mistakenly or carelessly thought "this
" points
to the countup
object, when in fact it points to the global window
object due to
the context "this
" is being called- inside the global window method
setInterval()
.
The result is a reference to a non existent window.counter
property that will
repeatedly return NaN
when we try to increment it. To properly reference the
countup
object then inside the anonymous function, we should cache a reference
to the correct "this
" object before the context changes to a different one:
var countup = { counter: 0, start:function(){ var countup = this; // cache reference to the countup object setInterval(function(){ countup.counter++; // NOW CORRECT- increments countup's counter property }, 1000); } }; countup.start();
Now the code works as expected.
Arrow functions deviate from regular functions in the way the
"this
" object inside them behaves; the goal here was to simplify and eliminate a
common source of mistakes when referencing "this
" inside an anonymous function.
The "this
" object inside a arrow function is lexically bound, which is just a
fancy way of saying its value is static and determined by the scope "this
" is
defined in. Contrast that with regular functions, where "this
" is dynamic and
based on the context it's called regardless of the scope at the time "this
" was
defined. Lets take the previous example that gave us trouble initially, and see
how changing over to using an arrow function intuitively addresses the problem:
var countup = { counter: 0, start:function(){ setInterval( () => { this.counter++; // Increments countup's counter property }, 1000); } }; countup.start();
Since the "this
" object inside the arrow function
is bound at the time of definition based on the scope it's part of (in this
case, the countup
object), the code above works right out of the
box without worrying about the context in which "this
" is called.
As another example, lets create a constructor function with a method that's
called each time a HTML element is clicked on. Using an arrow function as the
event handler's callback function takes the guess work out of accessing the
constructor function to properly reference its method:
function clickslideshow(imgid, imagearray){ this.imgref = document.getElementById(imgid) this.images = imagearray this.currentimg = 0 this.imgref.addEventListener('click', () => { this.rotateimage() // lexical binding of this }) } clickslideshow.prototype.rotateimage = function(){ this.currentimg++ if ( this.currentimg == this.images.length) // wrap around index value this.currentimg = 0 this.imgref.src = this.images[ this.currentimg ] } new clickslideshow('myimage', ['1.gif', '2.gif', '3.gif'])
The above function changes the image with ID "myimage" to a
different one each time it's clicked on, cycling through a list of image URLs.
When clickslideshow
is instantiated, it attaches a "click"
event to the target image so that when it's clicked on, the object method
rotateimage()
is called to change the image. Because the "this
"
object inside an arrow function is based on the scope it is defined in, in this
case the clickslideshow
object instance, this.rotateimage()
accesses the rotateimage()
method of clickslideshow
instance. If we had used a regular anonymous function instead, "this
"
would have been context based and pointed to the DOM Image Element, not what we
want in most cases when we're invoking "this
" inside custom
objects.
-Other Considerations
Some other considerations before we set you loose with Arrow
Functions. As mentioned in the introduction, Arrow Functions cannot act as
constructor functions (using the keyword new
), so the following
would return an error:
var poorconstruction = x => x-1; new poorconstruction (5); // TypeError: not a constructor
Also, not only is the "this
" object lexically bound
inside Arrow Functions, but it also cannot be modified. This differs from
regular functions, where calling the function using
bind()
,
call()
or apply()
allows you to modify the "this
"
object at the same time, for example:
var evthandler = function(){ alert(this) }; window.addEventListener('click', evthandler.bind(document), false); // alerts document instead of window
Calling
bind()
,
call()
or apply()
on an Arrow Function still
works, though has no effect on its "this
" object:
var evthandler = () => alert(this); window.addEventListener('click', evthandler.bind(document), false); // always alerts window, the scope when evthandler() was defined
And last but not least, Arrow Functions can be identified the same way functions are, such as using instanceof Function or typeof:
var evthandler = () => alert('hi'); evthandler instanceof Function // true typeof evthandler // function
And with that you now have all the info you need to start using Arrow Functions, well, as soon as they become widely supported across modern browsers that is.
- Overview of JavaScript Arrow Functions
- Arrow function usage, lexical binding of "this"
End of Tutorial