Three mildly interesting things about JavaScript functions
Published on
Last modified on
I was reading up on functions in JavaScript (from the book Professional JavaScript for Web Developers, 3rd edition) and felt the need to capture a few things that stood out to me. Below I will explain:
- the distinctions between
apply
andcall
; - the value of returning functions from functions; and
- writing "safe" recursions with function expressions.
apply
and call
Both of these functions allow a function to be executed with a custom this
value.
In a function, the this
keyword is equal to the execution context of the
function, which is the object that calls the function. This means that when a
function is run in the global scope this
equals window
—below is an example
of this.
var color = 'blue';
function sayColor() { alert(this.color); }
sayColor(); // 'blue';
If the same function lives within an object, then the function is run in the
scope of that object meaning that this
will refer to the object.
var obj = {
color: 'red',
sayColor: function() {
alert(this.color);
}
};
obj.sayColor(); // 'red'
apply
and call
allow us to specify our own value for this
, meaning we can
get a lot of control over how our functions perform. The only difference between
the two comes down to how they accept arguments.
apply
accepts two arguments; the first being the execution context, i.e.
this
, and the second being an array of arguments passed to the function.
var color = 'blue';
var obj = { color: 'red' };
function sayColor(message) { alert(message + this.color); }
sayColor('The color is: '); // 'The color is: blue'
sayColor.apply(obj, ['The color on "obj" is: ']); // 'The color on "obj" is: red'
call
accepts an endless number of arguments; the first being the execution
context and the arguments following being passed to the function.
var color = 'blue';
var object = { color: 'red' };
function sayColor(message) { alert(message + this.color); }
sayColor('The color is: '); // 'The color is: blue'
sayColor.call(object, 'The color on object is: '); // 'The color is: red'
Returning functions from functions
It is easy to forget or take for granted that JavaScript functions can be treated like variables. Because of this, it's possible to not only return variables from functions, but to return functions as well.
A good example of this is from Professional JavaScript for Web
Developers
relating to the sort
method on Arrays which is demonstrated below.
function createComparisionFunction(property) {
return function(obj1, obj2) {
var value1 = obj1[property];
var value2 = obj2[property];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}
var data = [{
name: 'A',
number: 1
},{
name: 'B',
number: 2
}];
alert(data[0].name); // 'A'
data.sort(createComparisionFunction('number'));
alert(data[0].name); // 'B'
"Safe" recursion with function expressions
A common way to achieve recursion in JavaScript is demonstrated below:
function alertArray(item) {
for (var i = 0, length = item.length; i < length; i++) {
if (Array.isArray(item[i])) {
return alertArray(item[i]);
} else {
alert(item[i]);
}
}
}
alertArray([1, 2, 3, [4, 5]]); // '1', '2', '3', '4', '5'
This all seems well and good, but it is assumed that the alertArray
function
will always be available. The below example demonstrates why this can be a
problem:
function alertArray(item) {
for (var i = 0, length = item.length; i < length; i++) {
if (Array.isArray(item[i])) {
return alertArray(item[i]);
} else {
alert(item[i]);
}
}
}
var shortcut = alertArray; // Remember, functions can be stored as variables
alertArray = null;
shortcut([1, 2, 3, [4, 5]]); // '1', '2', '3', and then an undefined error
To get around this, named function expressions can be used within an Immediately-Invoked Function Expression (IIFE). A function expression can be described as a function stored within a variable (i.e. an expression).
var alertArray = (function f(item) {
for (var i = 0, length = item.length; i < length; i++) {
if (Array.isArray(item[i])) {
return f(item[i]);
} else {
alert(item[i]);
}
}
});
var shortcut = alertArray; // Remember, functions can be stored as variables
alertArray = null;
shortcut([1, 2, 3, [4, 5]]); // '1', '2', '3', '4', '5'
It should be noted that function expressions are treated like variables and, as
such, cannot be accessed globally due to the order of execution. That means that
alertArray
cannot be called before it is defined.
alertArray([1, 2, 3, [4, 5]]); // "Uncaught TypeError: alertArray is not a function..."
var alertArray = (function f(item) {
for (var i = 0, length = item.length; i < length; i++) {
if (Array.isArray(item[i])) {
return f(item[i]);
} else {
alert(item[i]);
}
}
});