What We Will Cover

Welcome to this documentation on JavaScript Functions.

In this documentation, we are going to learn how functions can help you write modular, concise, reusable, and maintainable code.

Before we dive in, let's review what we'll be covering.
First, we learn what a function is, why use them, and how to write and run them. Next we introduce arguments and how they let us pass information to our functions. We also cover how the variables in a function are scoped and how to access them correctly. Then, we look at Immediately Invoked Function Expression, also known as the IIFE pattern, and compare it with regular functions. Next, we introduce closures. Lastly, we'll look into JavaScript modules, and why we should use them.

Some of the major topics that we'll cover include:

  1. Functions, Scope and Closures
  2. Arrow Functions and other Built-in functions
  3. Call, Apply, and Bind methods
  4. Rest Parameters, and the Spread Operator
  5. JavaScript modules

To easily follow through, you should be familiar with the basics of JavaScript, like variables, expressions, and conditional statements. And of course setting up your development environment.

Introduction

A function is a block of organized, reusable code that's used to perform a single related action. In other words, it's a set of statements that perform a task or calculates a value.
Functions lets you create reusable chunks of code, making coding faster, easier, and less error prone.

A function declaration consists of the function keyword, followed by the name of the function, a list of optional parameters enclosed in parentheses, separated by commas, and one or more statements that define the body of the function, which are enclosed in curly brackets (i.e. { }).

Note that the function definition by itself doesn't really do anything, unless it is run or invoked/called.
To do this: You include the function name, followed by parenthesis, and a semicolon, as shown below.

                            
                                function greetings(person1) {
                                    console.log("Hello " + person1 + "!");
                                }
                                greetings("John");//  Hello John!

                                let name = "Jane Doe";
                                greetings(name);// Hello Jane Doe!
                                name = "Sarah D";
                                greetings(name);// Hello Sarah D!
                            
                        

In the example above, the greetings function is passed a value(argument) 'John'. When called, and logged, we get 'Hello John'.
But if we assign a value 'Jane Doe' to the variable 'name' and use this as the argument for the greetings function, we get 'Jane Doe' in the console.

Difference between an Argument and a Parameter

These words are often confused, so before we look at other examples, let's clarify the difference between them.
An argument is a value that we pass to the function when we invoke it. A parameter, on the other hand, is a variable that we list as part of a function definition. In this example, we define a function called sum that takes in two parameters.

Returning values

By default, all functions return a value. Without specifying with a return statements, this value is undefined
Using return statement at the end of our function body makes it possible to collect the value in the stated variable when the function is invoked/called.

                            
                                function greetings() {
                                    return "Hello Jane!";
                                }
                                let message = greetings();
                                console.log(message);//  Hello Jane!
                            
                        

Below is another example of using function to perform simple arithmetic operation.
Here the function sum has two parameters. Within the function body, we are returning the sum of the parameters. We call the function, passing in two arguments(values) that represent the function parameters, and we set this to a variable, result. If we 'console.log' result gives the result, 5 as seen below.

                            
                                function sum(num1, num2) {
                                    return num1 + num2;
                                }
                                let result = sum(2, 3);
                                console.log(result);//  5
                            
                        

Note:
If your number of arguments and parameters don't match? Let's try it out. We invoke the sum function with only one argument. The output is NaN, which stands for not a number. This is because when the sum function doesn't get any value for num2, it defaults to undefined, and JavaScript evaluates the addition of num1 and undefined as not a number.

Understanding Scope

Think of scope as a blocked account. You go to another branch to open it, but told to go to where you opened it. Frustrating right? This is why you need to understand scope. Understanding the lifetime of variables, as well as 'what/who' has access to them can help you avoid bugs.

Function Scope

In the example below, we have a function greeting that simply declares a variable message and assign the value Hello to it. If we invoke the greeting function first, and try to access the message variable outside, we get a reference error message. This is because message has function scope, and as soon as the greeting function completes execution, the message variable is no longer available. In other words, message is now out of scope, and it's value does not exist outside the parent function in which it's referenced, defined or declared.

