The Web Security Topics Series
Functions
Anonymous Functions
[...]
You can also think of functions in programming languages in a more abstract way, as an approximation to the mathematical concept of a function as a mapping that turns input values into output values. In mathematics we often distinguish between the output of a function f for some input x, written f (x) and the function itself, comprising the correspondence between all possible inputs and outputs, just written f. In programming languages, the correspondence between a function’s inputs and outputs – that is, its arguments and the result it returns – is the consequence of some computation taking place. You can imagine the function as being a sort of mechanism, whose operation is defined in the function body. Hence, we can distinguish between the result a function returns, f(x)
, and the function itself, f
, which is this mechanism. As we remarked before, JavaScript functions do not always return results or accept arguments, so this mechanism does not communicate with its environment purely through its inputs and outputs. As functional languages in general and Lisp in particular have demonstrated over many years, the ability to manipulate functions as objects in their own right makes it possible to synthesize almost any other language feature that you could desire. JavaScript’s support for “first class” functions provides it with the means of overcoming the shortcomings of its other features.
In the paragraphs above we have referred to a function by giving it a name. Since the mechanism defining the computation carried out by the function has a written form (the argument list and function body), it does not necessarily have to be given a name when we need to refer to it or use it. Anonymous functions are commonly used in JavaScript programs. For example
function (n) {
return typeof n != 'number' || n%1 != 0? '':
n < 0? '-' + insertCommasInto(-n): insertCommasInto(n)
}
is an expression, whose value is the function that carries out the operation of converting integers into strings with commas between groups of three digits. Such an expression may be used in several ways, the simplest being as the right-hand side of an assignment statement. This leads to the alternative syntax for our collection of functions shown in Listing 2.
Listing 2
var threeDigits = function (n) {
var s = n.toString()
while (s.length < 3)
s = '0' + s
return s
}
var insertCommasInto = function (n) {
if (n < 1000)
return n.toString()
else
return insertCommasInto(Math.floor(n/1000)) + ',' +
threeDigits(n%1000)
}
var insertCommas = function (n) {
return typeof n != 'number' || n%1 != 0? '':
n < 0? '-' + insertCommasInto(-n): insertCommasInto(n)
}
Note that the name of the variable to which a function expression is assigned is in scope within the function’s body, so in the second function we can use the name insertCommasInto
to call the function recursively.
Suppose, though, that we find ourselves having to perform a lot of transformations, all of which follow a pattern in which we reject any argument that isn’t an integer, and otherwise apply some function that only accepts non-negative arguments. If the argument is negative, we invert it and insert a minus sign in front of the result returned by the function. We would capture the common pattern by abstracting the function out as a parameter, and then redefining our particular function by passing a function as the first argument to applyToInteger
as in Listing 3.
Listing 3
var applyToInteger = function (f, n) {
return typeof n != 'number' || n%1 != 0? '':
n < 0? '-' + f(-n): f(n)
}
var insertCommas = function (n) {
return applyToInteger(insertCommasInto, n)
}
Alternatively, we might prefer to eliminate the named function insertCommasInto
entirely. One of the things you can do with a function expression is pass it as an argument to another function – in fact, this is one of the most common idiomatic uses of anonymous functions – so we might try amending the code as shown in Listing 4.
Listing 4
var insertCommas = function (n) {
return applyToInteger(function (n) {
if (n < 1000)
return n.toString()
else
return insertCommasInto(Math.floor(n/1000)) + ',' +
threeDigits(n%1000)
}, n)
}
You can see that this won’t work, though, because insertCommasInto
is no longer defined anywhere. To enable anonymous functions to be called recursively, JavaScript allows you to include a name in a function expression, just as you would in a function declaration. Our example could therefore be written as in Listing 5.
Listing 5
var insertCommas = function (n) {
return applyToInteger(function insertCommasInto(n) {
if (n < 1000)
return n.toString()
else
return insertCommasInto(Math.floor(n/1000)) + ',' +
threeDigits(n%1000)
}, n)
}
A function expression that includes a name in this way is called a “named anonymous function”, even though this is obviously an oxymoron. Despite the similarity to our earlier function declaration, the name of a named anonymous function is only in scope in the function’s body. It cannot be used to call the function except recursively.
If the anonymous function wasn’t recursive there would have been no problem in passing a purely anonymous function as an argument to another function. In Node.js, jQuery and other frameworks and libraries, it is common for programmers to pass an anonymous function whenever an API call requires a callback as an argument. You will see many examples of this later in this book.
[...]
Many JavaScript programmers prefer to use function expressions assigned to variables to name their functions in all contexts, for consistency and to emphasize the independence of the function from its name.
Closures
Function expressions (and declarations) can appear inside the body of other functions. Consider the function threeDigits
in our example. It is only used within insertCommasInto
, so it could – and arguably should – be defined inside it, so that it is out of scope elsewhere in the program. For example, we could rewrite our earlier program to hide threeDigits
, as shown in Listing 6.
Listing 6
var insertCommasInto = function (n) {
var threeDigits = function (n) {
var s = n.toString()
while (s.length < 3)
s = '0' + s
return s
}
if (n < 1000)
return n.toString()
else
return insertCommasInto(Math.floor(n/1000)) + ',' +
threeDigits(n%1000)
}
Functions can be nested inside anonymous functions in any context, but excessive use of nesting inside anonymous function arguments can become hard to read.
In this case, the nesting does nothing except confine the inner function to the scope where it is needed, but more complicated cases lead to more interesting patterns of use.
Suppose, for instance, that we wanted a more general function than insertCommas
, which could insert an arbitrary character between the groups of three digits. For example, in most European countries a point is used for this purpose instead of the comma favoured by English speakers. We would therefore like to have a function insertSeparators
, which will take two arguments, a character to use as the separator and the value to be formatted. The value of insertSeparators(',', 67544)
would be '67,544'
while insertSeparators('.', 67544)
would be '67.544'
. The definition shown in Listing 7 can be used.
Listing 7
var insertSeparators = function (separator, n) {
return applyToInteger(function insertSeparatorsInto(n) {
var threeDigits = function (n) {
var s = n.toString()
while (s.length < 3)
s = '0' + s
return s
}
if (n < 1000)
return n.toString()
else
return insertSeparatorsInto(Math.floor(n/1000)) +
separator +
threeDigits(n%1000)
}, n)
}
The significant feature here is that the formal parameter separator
is used inside the body of the “anonymous” function insertSeparatorsInto
. You will only find this worth remarking upon if you are used to programming in C-like languages where referring to free variables – that is, variables defined in an enclosing scope – is outlawed. In JavaScript, the scope of a variable – the region of the program in which it may be referred to – includes any nested function definitions. Here, the consequence is straightforward: the value of separator
does not have to be passed into insertSeparatorsInto
as an argument, because it is already in scope. In other situations, the possibility of referring to free variables leads to some powerful techniques.
For example, as well as being passed to another function, functions may be returned as the result of other functions. Functions that return functions are often used to generate specialized versions of a general operation. This technique can be used to generate functions for inserting commas and points into numbers, as shown in Listing 8.
Listing 8
var insertSeparators = function (separator) {
return function(n) {
return applyToInteger(function insertSeparatorsInto(n) {
var threeDigits = function (n) {
var s = n.toString()
while (s.length < 3)
s = '0' + s
return s
}
if (n < 1000)
return n.toString()
else
return insertSeparatorsInto(Math.floor(n/1000)) +
separator +
threeDigits(n%1000)
}, n)
}
}
var insertCommas = insertSeparators(',')
var insertPoints = insertSeparators('.')
The logic is the same as before, but instead of taking the number to be formatted as its first argument, insertSeparators
only takes the value of the separator, and instead of performing the formatting operation and returning the formatted string, it returns a function which will take a number and format it, using the separator supplied to insertSeparators
. Thus, the value of insertSeparators(',')
is a function which inserts commas into numbers, and insertSeparators('.')
is one that inserts points. It isn’t even necessary to assign these functions to variables, as we have at the end of Listing 8: insertSeparators(',')(-1202405006)
is a valid expression, which evaluates to '-1,202,405,006'
.
A function that can be manipulated as a value and which refers to free variables is called a closure. Notice that in the example in Listing 8, both the variable separator
and the locally defined function threeDigits
are part of the closure returned from insertSeparators
.
End of Extract
[Extracted from Javascript on the Server Using Node.js and Express by Nigel Chapman and Jenny Chapman]
For an alternative description of Javascript functions, see the blog post Functions Explained by Mark Daggett.