Functions As Values
So far, we’ve discussed functions as the primary mechanism of scope in JavaScript. You recall typical function
declaration syntax as follows:
function foo() {
// ..
}
Though it may not seem obvious from that syntax, foo
is basically just a variable in the outer enclosing scope that’s given a reference to the function
being declared. That is, the function
itself is a value, just like 42
or [1,2,3]
would be.
This may sound like a strange concept at first, so take a moment to ponder it. Not only can you pass a value (argument) to a function, but a function itself can be a value that’s assigned to variables, or passed to or returned from other functions.
As such, a function value should be thought of as an expression, much like any other value or expression.
Consider:
var foo = function() {
// ..
};
var x = function bar(){
// ..
};
The first function expression assigned to the foo
variable is called anonymous because it has no name
.
The second function expression is named (bar
), even as a reference to it is also assigned to the x
variable. Named function expressions are generally more preferable, though anonymous function expressions are still extremely common.
For more information, see the Scope & Closures title of this series.
Immediately Invoked Function Expressions (IIFEs)
In the previous snippet, neither of the function expressions are executed — we could if we had included foo()
or x()
, for instance.
There’s another way to execute a function expression, which is typically referred to as an immediately invoked function expression (IIFE):
(function IIFE(){
console.log( "Hello!" );
})();
// "Hello!"
The outer ( .. )
that surrounds the (function IIFE(){ .. })
function expression is just a nuance of JS grammar needed to prevent it from being treated as a normal function declaration.
The final ()
on the end of the expression — the })();
line — is what actually executes the function expression referenced immediately before it.
That may seem strange, but it’s not as foreign as first glance. Consider the similarities between foo
and IIFE
here:
function foo() { .. }
// `foo` function reference expression,
// then `()` executes it
foo();
// `IIFE` function expression,
// then `()` executes it
(function IIFE(){ .. })();
As you can see, listing the (function IIFE(){ .. })
before its executing ()
is essentially the same as including foo
before its executing ()
; in both cases, the function reference is executed with ()
immediately after it.
Because an IIFE is just a function, and functions create variable scope, using an IIFE in this fashion is often used to declare variables that won’t affect the surrounding code outside the IIFE:
var a = 42;
(function IIFE(){
var a = 10;
console.log( a ); // 10
})();
console.log( a ); // 42
IIFEs can also have return values:
var x = (function IIFE(){
return 42;
})();
x; // 42
The 42
value gets return
ed from the IIFE
-named function being executed, and is then assigned to x
.
Closure
Closure is one of the most important, and often least understood, concepts in JavaScript. I won’t cover it in deep detail here, and instead refer you to the Scope & Closures title of this series. But I want to say a few things about it so you understand the general concept. It will be one of the most important techniques in your JS skillset.
You can think of closure as a way to “remember” and continue to access a function’s scope (its variables) even once the function has finished running.
Consider:
function makeAdder(x) {
// parameter `x` is an inner variable
// inner function `add()` uses `x`, so
// it has a "closure" over it
function add(y) {
return y + x;
};
return add;
}
The reference to the inner add(..)
function that gets returned with each call to the outer makeAdder(..)
is able to remember whatever x
value was passed in to makeAdder(..)
. Now, let’s use makeAdder(..)
:
// `plusOne` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusOne = makeAdder( 1 );
// `plusTen` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusTen = makeAdder( 10 );
plusOne( 3 ); // 4 <-- 1 + 3
plusOne( 41 ); // 42 <-- 1 + 41
plusTen( 13 ); // 23 <-- 10 + 13
More on how this code works:
- When we call
makeAdder(1)
, we get back a reference to its inneradd(..)
that remembersx
as1
. We call this function referenceplusOne(..)
. - When we call
makeAdder(10)
, we get back another reference to its inneradd(..)
that remembersx
as10
. We call this function referenceplusTen(..)
. - When we call
plusOne(3)
, it adds3
(its innery
) to the1
(remembered byx
), and we get4
as the result. - When we call
plusTen(13)
, it adds13
(its innery
) to the10
(remembered byx
), and we get23
as the result.
Don’t worry if this seems strange and confusing at first — it can be! It’ll take lots of practice to understand it fully.
But trust me, once you do, it’s one of the most powerful and useful techniques in all of programming. It’s definitely worth the effort to let your brain simmer on closures for a bit. In the next section, we’ll get a little more practice with closure.
Modules
The most common usage of closure in JavaScript is the module pattern. Modules let you define private implementation details (variables, functions) that are hidden from the outside world, as well as a public API that is accessible from the outside.
Consider:
function User(){
var username, password;
function doLogin(user,pw) {
username = user;
password = pw;
// do the rest of the login work
}
var publicAPI = {
login: doLogin
};
return publicAPI;
}
// create a `User` module instance
var fred = User();
fred.login( "fred", "12Battery34!" );
The User()
function serves as an outer scope that holds the variables username
and password
, as well as the inner doLogin()
function; these are all private inner details of this User
module that cannot be accessed from the outside world.
Warning: We are not calling new User()
here, on purpose, despite the fact that probably seems more common to most readers. User()
is just a function, not a class to be instantiated, so it’s just called normally. Using new
would be inappropriate and actually waste resources.
Executing User()
creates an instance of the User
module — a whole new scope is created, and thus a whole new copy of each of these inner variables/functions. We assign this instance to fred
. If we run User()
again, we’d get a new instance entirely separate from fred
.
The inner doLogin()
function has a closure over username
and password
, meaning it will retain its access to them even after the User()
function finishes running.
publicAPI
is an object with one property/method on it, login
, which is a reference to the inner doLogin()
function. When we return publicAPI
from User()
, it becomes the instance we call fred
.
At this point, the outer User()
function has finished executing. Normally, you’d think the inner variables like username
and password
have gone away. But here they have not, because there’s a closure in the login()
function keeping them alive.
That’s why we can call fred.login(..)
— the same as calling the inner doLogin(..)
— and it can still access username
and password
inner variables.
There’s a good chance that with just this brief glimpse at closure and the module pattern, some of it is still a bit confusing. That’s OK! It takes some work to wrap your brain around it.
From here, go read the Scope & Closures title of this series for a much more in-depth exploration.