On the other hand, if a nested function (expression) sayHello is defined, and its value a function hi which has the message variable assigned a value of string Hi. If we call the function sayHello with message = "Hi", we get the result Hi in the console.
This is because message variable inside the nested function has been re-initialized or reassigned a value Hi, and it's logged result shown immediately and sayHello is called. i.e. message has a value within the scope of the function.

But if we comment out message = "Hi", and call the function sayHello we get the result Hello in the console.
This is because message no longer has a value within it, and hence, gets it's value from the parent function. And the output is Hello.

                            
                                function greeting() {
                                    let message = "Hello";
                                    let sayHello = function hi() {
                                        message = "Hi";
                                        console.log(message);
                                    }
                                    sayHello(); // Hi
                                    // if message = "Hi" is commented out
                                    sayHello(); // Hello 
                                }
                                greeting();
                                console.log(message);//  ReferenceError: message is not defined
                            
                        

Note:
Whenever a function cannot find a variable, it looks to its parent function for it. That is, JavaScript will keep looking at a parent and its parent for deeply nested functions until it either finds a variable, or you'll get a reference error.

Block Scope

When we say block, we are referring to the code placed between curly braces. So with block scope, we are talking about the lifetime of variables within the curly brace of an if statement, a while, or a for loop, or any set of curly braces other than a function.
Also, note that the variables declared with the var keyword or created by function declarations do not have block scope.

                            
                                let message = "Hello";
                                if (message === "Hello") {
                                    let message = "Inside if block";
                                    console.log(message);// Inside if block
                                    let count = 100;
                                }
                                console.log(message);// Hello
                                console.log(count);// ReferenceError
                            
                        

In the above, the count variable was declared inside the if block, and as soon as the code block execution was completed, it went out of scope (of the block).
Console.logging message inside the if block gives it's value inside, but outside, it gives Hello
As you can see, we are allowed to reuse variable names within code blocks, but they override the value of message while the block is being executed.

Note:
If we change the type of count to var, then we get 100. This shows that count no longer has block scope.
When building larger applications, we require that values referenced within scope are accessed, and when it is re-initialized/re-assigned with a new value, we only want to get and use exact values. Therefore, it is always a good practice to use let instead of var to declare your variables. Or otherwise const variable, if we want the value to remain constant all through.

Var, Let, and Const Keywords

Var keyword is used if you want to access the same value anywhere. Using var, you have to be careful to know what value is what within what reference, function, or loop, especially since the variable name is the same.

Let keyword when used gives you more control over variable values. Once a variable is assigned a value, it is scoped within the function or block it is in, and can only be re-assigned (without the let keyword), if i need to set a new value.

Const keyword on the other hand, is used when you don't want to change its value ever, otherwise, we get an error message saying it has already been assigned a value.

Immediately Invoked Function Expression (IIFE)

The IIFE pattern lets us group our code and have it work in isolation, independent of any other code. To break it down:
A function expression lets us define a function and assign a true variable.
Immediately invoked means invoking the function immediately where it's defined.

IIFE function doesn't have a name and it's wrapped completely within parentheses. Finally, it has an opening and closing parentheses appended to it, followed by a semicolon. This invokes it right away. The example below is a regular function.

                            
                                function greeting() {
                                    console.log("Hello World");
                                }
                                greeting();// Hello World

                                // Conversion to IIFE
                                (function() {
                                    console.log("Hello World");// Hello World
                                })();
                            
                        

To make it a function expression, we just assign it to a variable or use it in some other expression. We will cover this as we look into Closures in the next section.

Closures

Variables and nested functions go out of scope once the functional block they're contained in completes execution. What if we want to hold onto them, even after a function is executed!

We use Closures, which allow us to hold onto variables and nested functions after a function is executed. Consider the example below.

                            
                                let greeting = (function() {
                                    let message = "Hello";
                                    let getMessage = function() {
                                        return message;
                                    };
                                })();
                                console.log(greeting.message);//  Undefined
                            
                        

We get Undefined because the function is an IIFE, and immediately after it is invoked, the variable, message and function, getMessage go out of scope and values inaccessible.
To prevent this, we create a closure inside. How?

