Introduction
We are going to recap what we’ve learnt about Objects as well as dive a little deeper.
An object is one of the JavaScript data types. It allows you to store data using “key/value pairs”.
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
To access a value from the object, we use a key
. There are two ways we can use the key
to access our values:
- Dot notation
- Bracket notation
1. Dot Notation
With dot notation
, we put a .
“dot” (also known as a full stop or period) after the object name and then the key
we want to use.
const name = person.firstName;
console.log(name);
// Returns:
// Ola
2. Bracket Notation
With bracket notation
, we put the key, as a string with quotation marks, in square brackets right after the Object’s name.
const name = person['firstName'];
console.log(name);
// Returns:
// Ola
It would help if you usually used dot notation
to access the values in your objects. However, bracket notation
is still critical to know because it lets us dynamically access values in our Objects. This is because we can generate our keys with variables and string concatenation (adding of strings together).
In this example, we have multiple keys that are the same except for a number at the end. We create our key by combining the string ingredient_
with a number e.g. "ingredient_" + 1
.
const recipe = {
ingredient_0: 'Egg',
ingredient_1: 'Milk',
ingredient_2: 'Flour'
};
const ingredient0 = recipe['ingredient_' + 0];
const ingredient1 = recipe['ingredient_' + 1];
const ingredient2 = recipe['ingredient_' + 2];
console.log(ingredient0);
console.log(ingredient1);
console.log(ingredient2);
// Returns:
// Egg
// Milk
// Flour
Let’s do another example with the same data as the last example. This time, we will use a for loop
and the index from the loop. We will use this index to automatically increment the number in the Object key we are generating.
const recipe = {
ingredient_0: 'Egg',
ingredient_1: 'Milk',
ingredient_2: 'Flour'
};
for (let index = 0; index <= 2; index++) {
// We use
const ingredient = recipe['ingredient_' + index];
console.log(ingredient);
}
// Returns:
// Egg
// Milk
// Flour
Looping with an object
An array is a list of elements that are in a specific order. In an array, we can access an element at a certain position by using an index.
An object is not a list like an array. The keys in an object are not in any specific order, so we cannot simply use a for loop
with an index to loop through key/value pairs in an object.
There are other ways to loop through the values in an object.
for...in
loop
The for...in
loop used with an object will iterate over the keys of the object.
for (const myKey in myObject) {
// We are looping through the keys of the object. To get the value you
// need to use the key (myKey) as a way to reference the key/value pair.
console.log(myObject[myKey]);
}
The following code is a more practical example of the for...in
loop with an object. Note how we are using the key as an accessor in the object so that we can get the value.
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
for (const key in person) {
// Use the key to access the value from the key/value pair
console.log('key:', key, 'value:', person[key]);
}
// Returns:
// key: firstName value: Ola
// key: lastName value: Nordmann
// key: module value: JavaScript
Object Methods
Object.keys()
The object Object.keys()
method returns an array containing all of the keys that are in that object.
The syntax is fairly basic. Object
is an actual keyword you need to use, giving us access to the .keys
method. You then pass in the object as the parameter. In the example below, we are passing in the myObject
object.
const myObject = {
// Key/value pairs here
};
Object.keys(myObject);
Let’s have a look at the previous example we did with for...in
. You can see that we set keysArray
to the result of Object.keys(person)
. The end result is then an array of keys from the object.
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
const keysArray = Object.keys(person);
console.log(keysArray);
// Returns:
// ['firstName', 'lastName', 'module']
The array being returned from Object.keys()
is actually of significance because it means that we can now use our array methods, such as map()
, filter()
and reduce()
.
forEach() example
In this example we will use the forEach
array method to loop through the keys of the object. The forEach
method is for when we simply want to loop and don’t need a new array being returned.
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
Object.keys(person).forEach((key) => {
console.log(`key: ${key}, value: ${person[key]}`);
});
// Logs:
// key: firstName, value: Ola
// key: lastName, value: Nordmann
// key: module, value: JavaScript
map() example
In this example, we use map
to create a new array. Each element in the array will contain the key
and value
for each of the key/value pairs.
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
// .map will return a new array that contains the same number
// of elements as there were in the initial array
const keysAndValues = Object.keys(person).map((key) => {
return `key: ${key}, value: ${person[key]}`;
});
console.log(keysAndValues);
// Logs:
// 0: "key: firstName, value: Ola"
// 1: "key: lastName, value: Nordmann"
// 2: "key: module, value: JavaScript"
Object.values()
The Object.values()
method will create an array of only the values from the object. It can be seen as the opposite of the Object.keys()
object method which creates an array of the keys in the object.
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
console.log(Object.values(person));
// Returns:
// ['Ola', 'Nordmann', 'JavaScript']
Object.entries()
Object.entries()
returns an array containing both the key and value.
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
Object.entries(person).forEach((keyValuePair) => {
console.log(keyValuePair);
});
// Returns:
// ['firstName', 'Ola']
// ['lastName', 'Nordmann']
// ['module', 'JavaScript']
We can use array destructuring for the key/value pair as the value returned will always be the key and the value. Note how we do not have to access the value using the key as we already have the value given to us as index 1
of the array being returned:
const person = {
firstName: 'Ola',
lastName: 'Nordmann',
module: 'JavaScript'
};
Object.entries(person).forEach(([key, value]) => {
console.log('key:', key, 'value:', value);
});
// Returns:
// key: firstName value: Ola
// key: lastName value: Nordmann
// key: module value: JavaScript
ES6 Modules
Introduction to ES6 Modules
In the earlier days of web development, JavaScript usage on websites was minimal, primarily addressing minor functionalities. This scenario involved handling smaller JavaScript code blocks, often lacking complexity.
However, the landscape of web development has evolved significantly. Presently, we encounter web applications entirely crafted using JavaScript, necessitating a structured approach to managing and maintaining code. This shift led to the critical need for code modularity.
Initially, modules were a feature exclusive to environments like Node.js. Yet, with advancements in web technologies, modern browsers have embraced native support for modules. This pivotal change was ushered in by the introduction of ES6 modules.
ES6 modules revolutionize the way we handle JavaScript code. They enable developers to seamlessly import and export code elements, such as variables and functions, using import
and export
keywords. This feature greatly enhances code organization and reusability.
Here’s a straightforward illustration of employing ES6 modules through import
and export
:
// Importing the 'addNumbers' function from a module
import { addNumbers } from './math.mjs';
// Using the imported function
const result = addNumbers(10, 10);
// Exporting the result for use in other modules
export { result };
This example encapsulates the essence of ES6 modules, showcasing how they facilitate code segmentation and collaboration in modern JavaScript development.
Setup
Before we begin to use modules, we need to look at some minor setup that needs to be done.
Naming of module files
It’s the convention to use the .mjs
extension for JavaScript module files instead of the .js
extension e.g.
myModule.mjs
It is not mandatory to name a module file with the .mjs
extension, but it is becoming common practice and is recommended. This is because:
It lets developers know that the file they are working with is a module instead of a normal script.
It ensures the file is correctly parsed as modules when tools are used, such as Babel.
Importing of Module files in a browser
To make use of a module in a browser, you need to add type="module"
when importing the JavaScript module file.
Below is how we would usually import a normal JavaScript file:
<script src="myFile.js"></script>
Below is how we would import a JavaScript module.
<script type="module" src="myModule.mjs"></script>
If you tried to use import
or export
and did not specify type="module"
, you would likely get the following error:
Uncaught SyntaxError: Cannot use import statement outside a module (at main.mjs:1:1)
title: Modules basics keywords: modules tags: JavaScript 2 sidebar: javascript-2 permalink: javascript-2/basics.html folder: javascript-2
Imports and exports
import
and export
keywords are the way you will be able to use modules throughout your code.
When we use import
we will be importing code from a module so we can put the code in another file. This code can be functions or variables. This code has to be exported from another file; otherwise, we won’t be able to import it.
When we use export
we will be exporting code from a module so that it can be imported into other files using the import
keyword.
Named and default imports/exports
There are two main ways we can use the import
and export
keywords, which are “named imports/exports” and “default imports/exports”.
We are going to look at the two different types below:
Named imports/exports
Named imports/exports allow us to import and export specific portions of our code.
For example, we might have three functions in a module, but we only want to expose two of them to other developers. In this case, we would then only export the two functions, e.g.
// module.mjs
function functionOne() {
// code here
}
function functionTwo() {
// code here
}
function functionThree() {
// code here
}
// We only export functionOne and functionTwo
export { functionOne, functionTwo };
When we want to import these functions into another file, we will use the following code.
// main.mjs
import {functionOne, functionTwo} from './module.mjs';
If we tried to import functionThree
like this, then it will throw an error:
import {functionOne, functionTwo, functionThree};
The error will look as follows:
Uncaught SyntaxError: The requested module './module.mjs' does not provide an export named 'functionThree' (at main.mjs:1:36)
This is because we didn’t export functionThree
from the module.mjs
file.
Aliasing
When using “named imports/exports”, we have to use the name of the function or variable we are importing. If we use a named export to export the function named functionOne
, we need to import it as this name. This is why it’s called a “named import/export”.
There is a way around this, though, by using “aliases”. This lets us change the named import. We do this by adding as
after the variable name and then the name you want to use.
In the example below, we alias functionOne
to have the name newFunctionName
:
NOTE: When you use an alias, you can’t use the original name where you’ve added the alias.
import { functionOne as newFunctionName } from './module.mjs';
// ✅ This works
newFunctionName();
// ❌ This won't work as we have aliased 'functionOne' to be 'newFunctionName'.
functionOne();
Default imports/exports
The other way we can import/export is using “default imports/exports”.
Default imports/exports involve exporting code using the default
keyword after the export
keyword, e.g.
function mainFunction() {
// code here
}
export default mainFunction;
When we import a “default export”, we don’t use the {}
braces, we simply write the name we want to use:
import mainFunction from './module.mjs';
Default imports can be named anything
You can name your default import whatever you would like. You do not have to use the same name like you do with “named imports/exports”.
In the example below, we default export a function functionOne
but we import it as newFunctionName
:
// module.mjs
function functionOne() {
// code here
}
// We are using a default export, not a named export
// You can tell by the 'default' keyword and lack of
// {} curly braces
default export functionOne;
// main.mjs
// Here we import the default export from 'module.mjs'
// and called it 'newFunctionName'
import newFunctionName from './module.mjs';
Combining named exports/imports with default exports/imports
You can combine named and default imports/exports, which is not uncommon.
We are using both named and default imports/exports in the example below:
// module.mjs
function functionOne() {
// code here
}
function mainFunction() {
// code here
}
// Named export
export { functionOne };
// Default export
export default mainFunction;
// main.mjs
// Here we are using the default import (lack of curly braces)
// as well as the named import { functionOne }
import renamedDefaultFunction, { functionOne } from './module.mjs';
If you are familiar with React, you will likely have already seen default and named exports being used together:
import React, { useEffect, useState } from 'react';
Other ways to export default and named exports
So far, you have only seen exports used in one way, where we add the exports at the bottom of the file.
function myFunction() {
// code here
}
export { myFunction };
Another way we can export our code is to add the export keyword when you create the function. The advantage of this is that you don’t have an extra line of code that you need to keep updating as you add more code.
Named export:
Below is another way we could do a named export:
export function myFunction() {
// code here
}
Default export:
Below is an alternative way to do a default export:
export default myFunction() {
// code here
}
Splitting out code
As mentioned previously, modules allow us to split our code easily. We can choose what code (such as functions and variables) we can export from a module.
This means we can create a “separation of concerns”, which means that we have different modules for different parts of our application.
For example, if we had a game, we could have our player code in a player.js
file, our enemy code in an enemy.js
file and our main game in a game.js
file.
This makes your code more manageable as your code is independent. Any changes to the player.js
file will not have side-effects that impact your enemy.js
file.
There are additional benefits to this. When you start using a bundler, such as Webpack, code-splitting lets you load only certain files that would be needed, allowing your website to load faster.
Practical example
NOTE: The code for this practical example can be found in the following repo.
In this example, we will emulate a part of a simple online shop. There is an item with a set price that will have tax added. It will be formatted, and the final amount will be displayed to the user.
Here is the list of files:
index.mjs
: This is our main entry point file that will call the functions from the modules.tax.mjs
: This contains a functioncalculateTax
which calculates the tax.utils.mjs
: This contains a functionformatCurrency
which will make our item look more presentable e.g. 115 becomes “115.00 kr”.display.mjs
: This contains the functiondisplayAmount
which will display a message to the user about how much the item will cost after tax is applied.
1. index.mjs
// 1. index.mjs
import { calculateTax } from './tax.mjs';
import { formatCurrency } from './utils.mjs';
import { displayAmount } from './display.mjs';
// Our item initially costs 20
const price = 100;
const taxPercentage = 15;
// We need to add tax to our item.
const priceWithTax = calculateTax(price, taxPercentage);
// priceWithTax = 115
// We need to now format the item amount
// so it has 2 decimal spaces but also so
// it shows a currency symbol
const formattedPriceWithTax = formatCurrency(priceWithTax, 'kr');
// formattedPriceWithTax = '115.00 kr'
// We finally display a message to the user:
displayAmount(formattedPriceWithTax);
// Logs:
// The item costs 115.00 kr.
2. tax.mjs
// 2. tax.mjs
/**
* Calculates the tax for a given amount. Tax
* defaults to 15%.
* @param {number} amount
* @param {number} taxPercentage
*/
export function calculateTax(amount, taxPercentage = 15) {
return amount + amount * (taxPercentage / 100);
}
3. utils.mjs
// 3. utils.mjs
/**
* Formats an amount to be a currency display amount e.g.
* "500" becomes "500.00 kr"
* @param {number} amount Currency amount
* @param {string} currencySymbol The currency symbol
* @returns
*/
export function formatCurrency(amount, currencySymbol = 'kr') {
const formattedAmount = amount.toFixed(2);
return `${formattedAmount} ${currencySymbol}`;
}
4. display.mjs
// 4. display.mjs
/**
* Displays the cost of the item with tax applied to the user
* @param {string} amount
*/
export function displayAmount(formattedAmount) {
console.log(`The item costs ${formattedAmount}.`);
}
Additional Info
Modules are deferred by default
When using a <script>
tag with a normal JavaScript file, you run into the issue where the file loads before the HTML elements have been added to the page. This leads to errors when trying to select the HTML elements with JavaScript because these HTML elements don’t exist when the JavaScript is running.
The solution to this is to add defer
or add the <script>
import right before the end of the </body>
tag.
Modules, on the other hand, are deferred by default. This means that you can add your module imports to the <head>
and without needing to use the defer
keyword.
Dynamic imports
You don’t have to load all of your modules upfront. Instead, you can load them when needed.
Below is an example of a dynamic import:
// math.mjs
export addNumbers(a, b) {
return a + b;
}
// main.mjs
async function doSum() {
const mathModule = './math.mjs';
const { addNumbers } = await import(mathModule);
const result = addNumbers(10, 10);
console.log(result);
// Logs:
// 20
}
doSum();
Additional Resources
V8.dev: Modules - Dynamic imports
Lesson Task
Brief
Develop a simple web application utilizing ES6 modules. Your application will consist of a main module that imports functions from two separate modules and uses them to perform tasks.
Requirements:
Module 1 (
stringUtils.mjs
): Create a module that exports two functions:capitalize
: Takes a string and returns it with the first letter capitalized.lowerCase
: Takes a string and converts it to lowercase.
Module 2 (
mathUtils.mjs
): Create a module that exports a function:add
: Takes two numbers and returns their sum.
Main Module (
app.mjs
): Import the functions from the above modules and use them to:- Capitalize a string.
- Convert a string to lowercase.
- Add two numbers.
Display the results of these operations in the console.
Expected Outcome:
On running the main module, it should display the results of the operations in the console.
Solution:
File: stringUtils.mjs
// Exporting a function to capitalize the first letter of a string
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// Exporting a function to convert a string to lowercase
export function lowerCase(str) {
return str.toLowerCase();
}
File: mathUtils.mjs
// Exporting a function to add two numbers
export function add(num1, num2) {
return num1 + num2;
}
File: app.mjs
// Importing functions from the stringUtils and mathUtils modules
import { capitalize, lowerCase } from './stringUtils.mjs';
import { add } from './mathUtils.mjs';
// Using the imported functions
const capitalizedString = capitalize('hello');
const lowerCaseString = lowerCase('WORLD');
const sum = add(10, 5);
// Displaying the results in the console
console.log(`Capitalized: ${capitalizedString}`); // Output: Hello
console.log(`Lowercase: ${lowerCaseString}`); // Output: world
console.log(`Sum: ${sum}`); // Output: 15
Task for Students:
Create the above modules and run the main module (app.mjs
). Ensure your environment supports ES6 module syntax. Observe the outputs in the console and understand how the import
and export
statements are used to share functionality across different modules.