1. Interaction
  2. JavaScript
  3. Functions and closures

Interaction · JavaScript 05

Functions and closures

A JavaScript function looks like a function in any C-style language. It also ignores how many arguments you pass, can be called above the line that defines it, is an object you can store and hand around, and keeps the variables of the place it was created for as long as it lives. This page goes through those behaviors in order, ending with the arrow syntax you will see in most current code.

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.

function functionName(parameterList) { // statement(s) }

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.

function sayHello() { console.log('Hello there'); } sayHello(); // logs: Hello there

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.

sayHello(); // logs: Hello there function sayHello() { console.log('Hello there'); }

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.

greet(); // throws: ReferenceError let greet = function () { console.log('Hello'); };

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.

console.log(typeof greet); // logs: undefined greet(); // throws: TypeError var greet = function () { console.log('Hello'); };

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.

function sayHello(someName) { if ((someName) && (someName.length > 0)) { console.log(`Hello ${someName}, I'm a function!`); } else { console.log(`Don't be shy. Functions are fun.`); } } sayHello('George'); sayHello(); sayHello(3); sayHello(['George']); // logs: Hello George, I'm a function! // logs: Don't be shy. Functions are fun. // logs: Don't be shy. Functions are fun. // logs: Hello George, I'm a function!

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.

function sayHello(someName) { if (typeof someName === 'string' && someName.length > 0) { console.log(`Hello ${someName}, I'm a function!`); } else { console.log(`Don't be shy. Functions are fun.`); } } sayHello(['George']); // logs: Don't be shy. Functions are fun.

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.

function greet(name = 'stranger') { return `Hello ${name}`; } console.log(greet()); // logs: Hello stranger console.log(greet(undefined)); // logs: Hello stranger console.log(greet(null)); // logs: Hello null

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.

function addThree(num1, num2, num3) { return (num1 + num2 + num3); } let x = 5; let y = 7; let result = addThree(x, y, 11); console.log(result); // logs: 23 result = addThree(x, y, '11'); console.log(result); // logs: 1211

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.

function myMax(num1, num2) { if (num1 >= num2) { return num1; } else { return num2; } } console.log(myMax(3, 8)); // logs: 8

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.

function logOnly() { console.log('working'); } let value = logOnly(); // logs: working console.log(value); // logs: undefined

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.

function fiddle(arg1) { arg1 = 10; console.log(`In fiddle arg1 = ${arg1}`); } let x = 5; console.log(`Before function call x = ${x}`); fiddle(x); console.log(`After function call x = ${x}`); // logs: Before function call x = 5 // logs: In fiddle arg1 = 10 // logs: After function call x = 5

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.

Passing a primitive against passing an arrayOn the left, the primitive x equals 5 is copied into arg1 at the call. arg1 becomes 10 and x stays 5. On the right, x and arg1 are separate variables holding copies of one reference, and both arrows lead to the same array, whose first element is now changed. x sees the change. Assigning a new array to arg1 repoints arg1 only.Primitive: the value is copiedx = 5copied at the callarg1 = 10x is still 5Array: the reference is copiedxarg1['changed', 'second', 'third']x sees the changed contentsarg1 = [] repoints arg1 only; x keeps its array.
The rule is the same on both sides: the variable's value is copied. For an array, that value is a reference.

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.

function swap(a, b) { let temp = a; a = b; b = temp; } let first = 1; let second = 2; swap(first, second); console.log(first, second); // logs: 1 2

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.

// Define x globally let x = 5; function myFunction() { console.log('Entering function'); console.log(`x=${x}`); console.log('Changing x'); x = 7; console.log(`x=${x}`); console.log('Leaving function'); } console.log('Starting Script'); console.log(`x=${x}`); myFunction(); console.log('Returning from function'); console.log(`x=${x}`); console.log('Ending Script'); // logs: Starting Script // logs: x=5 // logs: Entering function // logs: x=5 // logs: Changing x // logs: x=7 // logs: Leaving function // logs: Returning from function // logs: x=7 // logs: Ending Script

A variable declared in a function does not exist outside it. This example ends in an error on purpose.

function myFunction() { let y = 5; // define a local variable console.log(`Within function y=${y}`); // logs: Within function y=5 } myFunction(); // The line below causes an error trying to access y console.log(`After function y=${y}`); // throws: ReferenceError

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.

if (typeof y === 'undefined') { console.log('y is undefined'); // logs: y is undefined } else { console.log(`After function y=${y}`); }

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.

function setCount() { count = 1; } setCount(); console.log(count); // logs: 1

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.

let x = 'As a global I am a string'; function maskDemo() { let x = 5; console.log(`In function maskDemo x=${x}`); } console.log(`Before function call x=${x}`); maskDemo(); console.log(`After function call x=${x}`); // logs: Before function call x=As a global I am a string // logs: In function maskDemo x=5 // logs: After function call x=As a global I am a string
A local x shadowing a global xA large box for the global scope holds let x as a string. Nested inside it, a box for the scope of maskDemo holds let x equals 5. console.log of x inside the nested box finds the inner x, 5. console.log of x outside the nested box can reach only the global x.global scopelet x = 'As a global I am a string';maskDemo() scopelet x = 5;console.log(x);finds the inner x first: 5console.log(x);only the global x is in reach here
A name is looked up from the innermost scope outward, and the first match wins.

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.

let gScore = 10; function bumpScore() { let bumpAmount = 5; gScore = gScore + bumpAmount; } bumpScore(); console.log(gScore); // logs: 15

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.

function foo() { // Since this function is only called inside foo() // in this program, we can nest it inside as an // inner function to clean up our code function _doubleNum(x) { return x * 2; } console.log(_doubleNum(5)); // logs: 10 } foo(); _doubleNum(5); // throws: ReferenceError

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.

function closureDemo() { // This variable will be stored in the closure. // Any time it is modified in later calls that // modification will also be stored. let currNum = 1; // Function to return function _doubleNum() { currNum *= 2; return currNum; } return _doubleNum; } // _doubleNum() has been created and returned, but not run yet let test = closureDemo(); // _doubleNum has run once, currNum === 2 console.log(test()); // logs: 2 // _doubleNum has run twice, currNum === 4 console.log(test()); // logs: 4 // A new call to closureDemo builds a new currNum let other = closureDemo(); console.log(other()); // logs: 2 console.log(test()); // logs: 8

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.

Two closures, each keeping its own scopeTwo rows. In the first, the variable test refers to a _doubleNum function, which refers to the scope of one closureDemo call, holding currNum equal to 4. In the second, the variable other refers to a different _doubleNum function, which refers to the scope of another closureDemo call, holding currNum equal to 2. Both calls to closureDemo have already returned.variablefunction objectscope of a finished calltest_doubleNumcurrNum = 4other_doubleNumcurrNum = 2
As long as something can reach the function, the variables it uses stay alive. Each outer call makes a separate set. This shows the state after 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.

function addClickCounter(button) { let clicks = 0; button.addEventListener('click', function () { clicks = clicks + 1; button.textContent = `Clicked ${clicks}`; }); } function loadUser(userId) { fetch(`/users/${userId}`).then(function (response) { console.log(`User ${userId} answered with ${response.status}`); }); }

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.

for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 0); } // logs: 3 // logs: 3 // logs: 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.

for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 0); } // logs: 0 // logs: 1 // logs: 2
One shared loop variable against one per passOn the left, with var, three functions all point to a single variable i, which equals 3 after the loop, so every call reads 3. On the right, with let, each of the three functions points to its own i, equal to 0, 1 and 2, so each call reads its own value.for (var i ...): one variablefn 0fn 1fn 2i = 3every call reads 3for (let i ...): one per passfn 0fn 1fn 2i = 0i = 1i = 2each call reads its own i
The functions are the same in both loops. What differs is how many variables they have to close over.

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.

for (var i = 0; i < 3; i++) { (function (pass) { setTimeout(function () { console.log(pass); }, 0); })(i); } // logs: 0 // logs: 1 // logs: 2

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.

function sayHello(someName, greeting) { console.log('Hello there'); } console.log(typeof sayHello); // logs: function console.log(sayHello.name); // logs: sayHello console.log(sayHello.length); // logs: 2 sayHello.timesCalled = 0; console.log(sayHello.timesCalled); // logs: 0 let sayHelloAgain = sayHello; sayHelloAgain(); // logs: Hello there

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.

let sayHello = new Function("console.log('Hello there');"); sayHello(); // logs: Hello there let sayHello2 = new Function('msg', "console.log('Hello there ' + msg);"); sayHello2('Thomas'); // logs: Hello there Thomas

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.

function make() { let secret = 1; return new Function('return typeof secret;'); } console.log(make()()); // logs: undefined

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.

function myFunction() { console.log(`Number of parameters defined = ${myFunction.length}`); console.log(`Number of parameters passed = ${arguments.length}`); for (let i = 0; i < arguments.length; i++) { console.log(`Parameter ${i} = ${arguments[i]}`); } } myFunction(33, 858, 404); // logs: Number of parameters defined = 0 // logs: Number of parameters passed = 3 // logs: Parameter 0 = 33 // logs: Parameter 1 = 858 // logs: Parameter 2 = 404

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.

function sumAll() { let total = 0; for (let i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; } console.log(sumAll(3, 5, 3, 5, 3, 2, 6)); // logs: 27

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.

function foo(firstName, lastName, ...clubs) { console.log(`${firstName} ${lastName} is in the following clubs:`); for (let i = 0; i < clubs.length; i++) { console.log(clubs[i]); } } foo('Camdyn', 'Rasque', 'Red Cross', 'Knitting'); // logs: Camdyn Rasque is in the following clubs: // logs: Red Cross // logs: Knitting

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.

function foo(firstName, lastName, ...clubs) { console.log(`${firstName} ${lastName} is in the following clubs:`); for (let i = 0; i < clubs.length; i++) { console.log(clubs[i]); } } let clubs = ['Red Cross', 'Ultimate Frisbee', 'Knitting']; foo('Camdyn', 'Rasque', ...clubs); // logs: Camdyn Rasque is in the following clubs: // logs: Red Cross // logs: Ultimate Frisbee // logs: Knitting

You can also use spread inside an array literal to combine arrays.

let arr1 = ['apples', 'oranges']; let arr2 = ['peaches', 'coconuts']; let fruits = [...arr1, ...arr2]; console.log(fruits.join()); // logs: apples,oranges,peaches,coconuts

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.

// Rest Parameter function listColors(...colors) { colors.forEach(color => console.log(color)); } let warm = ['red', 'orange', 'yellow']; let cool = ['blue', 'green']; // Spread Syntax listColors(...warm, ...cool); // logs: red // logs: orange // logs: yellow // logs: blue // logs: green

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.

function Robot(robotName) { this.name = robotName; this.sayHi = function () { console.log(`Hi my name is ${this.name}`); }; this.sayBye = function () { console.log('Bye!'); }; this.sayAnything = function (msg) { console.log(`${this.name} says ${msg}`); }; // return this; // would that make it more clear? } let fred = new Robot('fred'); fred.sayHi(); fred.sayAnything("I don't know what to say"); fred.sayBye(); // logs: Hi my name is fred // logs: fred says I don't know what to say // logs: Bye!

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.

function factorial(n) { if (n === 0) { return 1; } else { return n * factorial(n - 1); } } console.log(factorial(5)); // logs: 120

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.

function foo(x) { console.log(typeof x); } foo(function bar() {}); let x = foo; x(); console.log(typeof bar); // logs: function // logs: undefined // logs: undefined

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.

let materials = [ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium' ]; let lengths = materials.map(function (material) { return material.length; }); console.log(lengths.join()); // logs: 8,6,7,9 lengths = materials.map((material) => { return material.length; }); console.log(lengths.join()); // logs: 8,6,7,9 lengths = materials.map(material => material.length); console.log(lengths.join()); // logs: 8,6,7,9

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.

function outer() { let inner = () => arguments.length; return inner(1, 2, 3); } console.log(outer('only one')); // logs: 1

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.

let wrong = () => { name: 'Ada' }; let right = () => ({ name: 'Ada' }); console.log(wrong()); // logs: undefined console.log(right().name); // logs: Ada

8. Summary

ConceptKey points
HoistingFunction declarations can be called anywhere in their scope. Function expressions and classes cannot be used before their line runs.
ParametersNo types and no count check. Missing arguments are undefined, extras are ignored. Check input with typeof, and use defaults where they fit.
Return valuesEvery function returns something, and with no return it is undefined.
PassingPrimitives are copied. Objects are passed by sharing: the reference is copied, so contents can change for the caller, while assigning to the parameter cannot.
ScopeNames 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 functionsA function declared inside another is private to it. A leading _ is only a signal.
ClosuresA 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 closuresvar 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 objectsThey 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 argumentslength 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 spreadThe 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 functionsShorter syntax, an implicit return for an expression body, no this or arguments of their own, and no use with new.