Create a return object with a property getMessage and the value is the function getMessage. Typically, the property and value names are the same, but they don't have to be. This is illustrated below.

                            
                                let greeting = (function() {
                                    let message = "Hello";
                                    let getMessage = function() {
                                        return message;
                                    };
                                    return {
                                        getMessage: getMessage;
                                    };
                                })();
                                console.log(greeting.getMessage());//  Hello
                            
                        

Below is an example of how we can use closure to preserve a function and re-use it, after being called by another function.

                            
                                function setupCounter() {
                                    return function counter() {
                                        return val++;
                                    };
                                }
                                let counter1 = setupCounter(0)
                                console.log(counter1());//  0
                                console.log(counter1());//  1
                                console.log(counter1());//  2

                                let counter2 = setupCounter(10)
                                console.log(counter2());//  10
                                console.log(counter2());//  11
                                console.log(counter2());//  12
                            
                        

In the above, counter1, and counter2 are variables whose values are the function setupCounter. They are each initialized with different arguments 0 and 10 respectively. Each time they are invoked through a console.log, their initial values are incremented by 1.

Arrow Functions

Arrow Functions are anonymous functions with their own unique syntax.

Important notes about Arrow Functions

  • Arrow Functions greatly simplify creating function expressions.
  • They make our code concise and more readable because of the shorter syntax.
  • They it's also a lot easier to understand how variables are scoped within them
  • are not really a substitute for regular functions
  • When you use arrow functions, you have to be aware of several side effects, especially around how the this keyword behaves, as well as how they don't have their own implicit arguments object.

Writing Arrow Functions

                            
                                function sum(num1, num2) {
                                    return num1 * num2;
                                }
                                let output = sum(2, 3);
                                console.log(output);//  6

                                // Conversion to Arrow Function
                                let sum = (num1, num2) => {
                                    return num1 * num2;
                                }                                
                                // Since there's only one statement inside the function body, we can delete the return keyword, as well as the opening and closing curly braces, and move everything on one line, and invoking the function with values 10 and 7 gives 17
                                let sum = (num1, num2) => num1 + num2;
                                let output = sum(10, 7);
                                console.log(output);//  70
                            
                        

The this Keyword

In JavaScript, this refers to the owner of the function we are executing.
If it is a standalone function, it refers to the global window object, otherwise, it can refer to the object, the function is a method of.

                            
                                let message = {
                                    name: "John";
                                    regularFunction: function() {
                                        console.log("Hello " + this.name);
                                    };
                                    arrowFunction: () => console.log("Hi " + this.name);
                                };
                                message.regularFunction();
                                message.arrowFunction();
                            
                        

Invoking both these functions one after another. In the console, the regularFunction prints out Hello John. This is because when we log our message.regularFunction, our this keyword refers to the message object that holds a function. So when we access this.name, we get John.

However, when we call message.arrowFunction, it looks for the name variable in the global context, and the Window object. Since it cannot find it, only Hi gets printed to the console.
If we try to print this.name, we get an empty string. If we try to print just the value of this, we can see the Window object in the console.

NOTE:
While the value of this inside the regularFunction is the message object, that of the arrow function is the Window object

Changing Function Context

Understanding Function Context And The Value of this Keyword

Every JavaScript function while executing has a reference to its current execution context to the this keyword. So, in the example below, the value of this refers to the global Window object.

                            
                                function sayHi() {
                                    console.log("Hi");
                                    console.log(this);
                                }
                                sayHi();//  Hi
                                // Window {...}
                            
                        

Here, we declare a greeting variable and assign an empty object to it. Then, we define a property, sayHi, whose value is an anonymous function. In this case, the value of this is no longer the global window object because the execution context in this function is different. The value of this now refers to the object referenced by the greeting variable. Every JavaScript function while executing has a reference to its current execution context to the this keyword. So, in the example below, the value of this refers to the global Window object.

                            
                                let greeting = {};
                                greeting.sayHi = function() {
                                    console.log("Hi");
                                    console.log(this);
                                }
                                greeting.sayHi();//  Hi
                                // {sayHi: f}
                            
                        

