Samples state what they print, and those outputs were checked by running them. Closures are the section to slow down for, since callbacks, event handlers and most of the code on later pages depend on them. Read each playground and predict its output before you change it.
1. Declaring and calling a function
A function declaration has the keyword function, a name, a parameter list and a body. The name must be a well-formed JavaScript identifier, following the rules in What a name tells you. The parameter list is a set of identifiers separated by commas, and it may be empty.
Here is a function with no parameters. The parentheses on the last line are what invoke it. Without them, sayHello is a reference to the function and nothing runs.
Calling before defining
A function declaration is hoisted, which means it can be called anywhere in its scope, including above the line where it appears. Before the script's first statement runs, the engine has already created the function. Many programmers use this to put the main flow at the top of a file and the helpers below it.
A function expression does not behave this way, because the function is a value assigned to a variable, and the assignment happens when that line runs. What you get before that line depends on the variable. A let cannot be touched before its declaration, so the call throws a ReferenceError.
A var is hoisted without its value, as page 03 showed. The name exists and holds undefined, so the call fails with a different error, because undefined is not something you can call.
Classes follow the let rule. Writing new Robot() above class Robot {} in the same scope throws a ReferenceError.
Older advice told you to define every function in the document's <head> so that it would exist before anything called it. Hoisting does not require that, but it also stops at the edge of a script. Classic scripts run one after another, so code in one script can call functions from a script that has already run and cannot call functions from one that has not run yet. Getting script into a page covers how to control that order.
2. Parameters and return values
Nothing checks the arguments
Parameters have no declared types, and the language does not compare the number of arguments with the number of parameters. A parameter with no matching argument is undefined, and the call goes ahead. So a function that cares about its input has to check it, as this one does.
The first two calls behave as intended. The call with 3 reaches the right branch by accident. The number is truthy, it has no length, and undefined > 0 is false. The last call passes the check, because an array has a length too, and the template string turns it into text. To say what you mean, use typeof.
A parameter can also declare a default, which applies when the argument is missing or is undefined. It does nothing about an argument of the wrong type, and null does not trigger it.
Weak typing reaches through
With several parameters, the function below looks like arithmetic. Nothing guarantees that it is, because + decides what to do from the values it receives at run time. Pass one string and the sum becomes concatenation. Types and coercion explains the rule.
The second result comes from reading left to right. 5 + 7 is the number 12, and 12 + '11' is the string '1211'.
Every function returns something
A function may have several return statements, and the first one reached ends the call.
Functions always return some value, whether or not a return is written. With no return, or with a bare return;, the value is undefined. You will notice this in Node's prompt and in the browser's console, which print undefined after you call a function that returns nothing. Keep in mind the line-break trap from page 03: a value on the line after return is never returned.
3. How arguments are passed
Primitive values are passed by value, meaning a copy of the data is made and given to the function. Changing the parameter changes the copy.
Objects and arrays seem to work differently. Change an element through the parameter and the caller sees the change. This is often described as pass by reference, which holds for changing contents and fails for assignment. The name for what JavaScript does is pass by sharing. The variable holds a reference to the array, and the call copies that reference. Both names now lead to one array, so a change to its contents is visible through either. The two names themselves stay separate, so assigning a new array to the parameter moves only the parameter. The caller's variable keeps the array it had.
The playground shows both results. fiddle changes the shared array's contents, and replace assigns a new array to its parameter, which the caller never sees.
A swap function is a quick test of a language's passing rules. With true reference parameters, as with int& in C++, a function can exchange the caller's two variables. In JavaScript it cannot, because it only ever has copies. Java object references and Python names behave the same way, so this model may already be familiar.
Objects and arrays, the next page, returns to references when it compares objects and copies them.
4. Global and local variables
A global variable is known throughout the application, while a local variable is limited to the part of the program where it is defined. In JavaScript, a variable defined outside any function in a classic script has traditionally been global. A var inside a function is local to that function. let and const narrow this to the block, as page 03 explained, and in general you should use let.
A function can read and change a global from inside. In the sample below, x has no declaration inside myFunction, so the name refers to the global, and the change is still there after the function returns.
A variable declared in a function does not exist outside it. This example ends in an error on purpose.
You can guard against that error with an if statement, and typeof is the safe way to write one, because it accepts a name that was never declared. You will also see if (window.y). It avoids the error, but it asks a narrower question: whether the global object has a truthy property called y. It reports nothing about local variables, and it misses a global declared with let, which does not become a property of window.
Forgetting a declaration has a different result, and an easy one to miss. In a classic script that is not in strict mode, assigning to a name that was never declared creates a global variable, so a typo or a missing let inside a function quietly leaks out of it. In strict mode and in modules, the same assignment throws a ReferenceError. Programming in the large explains why shared globals cause trouble.
Shadowing and readability
A local variable can have the same name as a global. The local one then shadows, or masks, the global for that whole scope, so the name means something different inside the function than it does outside. The global is untouched.
Be careful with local and global variables of the same name, because similar names read badly. You might use a prefix such as g for a global, or the stem of the function a variable is local to.
JavaScript's weak typing and scope rules can both hurt readability, and naming conventions can offset some of that. Names such as strMyName, numMyBalance and boolLikeJS use the Hungarian notation discussed in Seeing the type in the name. This course's position is that such prefixes are a good idea given the nature of JavaScript and can help you debug. That section also sets out their costs.
Local functions
A function can be declared inside another function. If a helper is called only inside foo, nesting it there cleans up the code, because the helper's name is then local to foo and cannot collide with anything outside. The leading underscore on _doubleNum marks it as private by agreement, and the nesting is what enforces it.
5. Closures
A closure is an inner function that retains the variables of the place where it was created, even when it is called from somewhere else and that outer function has long since returned. Other languages have closures too. In JavaScript you meet them constantly, so take the time to get this section right.
In the sample below, closureDemo creates a local variable and an inner function, then returns the function without running it. By the usual expectation of a local variable, currNum should be gone once closureDemo returns. It stays, because the returned function still refers to it.
The last two lines show that each call to closureDemo creates its own currNum. The closure holds the variable itself, not a snapshot of its value, which is why a change made by one call to test is there for the next one.
test() and other() have each run; the sample's final test() takes test's copy to 8.The classic small example is a counter. Each call to makeCounter gives you a function with a private count. No other code can read or reset that variable, because the only route to it is through the function. The playground shows two counters running independently, and shows that count is not a property you can reach from outside.
Where you will use them
Closures were fundamental to Ajax programming, and they remain fundamental to anything that runs later. You hand a function to the browser to call when a click arrives, a timer expires or a response comes back. By that time the code that created the function has finished, and the function reaches that code's variables through its closure.
addClickCounter has returned long before the first click, yet its clicks and button are still there for the handler, and each button gets its own count. Hello, events covers the handlers, Client-Side Communication covers the requests, and Errors and async covers the promise that fetch returns.
Private state like this is also how JavaScript hid module internals before it had a module system, as Programming in the large shows with the revealing module pattern.
Closures have a cost. Everything a closure uses stays in memory for as long as the closure can be reached, and a function created inside another function is a new object on every call. MDN advises against creating functions inside functions when no closure is needed. A handler that is never removed can hold variables alive for the life of the page, which Objects and arrays takes up under garbage collection and leaks. For another explanation, read MDN's guide to closures as well.
The loop-variable trap
Because a closure holds the variable and not its value, one bug caught JavaScript programmers for years. The loop below creates three functions, and each is meant to remember its own pass through the loop. A var has function scope, so the loop has a single i and all three functions close over it. They run after the loop has finished, when i is 3.
A let in the head of a for loop gets special treatment, which page 03 mentioned. Each pass has a new binding of the variable, which starts with the value from the end of the previous pass. So each closure holds a different i.
In the playground, the functions are stored in an array and called after the loop, and the second log line checks whether i outlived the loop. It runs only when you press its Run button, and it saves no edits, because deleting a loop's update as you type would otherwise hang it. Use the button and not the keyboard shortcut here. The button rebuilds the frame, while the shortcut reruns in the same frame, where a top-level var from the previous run survives and would make typeof i report a stale number.
The per-pass behavior belongs to the variable declared in the loop's head. A let declared above the loop and only assigned in its head is one variable, and it gives the same 3,3,3 as var. A let declared inside a loop's body, in any kind of loop, is new on each pass. for/of and for/in with let also give each pass a new variable.
You will see an older fix in code written before let existed. It wraps the body in a function that is called on the spot, so that each pass has a new function scope and a parameter holding a copy of the value. Programming in the large returns to this form, the immediately invoked function expression, as history.
6. Functions as objects
Functions are objects, and can be created and accessed as such. A function has properties, can have more added to it, and can be assigned to another variable. typeof reports 'function' for them, but that is a label for a callable object and not a separate kind of value. Assigning a function to a new name reuses it, since both names lead to the same function.
The Function constructor
Because functions are objects, the Function constructor can make one. The last argument is the body, as a string, and any arguments before it name the parameters. Calling it with or without new does the same thing.
The general syntax is new Function('argument 1', 'argument n', 'statements for function body'). You should recognize it and rarely write it, for reasons that follow from how it works. Its body is a string parsed at the moment of the call, so it costs more than a function written in the source. Text from a user that reaches it can run as code, which makes it a cross-site scripting risk, and a Content Security Policy with a script-src directive blocks it unless told otherwise. A function made this way also forms no closure over the code around it. It runs in the global scope only.
length and arguments
As a function is an object, you can read its length to find how many parameters it declares. To see what a particular call actually passed, use the arguments object, which is available inside any function that is not an arrow function.
Older code and older books often write myFunction.arguments. MDN marks that property as deprecated and non-standard, and reading it throws a TypeError in strict mode, so use the local arguments variable.
With arguments and length you can write more adaptive functions that take variable arguments.
The object is array-like, with a length and numbered entries, but it is not an array, so it has no map or forEach. The playground compares what functions declare with what they receive. Missing arguments arrive as undefined and extra ones are ignored by the parameters, though arguments still holds them. length stops at the first parameter with a default and leaves out a rest parameter.
Rest parameters
In some cases it is useful for a function to accept an unknown quantity of arguments, such as when summing a list of numbers. Current code uses the rest parameter for this, not arguments. It is written as three dots followed by a variable name, and that variable holds an array of the rest of the arguments. It can sit alongside explicitly defined parameters, but it must always be the last parameter. Anything after it is a SyntaxError.
Unlike arguments, clubs is an actual array, so clubs.map and the other array methods work on it directly. It also holds only the arguments that were left over.
Spread syntax
Spread is the reverse of rest. Sometimes you have an array, of a length you may or may not know, and want each index to become its own argument in a call without writing them out. Spread uses the same three dots, as part of the function call and not the definition.
You can also use spread inside an array literal to combine arrays.
The two can work together, gathering at the definition and spreading at the call. The => below is an arrow function, which the next section covers.
Objects and arrays shows spread used to copy objects.
7. Anonymous, recursive and arrow functions
Literal and anonymous functions
A function does not need a name. A function literal can be written wherever a value can go, and the usual case is assigning it to a variable or a property, or passing it to another function. In this constructor, each method is an anonymous function assigned to a property of the new object. The commented-out line asks whether return this; would make the code clearer. It would not change the result, because new returns the new object anyway.
this and new get full treatment in the next two pages. Objects and arrays explains how this depends on the way a function is called, and Prototypes and classes explains constructors, including why these methods are usually shared on a prototype instead of being created again for each robot.
Recursive functions
JavaScript supports recursive functions that call themselves, and everybody's favorite is factorial, n! = n * (n - 1) * (n - 2) * ... * 1.
Recursion needs a base case the calls can reach. factorial(-1) counts down past 0 and never stops, until the engine runs out of stack and throws. Chrome and Safari report a RangeError with the message "Maximum call stack size exceeded", and Firefox reports an InternalError with the message "too much recursion".
Arrow functions
Functions are first class in JavaScript, so they can be used as expressions: passed to a function, assigned to a variable and called through it. In the sample, bar is a named function expression. Its name is visible only inside itself, which helps recursion and stack traces and adds no name to the surrounding scope.
Some people find this inlining of functions messy, so ES6, the 2015 edition of the language, introduced arrow functions. The sample below does the same job three ways, going from a function literal to the shortest arrow. With one parameter the parentheses are optional. With an expression as the body, the braces and return can both go, and the expression's value is returned.
Arrow functions also behave differently from other functions, so they are not always a drop-in replacement. They have no this or arguments of their own, and use those of the code around them. This makes them a good fit for callbacks inside a method and a poor fit for the methods themselves, a difference Objects and arrays demonstrates. They cannot be used as constructors, so calling one with new throws a TypeError. There can be no line break between the parameters and the arrow. MDN's page on arrow functions has the full list.
Braces after the arrow always begin a function body, so an object literal there is read as a block, and the function returns undefined. Parentheses around the literal fix it.
8. Summary
| Concept | Key points |
|---|---|
| Hoisting | Function declarations can be called anywhere in their scope. Function expressions and classes cannot be used before their line runs. |
| Parameters | No types and no count check. Missing arguments are undefined, extras are ignored. Check input with typeof, and use defaults where they fit. |
| Return values | Every function returns something, and with no return it is undefined. |
| Passing | Primitives are copied. Objects are passed by sharing: the reference is copied, so contents can change for the caller, while assigning to the parameter cannot. |
| Scope | Names are found from the innermost scope outward. A local of the same name shadows a global. A missing declaration makes a global in a non-strict classic script. |
| Local functions | A function declared inside another is private to it. A leading _ is only a signal. |
| Closures | A function keeps the variables of the place it was created, as variables and not as copies of their values. Each call of the outer function makes a new set. |
| Loop closures | var gives one variable for the whole loop, so every closure sees the last value. let in the loop's head gives one per pass. |
| Functions as objects | They have properties and can be assigned and passed. new Function parses a string, sees only global scope and is rarely the right tool. |
| length and arguments | length is what was declared, stopping at a default and leaving out rest. arguments is what was passed and is array-like. myFunction.arguments is deprecated. |
| Rest and spread | The same three dots. Rest gathers the leftover arguments into an array and comes last. Spread expands an array into a call or an array literal. |
| Arrow functions | Shorter syntax, an implicit return for an expression body, no this or arguments of their own, and no use with new. |
