Skip to main content

Miscellaneous function topics

Rest parameters and spread syntax

How do we make a function take in an arbitrary number of arguments? Simple we use the ... rest operator in the function header.

function sum(...args) {
	let sum = 0;
	for (let arg of args) sum += arg;
    return sum;
}

When you prefix a parameter with ... you can pass in an arbitrary number of arguments into the function and it will all be collected into an array that's stored into the parameter args.

You can also mix rest parameters with normal parameters like so

function showName(firstName, lastName, ...extra) {
	console.log(firstName, lastName);
    console.log(extra);
}

showName("Ricky", "Lu", 30, 40, 50);

In this case, the first two parameter will be stored into firstName and lastName respectively, and any further parameter that you pass in will be stored into extra as an array of parameters.

Keep in mind that the rest parameter must be at the end when you use it. You cannot have a function like so
function f(arg1, ...rest, arg2) this will be a syntax error

Spread Syntax

On the other hand, you can also unpack the values from an array or any iterable into a function. For example:

// instead of writing
let arr = [3, 5, 1];

console.log(Math.max(arr[0], arr[1], arr[2])); // Too long, and if there are hundreds of values, we are not gonna do this

console.log(Math.max(...arr)); // Much better, this unpacks the arr

This is much like the opposite of doing the reverse of rest parameter. We want to spread the values from an array into the parameter of a function.

When ... is used in a function call it expands the iterable object into the list of arguments.

You can spread multiple iterable into a function

let arr1 = [1];
let arr2 = [2, 3, 4, 5, 6];

function foo(a, ...rest) {
	console.log(a, rest);
}

foo(...arr1, ...ar2); // prints out "1 [2, 3, 4, 5, 6]"
Merging array

You can also use the spread syntax to merge arrays together, instead of using arr.concat

let arr = [3, 5, 1];
let arr2 = [8, 9, 15];

let merged = [...arr, ...arr2]; // becomes [3, 5, 1, 8, 9, 15];

Lexical environment

Okay this is gonna just be a brief summary of what lexical environment is.

Every JavaScript script have something called a lexical environment object that is internal. It consists of: Environment record (all of the local variables and methods), and a reference to the outer lexical environment.

When you declare a global variable or global function it is stored in the global lexical environment that is associated with the whole script.

image.png

Here in this example above, phrase is a global variable and hence its record is stored in the global lexical environment. The global lexical environment does not have reference to a outer lexical environment because it is the most outer one, hence it is just null.

Variable declaration and function declaration

When you declare a variable, it is available in the lexical environment immediately but the value it has is uninitialized from the beginning, and as the script execute to the point where it is initialize, that value is updated.

On the other hand, for function declaration (not function expression or arrow functions), they are available immediately become ready-to-use functions. It doesn't have to wait until the line the function becomes defined to be initialized. This is why we are able to call the function before the function declaration!

Inner and outer lexical environment

When you invoke a function it creates a new lexical environment to store the local variables and parameter of the function call. Every new function invocation will create a new lexical environment!

image.png

Here in this example, invoking say creates a new lexical environment and has the parameter information in it. It has the reference to the outer lexical environment, which in this case is the global lexical environment.

When the function wants to access a variable, the inner lexical environment is searched first, then the outer one, and recursively back up until the global lexical environment.

If the variable is not found anywhere, then it is an error in strict mode, without strict mode then assignment to a non-existing variable will be automatically added to the global lexical environment.

Returning a function

If you wrote a function that returns another function say:

function makeCounter() {
	let count = 0;
    
    return function() {
    	return ++count;
	};
}

let counter = makeCounter();
counter(); // count becomes 1
counter(); // count becomes 2

In this case makeCounter() creates a lexical environment that holds the variable count, then it returned a function that will increment the outer count. How does it do that? When counter is invoked later on, it will again create a new lexical environment with nothing in it because it has no variables but rather referring to the outer one. Since it is created in the lexical environment in makeCounter the reference that the inner function points to will be makeCounter's lexical environment and it has the count. Then when it tries to increment count it will be incrementing the count under makecounter's lexical environment and it works out!

Closure

Now this is where closure comes in. A closure is a function that is able to remember it's environment context that it was created in. It remembers the outer variable and is able to access them. Some languages don't support closure, and if it doesn't then what we have just talked about isn't possible.

All JavaScript functions are closure, is able to resolve those outer variables that it used, and when those variables goes out of scope it is still able to remember them, have closure per say. The only exception is the new Function syntax, it is not closure.