Constructive Function

In this example, we are using the new keyword when invoking the sayHi function. This is also known as the Constructive Function.
The value of this here will be an empty object. Any time you invoke a function with a new keyword, JavaScript implicitly creates an empty object within the function before returning it. So in my example, the variable greeting will hold this empty object.

                            
                                function sayHi() {
                                    console.log("Hi");
                                    console.log(this);
                                }
                                let greeting = new sayHi();//  Hi
                                // [obj Object]
                            
                        

Using Call, Apply, and Bind

Call Method

In the code block below, commenting both our call methods and just run the sayHi function, just prints Hi as this reports to the global window object which does not have a name property. What we really want here is to set the value of this to something other than the current execution context.

Passing the person1 object to the call method as an argument of sayHi binds the value of this in sayHi to the person1 object, and this time, the string Hi, John gets printed to the console. In the same way, when we pass person2, the value Hi, Mary is printed to the console.

                            
                                let person1 = {name: "John", age: 22};
                                let person2 = {name: "Mary", age: 26};
                                let sayHi = function() {
                                    console.log("Hello " + this.name);
                                }
                                sayHi();// Hi
                                sayHi.call(person1);//  Hello John
                                sayHi.call(person2);//  Hello Mary

                                // We can pass additional argument, as seen below
                                let person1 = {name: "John", age: 22};
                                let sayHi = function(message) {
                                    console.log(message + ", " + this.name);
                                }
                                sayHi.call(person1, "Hi");//  Hi, John
                            
                        

You can also pass additional arguments to the call method. As seen above, when we passed a message variable in addition to the person1 object, Call let us run the function, sayHi, in the context of the first argument message.

Note:
You can only invoked the call method on another function. Calling person1.call will result in a type error as person1 is not a function.

Apply Method

While the apply method syntax looks almost identical to that of a call, call accepts an argument list, while apply accepts a single array of arguments.

From the example below, we are not really changing the function context of this value here, so we just pass undefined as the first argument. First we call the introduction function with the arguments John and student. Next, we call the same function once with apply and then with the call method.
The output of all the three functions is printed to the console.

                            
                                function introduction(name, profession) {
                                    console.log("My name is " + name + " and I am a " + profession + ".");
                                    console.log(this);
                                }
                                introduction("John", "student");
                                introduction.apply(undefined, ["James", "artist"]);
                                introduction.call(undefined, "James", "artist");
                            
                        

Note:
Use the apply method when the input parameter is already in the form of an array consisting of similar values; otherwise, if you're dealing with multiple arguments that are not really related to each other, use the call method.

Bind Method

Using the call and apply methods, we call an existing function and change the function context i.e. the value of the this object. But if we would like to make a copy of a function and then change the value of this, we use the bind method.

                            
                                let person1 = {
                                name: "Mary",
                                getName: function() {
                                return this.name;
                                }
                                };
                                let person2 = {name: "John"};
                                let getNameCopy = person1.getName.bind(person2);
                                console.log(getNameCopy());//  John
                            
                        

In the code block above, when we call bind on the person1.getName function and pass the person2 object as an argument, a new function is created and returned, and assigned to the getNameCopy variable.
Additionally, the value of this in the new function is set to the person2 object.

Therefore, when we print the output of the getNameCopy method call, it returns John, and this way, we are no longer changing the context of an existing function the way we did in call and apply, but instead create a new copy of function and then change its context to another object.

Note:
If we just print getNameCopy, it prints the body of the anonymous function to the console, which is a copy of the getName function in person1.

Built In Functions

Using eval, parseInt, and parseFloat

eval()

Eval, as its name suggests, accepts a string as input, a valued set, and returns a value.

In this example, we have two variables, x and y, initialized to the values 1 and 2, respectively.
Next, we append the output of the eval function that takes an expression and string format as input. The value 4 is output to the console.

If we introduce a new variable z = "abc", and try to evaluate the expression, x + y + z, the string value 3abc is displayed in the console. Therefore, when JavaScript comes across a string value abc, it concatenates that value with the number 3 and returns a string value.

                            
                                let x = 1
                                let y = 2
                                let z = "abc"
                                console.log(eval("x + y + 1"));//  4
                                console.log(eval("x + y + z"));//  3abc
                            
                        

