Like most other programming languages, JavaScript supports abstracting (potentially reusable) chunks of code into "functions". But additionally, JavaScript also support "lambda expressions". We will explain what these two mean. They are similar, and yet there are subtle differences. Stay tuned ...
Functions
Let's start with an example:
// function definition
function compare(num1, num2) {
if(num1 > num2) {
return 1;
} else if(num1 == num2) {
return 0;
} else {
return -1;
}
}
// function calls
const n1 = 6, n2 = 5;
const c1 = compare(n1, n2); // c1 is now 1
const c2 = compare(n1, n1); // c2 is now 0
const c3 = compare(n2, n1); // c3 is now -1
As seen in the example above, a function ...
- Is a chunk of code with definite boundaries (begin and end) defined once.
- Potentially called any number of times (if called more than once, it would be considered reusable).
- There is a clear distinction between a function definition and a function call.
- Function definition: A procedure/process made from a chunk of code that is used to complete a task e.g. everything from the first to last curly brace
{...}
in the example above is the definition. - Function call: Actually using the function to complete a task e.g. compare(n1, n2) in example above.
- Function definition: A procedure/process made from a chunk of code that is used to complete a task e.g. everything from the first to last curly brace
- The line of code that calls or invokes a function is called caller while the function itself is called callee in that context.
- There is a clear distinction between a function definition and a function call.
- Has a specific function or responsibilities, e.g. this one compares two numbers.
- Receives zero or more named parameters, e.g.
num1
andnum2
above. - Performs some computation, i.e. the body of the function.
- At some point, the function returns, i.e. terminates its own execution and returns to execution contexts of the caller.
- Sometimes it returns a value, e.g. 1, 0, or -1 above.
- It could also omit returning a value, in which case, the return value is
undefined
. - If you want to return multiple values, all you have to do is return one of the composite types (i.e. array or object)
- The function call and its returned value can be used as part of an expression (e.g.
3 * compare(9, 8)
). In our example above, we assigned the returned value toc1
,c2
, orc3
, which is but an assignment expression. - In the case above, it has a name. But that's not always the case. it is possible to have functions without names (anonymous functions, explained below).
- The caller context passes in actual parameter values that get assigned to the formal parameters once you enter the callee context. In our example,
n1
andn2
(with values 6 and 5) are actual parameters that are assigned tonum1
andnum2
, the formal parameters, when execution context enters the function.
Pass-by-value v/s pass-by-reference
- You can think of the formal parameters (
num1
andnum2
) as local variables in the function scope. Assigning new values to them doesn't affect the value of actual parameters (n1
andn2
). For example, if you added the following code somewhere in the function above, it would affect the value ofnum1
, but not ofn1
.
num1 = 999;
// Actual parameter
-
This behavior is called pass-by-value, where only a copy of the value of the actual parameter is passed, not the parameter variable from the caller context.
- In the code above, if assigning a new value to
num1
affected the value ofn1
, then that would be called pass-by-reference. JavaScript doesn't do that. It does pass-by-value.
- In the code above, if assigning a new value to
-
But then, there are some scenarios under which it may look like you're getting pass-by-reference behavior (but you're not!). Here's an example:
function upgradeCustomer(customer) {
customer.status = "premier";
}
const cust1 = {status: "standard"};
console.log("customer", cust1); // {status: "standard"}
upgradeCustomer(cust1);
console.log("customer", cust1); // {status: "premier"}
- If you run the code above (by pasting it into
Developer Tools
>Console
), it will first print a status of "standard" and then of "premier". Does that mean that assigning a new value tocustomer.status
inside the function is changing the value ofcust1
? It may look like that, but technically that's not the case. It is not changing the value ofcust1
, it is only changing the value of a property (status
) of the object thatcust1
(and latercustomer
) is referencing. If instead of reassigning tocustomer.status
had we reassigned tocustomer
itself, it would have had no effect oncust1
-- and that's why this is still pass-by-value.
function upgradeCustomer(customer) {
customer = "premier";
}
const cust1 = {status: "standard"};
console.log("customer", cust1); // {status: "standard"}
upgradeCustomer(cust1);
console.log("customer", cust1); // {status: "standard"}
- Then why are we getting the illusion of pass-by-reference? That's because objects (and arrays) are what we call "reference types". Meaning, variables of these types simply point to the object or the array in memory, they don't actually carry the entire object or array as value.
[TODO] - provide a graphic.
- Certain languages (e.g. PHP and C++) do provide the option to pass-by-reference, while JavaScript doesn't. Instead it provides reference types. And if you do want to compute a new value for a non-reference type (e.g. number), then return that new value from the function.
Anonymous Functions and IIFE
- JavaScript allows omitting the name of the function, like so:
function() {
// ... some code
}
- This is called anonymous function. The only problem is, how would you call such a function? There are a few ways:
- Assign the anonymous function to a variable, and then invoke or call that variable like a function.
// assign the anonymous function to a variable
const f1 = function() {
// ... some code
};
f1(); // *call* the variable as a function - But in most cases, we don't have to do this (assign an anonymous function to a variable), because most of the time we are simply passing the anonymous function into the invocation of another function as a parameter, like so:
As you can see above, there's no need to assign the anonymous function above (event callback) to a variable since it, as a value, is getting passed todocument.addEventListener(function (event) { // event callback
// respond to event
});addEventListener
as a function paramter. - The other way lies in the fact that the function definition in JavaScript is technically an expression. Yes,
function(){}
is actually an expression that has a value! That shouldn't really be surprising since we just assigned the value of that expression tof1
above. So we could eliminate thef1
variable by combining the function definition expression with the function call expression, like so:(function() {
// ... some code
})(); // IIFE
-
The above is called an Immediately Invoked Function Expression (IIFE), because we are defining and invoking a function in a single expression. We did have to wrap the function definition in parenthesis (i.e.
()
) because otherwise the last pair of()
would try to invoke the function before it was defined. By wrapping the function definition in()
we are forcing it to take precedence over function invocation. -
So why would anyone want to write code like that? The short answer is -- This is a better than writing a ton of code outside the IIEF (at global scope) because it does not pollute the global namespace that variables from this code. If that doesn't make any sense to you, then just wait. We'll explain when there's a need to use IIFE.