parseInt()

The function parseInt parses a string and returns an integer.
If the first argument cannot be converted to a number, then parseInt returns a special value of NaN (not a number). But if no argument is provided, JavaScript assumes it, and outputs result in base 10.

In the example below, the first statement returns the value F and base 16 while the second statement returns a value of 15 in decimal. Both statements display 15 to the console. The third statement returns NaN (not a number).

                            
                                console.log(parseInt("F", 16));//  15
                                console.log(parseInt("15", 10));//  15
                                console.log(parseInt("Hi", 10));//  NaN
                            
                        

parseFloat()

ParseFloat works just like parseInt, but it returns a floating point number. The first 2 statements here return 3.99 and 39.9, respectively, and we get not a number for the third statement as an empty string cannot be parsed to a floating point number.

                            
                                console.log(parseFloat(3.99));// 3.99
                                console.log(parseFloat(399));// 399
                                console.log(parseFloat("x"));//  NaN
                            
                        

Rest Parameters And Spread Operator

Before looking into Rest Parameters And Spread Operator, it is important to understand what Default parameters are.

Default Parameters

Default parameters allow you to give a default value to a parameter in a function.
In the code block below, name is a default parameter and it contains a single line of statement.

The name variable when initialized with the value World, and the sayHi function invoked, the result, Hello World is printed to the console.
But if sayHi is invoked with an argument like John, we have Hello John.

In the second block, we have the same function as the first, but it takes an additional parameter message without any default value.
Invoking the function, sayHi, with only the argument, Hello, outputs Hello World to the console, since the second parameter name, has default value World.
However, if the sayHi function is invoked again with two arguments, Hi and John, the output is Hi John.

                            
                                function sayHi(name = "World") {
                                    console.log("Hello " + name);
                                }
                                sayHi();// Hello World
                                sayHi("John");//  Hello John

                                // Passing in extra arguments
                                function sayHi(message, name = "World") {
                                    console.log(message + name);
                                }
                                sayHi("Hello");// Hello World
                                sayHi("Hi", "John");//  Hi John
                            
                        

If one argument is passed, when calling the sayHi function, it represents the first function parameter (non-default parameter), but if it is two arguments, it represents both, in the order it is passed.

Note:
Please note that the default parameter in our function definition should always come after the regular parameters.
So in our sayHi function here, message cannot come after the name parameter, otherwise we get undefined, and message never gets initialized;

Rest Parameters

With rest parameters, you can define a function to store multiple arguments in a single array. This is especially useful when you're invoking a function with multiple arguments.

In the example below, we define a greet function with the names parameter. The dot, dot, dot (...) is what makes names a rest parameter and allows greet to accept multiple arguments.
We then use the forEach method to iterate over each name value and print the string Hi, followed by the each name from the array of argument.

                            
                                let sayHi = function greet(...names) {
                                    names.forEach(name => console.log("Hi " + name));
                                }
                                greet("Mary", "John", "James");
                                // Hi Mary
                                // Hi John
                                // Hi James
                            
                        

Rest Parameters With Additional Parameter

Consider the same greet function, but with an additional parameter message.

                            
                                let sayHi = function greet(message, ...names) {
                                    console.log(message + "everyone!");
                                    names.forEach(name => console.log("Hi " + name));
                                }
                                greet("Welcome", "Mary", "John", "James");
                                // Welcome everyone!
                                // Hi Mary
                                // Hi John
                                // Hi James
                            
                        

In the code block above, the greet function was declared with two parameters, message, and a rest parameter, name. And we call the function with four arguments passed.
The value Welcome gets assigned to the message parameter, while the rest of the arguments get stored as an array in the names parameter. The output in the console is as seen above.

If the message parameter is placed after names, we get syntax error stating that rest parameter must be the last formal parameter.
That's expected, as we want the rest of the arguments to be stored in a rest parameter and not a regular parameter.

Note:
Rest parameters provide a great deal of flexibility, but when mixed with regular parameters, should be placed last.

Spread Operator

Spread Operator allows a function to take an array as an argument and then spread out its elements so that they can be assigned to individual parameters.

Here we have a function greet with 2 parameters, person1 and person2. We invoke the function, but instead of passing in two arguments, we pass a single argument, i.e. an array using the spread operator. This then spread the values John and Mary (as the argument) for person1 and person2 respectively.

                            
                                function greet(person1, person2) {
                                    console.log("Hello " + person1 + " and " + person2);
                                }
                                let names = ["John", "Mary"];
                                greet(...names);// Hello John and Mary
                            
                        

In the example below, since string is an array of characters, each of the characters in the variable letters is spread out to the parameters of the display function, so the spread operator can be used with any iterable like string, array, or list.

                            
                                function display(char1, char2, char3, char4) {
                                    console.log(char1, char2, char3, char4);
                                }
                                let letters = "abcd";
                                display(...letters);// a b c d
                            
                        

If we initialize letters with more than four letters and then run the display function, the additional letters never get collected since we only have four parameters.

                            
                                function display(char1, char2, char3, char4) {
                                    console.log(char1, char2, char3, char4);
                                }
                                let moreLetters = "abcdefg";
                                display(...moreLetters);// a b c d
                            
                        

However, if we add a rest parameter called others to the display function, first the arguments a b c d gets assigned based on the parameters in the display function, then the other additional letters get stored in the rest parameter (as an array).

                            
                                function display(char1, char2, char3, char4, ...others) {
                                    console.log(others);
                                    console.log(char1, char2, char3, char4);
                                }
                                let letters = "abcdefg";
                                display(...letters);// (3) ["e", "f", "g"]
                                // a b c d
                            
                        

Summary:
While the rest parameter collects individual arguments and stores them in an array, the spread operator takes an array as argument and spreads it into individual parameters.

JavaScript Modules

Intro

JavaScript Modules help us break up large JavaScript codes into smaller, re-usable pieces of codes.
To do this, we need some keywords:

  • Import and Export
  • Encapsulate code
  • Control access
  • Reference own dependencies

Export is the keyword used to create the module, while Import is used to consume the module. Import and Export allow us to Encapsulate our code into a single protected entity. Meaning we get to create little bundle of codes that we can package up and then only expose pieces out to help guide understanding about what this chunk of code can do and utilize that chunk of code in a meaningful way. This allows us to control access to the code as well, protecting them.
Also, we can reference our own dependencies internally. This means that if a piece of code needs to reference like a Lodash function or something similar to that, I don't have to import that globally into the whole application. I can just use it in my one module to keep scope to a to a minimum.

Module Considerations

  • Modules are singletons
  • When you export something from a module, every other thing that imports that module gets the same instance of it.

  • Properties are bound
  • The individual items exported are not just values, but they're pointers or bindings to the properties. If something is exported and then changed, it changes everywhere.

  • Exports are static
  • Also, once you define what's being exposed through an export, you can't change it later at run time. Once it's loaded, it stays how it is.
    What you export out of a module is static, Unlike in JavaScript, where you can change anything at run time (nothing is static).

  • One module per file
  • Modules are file-based.
    If you're loading multiple modules into your browser, you're going to request those files one at a time over HTTP. What we normally do in the mean time is use a module bundler like webpack or something like that to consolidate them all down into one file.

Creating Modules - Export Keyword

Named Exports

  • Create js file, e.g template.js
  • Write your functions, or variables inside this file
  • Write export before the function or variable. E.g. export function functionName(name1) { ... }
  • Use import { functionName, variableName } from './template.js' (depending on the directly) to import functionName from the template.js module
                            
                                // template.js file

                                export const errorMessage = `<p><em>No message found</em></p>`;
                                export function greetings(name1) {
                                    return "Hello " + name1;
                                };
                            
                        

Then to import in destination.js file, use

                            
                                import { greetings, errorMessage } from './template.js';
                            
                        

Note:
Using export before any function or variable allow us to use the import keyword as seen above.
However, we can export all of them form the template.js file thus:

                            
                                const errorMessage = `<p><em>No message found</em></p>`;
                                function greetings(name1) {
                                    return "Hello " + name1;
                                };
                                export { greetings, errorMessage };
                            
                        

To import and use in destination file is still the same as above.

We can also export { ... as ... }. That is:

                            
                                export { greetings as greetName };
                            
                        

Then to import in destination.js file, use

                            
                                import { greetName } from './template.js';
                            
                        

Default Exports

Sometimes, remembering the name can be overwhelming, especially if we are working with lots of functions. Export default allows us export, and then import in destination from the source, without necessarily remembering the name. Below are two code block examples that illustrates default exports:

                            
                                const errorMessage = `<p><em>No message found</em></p>`;
                                export default function greetings(name1) {
                                    return "Hello " + name1;
                                };
                            
                        
                            
                                const errorMessage = `<p><em>No message found</em></p>`;
                                function greetings(name1) {
                                    return "Hello " + name1;
                                };
                                export { greetings as default };

                                // to combine with the named export:
                                export { greetings as default, errorMesssage };
                            
                        

Then to import in destination.js file, use

                            
                                import greetings from './template.js'; // note the absence of curly braces around the function name
                            
                        

Purpose of using export default is to simplify how import is done; 'Don't worry about the name, just import !'

Aggregating Modules

Assume we have another js file just like template.js, called sessions.js, which has a getSessions function.
If we want to export getSessions, and then import into our destination.js file, we will be having clusters of import statements.

We can make this minimal by including export greetings from './template.js' inside sessions.js.

                            
                                const sessionURL = 'session.json';
                                function getSessions() {
                                    ...
                                };
                                export { getSessions as default, sessionURL };
                                export { greetings } from './template.js';
                            
                        

Note:
Template.js does not get added to scope for session.js in any way. Like, I can't access that anywhere, it's not an import. I'm just re-exporting it externally, and then I can aggregate everything up into just one bundle.

Using Modules - Import Keyword

First of all ensure browser is compatible for js module, then, in the index.html file add/change the script tag type to equal module. e.g
<script src="js/app.js" type="module"></script>

Default Imports

After changing the type to equal module, we import in destination.js file, thus:

                            
                                import getSessions from './sessions.js'; // in the app.js getSessions can be called any name
                            
                        

Note:
When we use export as default, from the app.js perspective, we don't care what it is called. Whatever it is called is what is used in here. However, we always want to ensure naming consistency and clarity.

Named Imports

Named Imports works in similar way as Default Imports, except that we must wrap the element(s) around curly braces and ensure the name(s) in destination.js are the same as in sessions.js or template.js file, except i use as, and then pass in a name-space. This is shown in the code block below.

                            
                                import { getSessions as sessions, sessionURL } from './template.js';
                            
                        

Also, if we don't want to list all functions/variables initially exported, we can import like so:

                            
                                import * as sessions from './sessions.js';
                            
                        

This means, we have taken everything in the sessions.js and dropped into the name-space or object called sessions.
Then, we can now use as sessions.getSessions()

However, for the Aggregate Module Export example above, we use Named Import thus:

                            
                                import getSessions, { greetings, sessionURL } from './sessions.js';
                            
                        

Hybrid Imports

This is when you're using default import and named import together.
In the code below, we are importing getSessions (default), and * or every other thing as sessions (named).

                            
                                import getSessions, * as sessions from './sessions.js';
                            
                        

For more explanations or illustrations on JavaScript Modules. Please visit:

Useful Resources

Reference

  • JavaScript: Functions (Pluralsight), by Prateek Parekh.
  • Working with JavaScript Modules (Pluralsight), by Jonathan Mills.
  • JavaScript Syntax and Operators (Pluralsight), by Paul D Sheriff.
  • JavaScript Algorithms and Data Structures (Freecodecamp)

This documentation page was made in fulfillment of Freecodecamp's Responsive Web Design Projects, and only it's main content is open to updates, corrections or modifications, based on generally acceptable standard/requirements.
Found a problem with this page, and want to make a contribution, feel free to contact me.