Advanced JavaScript

Lord AbiollaLord Abiolla
20 min read

On this topic, we shall learn some advanced JavaScript concepts that’ll enable us have better understanding of JavaScript which can later be applied on frameworks such as React.

Topics

The topics we shall dwell in on this journey towards Java

  • Arrays:

    Here we will learn the following;

    1. Fundamentals of arrays

    2. Accessing array elements

    3. Basic operations on Arrays (Array methods)

  • Objects

    Under Objects, we shall explore the following;

    1. The key concepts of objects

    2. Structure of object

    3. Object properties and methods.

  • ES6+ Features

    We shall delve deep by understanding ES6, where we shall learn the following;

    1. Modern JavaScript features such as let, const, arrow functions

    2. Template literals

    3. Destructuring, spread, and rest operators.

  1. Arrays in JavaScript

In JavaScript, an Array is a variable that can hold multiple values together. The values within an array are called elements and can be accessed by numerical indices.

A JavaScript array has the following characteristics:

  1. Arrays are resizeable and can contain a mix of different data types. For example, you have an array that stores elements with the types numbers, string, boolean, and null. Their size is also dynamic and auto-growing, and therefore you don’t need to specify the array size up front.

  2. JavaScript arrays are zero indexed. Meaning, the first element in array starts at index 0 while the last element is at array’s length minus 1.

  3. JavaScript arrays are associative and so elements cannot be accessed using arbitrary strings as indexes, but must be accessed using non negative integers as indexes.

1.1. Creating/Declaring an Array

JavaScript provides us with two main ways to declare an array variable.

  • Using square brackets [].

Arrays can be created using square brackets [] to wrap comma-separated list of elements together. It is the most common way of creating/declaring arrays in JavaScript.

For instance, here is an example that creates countries array that hold string elements.

// Creating an array of countries using square brackets

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

To create an empty array, you use the square brackets without necessarily specifying any element as shown below;

// Creating an empty array using square brackets

let countries = [];
  • Using the Array constructor.

This method of array declaration is not common compared to the use of square brackets.

It can be used as shown below;

// Creating an empty array using Array constructor

let countries = new Array();

If you happen to know the number of elements the array will hold, you can create an array with an initial size as shown below;

// Creating an array with defined number of elements
let countries = Array(5);

To create an array and initialize it some known elements, you can pass the elements as comma separated list into the Array() constructor as shown below;

// Creating array and initializing elements

let countries = new Array('Kenya', 'Uganda', 'South Africa', 'Nigeria', 'Ghana');

1.2. Accessing JavaScript Array elements

JavaScript arrays are zero indexed like I had mentioned before. Meaning, the first element starts at index 0, the second element starts at index 1 and so on.

Therefore, to access an element in an array, you have to specify the index of an element in the square brackets [].

This can be done as shown below;

arrayName[index];

Here is how to access elements of the countries array:

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

console.log(countries[0]); // Output: Kenya
console.log(countries[1]); // Output: Uganda
console.log(countries[2]); // Output: South Africa
console.log(countries[3]); // Output: Nigeria

To change the value of an element, you assign that value to the element as shown below;

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

countries[2] = 'Botswana';

console.log(countries); // Outputs: ['Kenya', 'Uganda', 'Botswana', 'Nigeria']

Getting array size

To get the size of an array, we use the length property of an array which then returns the number of elements within the array. Here is how to use the length property.

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

console.log(countries.length); // Outputs: 4

1.3. Basic operations Arrays

In this section, we’ll get to learn some basic operations that you can perform on an array elements.

They include the following:

  • Adding an element to an array

To add an element to the end of an array, you use the push() method, which looks something like this:

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

countries.push('Tanzania');

console.log(countries); // Outputs: ['Kenya', 'Uganda', 'South Africa', 'Nigeria', 'Tanzania']
  • Adding an element to the beginning of an array

To add an element at the beginning of an array, you use the unshift() method which looks something like this.

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

countries.unshift('Tanzania');

console.log(countries); // Outputs: ['Tanzania', 'Kenya', 'Uganda', 'South Africa', 'Nigeria']
  • Removing an element from the end of an array

To remove an element from the end on an array, you use the pop() methods. Popping reduces the length of an array.

It looks something like this:

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

let lastCountry = countris.pop();

console.log(lastCountry); // Outputs: Nigeria
  • Removing an element from the beginning of an array

To remove an element from the beginning of an array, you use the shift() methods. Shift also reduces the length of an array.

It looks something like this:

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

let firstCountry = countris.shift();

console.log(firstCountry); // Outputs: Kenya
  • Finding an index of an element in the array

To find an index of an element, you use the indexOf() method which looks something like this:

let countries = ['Kenya', 'Uganda', 'South Africa', 'Nigeria'];

let index = countris.indexOf('Kenya');

console.log(index); // Outputs: 0
  • Checking if a value is an array

To check if a value of variable is an array, we use the Arrray.isArray() method, which looks something like this:

console.log(Array.isArray(countries)); // Outputs: true
  1. JavaScript Objects

JavaScript objects can be define as unordered collections of key-value pairs, where keys are strings and values can be any data type.

They offer a convenient way to organize and access related data.

An example of a JavaScript Object is as shown below:

let student = {
  firstName: "Lord",
  lastName: "Abiolla",
  year: 2020,
  course: "Frontend Engineering"
};

The firstName, lastName, year, and course in this case are the properties which can be accessed using the dot notation or bracket notation.

2.1. Object declaration

JavaScript objects can be declared in a few different ways which may include:

  1. Object Literal Syntax

This forms the most common way of creating an object in JavaScript. It involves defining the property and value within a curly braces {}

Example:

let object = {
    key1: 'value1',
    key2: 'value2'
    key3: 'value3'
};
  1. Object Constructor

Object Constructor is another way you can use to create an object but it is not as commonly used as the literal syntax.

Example:

let object  = new Object();
object.key1 = 'value1';
object.key2 = 'value2';
object.key3 = 'value3';
  1. Constructor Function

Constructor functions can mostly be used to create multiple objects with the same structure.

Example:

function MyObject(key1, key2, key3) {
    this.key1 = key1;
    this.key2 = key2;
    this.key3 = key3;
}

let object = new MyObject('value1', 'value2', 'value3');

2.2. Object Properties

JavaScript object properties can be read using either dot notation or bracket notation.

  1. Dot Notation
let object = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

console.log(object.key1); // Outputs: 'value1'
  1. Bracket Notation
let object = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

console.log(object['key1']); // Outputs: 'value1'

Updating Object Properties

JavaScript object properties can also be updated using either dot notation or bracket notation

  1. Dot Notation
let object = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

object.key1 = 'new value1';
console.log(object.key1); // Outputs: 'new value1'

In the above snippet, we are updating the property key1 to a new value ‘new value1‘.

  1. Bracket Notation
let object = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

object['key1'] = 'new value1';
console.log(object['key1']); // Outputs: 'new value1'

In the above snippet, we are updating the property key1 to a new value ‘new value1‘.

2.3. Adding Object Properties

To add properties to an object after it has been created, again we can choose to either use the dot notation or bracket notation.

  1. Dot Notation
let object = {
  key1: 'value1',
  key2: 'value2'
};

object.key3 = 'value3';
console.log(object.key3); // Outputs: 'value3'
  1. Bracket Notation
let object = {
  key1: 'value1',
  key2: 'value2'
};

object['key3'] = 'value3';
console.log(object['key3']); // Outputs: 'value3'

In the above examples, we are adding a new property key3 to the object.

2.4. Deleting Object Properties

In JavaScript, properties can be deleted from an object using the delete operator.

This can be done as demonstrated below.

let object = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

delete object.key1;
console.log(object.key1); // Outputs: undefined

Above here, we are deleting the property key1 from the object, object. After deletion operation, when we try to access the object.key1, it returns undefined because the property no longer exists.

2.5. Checking if a Property Exists

We can always check if a property exists in an object in several ways as listed below.

  1. Using the in operator

The in operator enables us iterate through an object and if it finds an occurrence of our property, it returns true.

Example:

let object = {
  key1: 'value1',
  key2: 'value2'
};

console.log('key1' in object); // Outputs: true
console.log('key3' in object); // Outputs: false
  1. The hasOwnProperty method

This is a method which returns true if the object has the specified property as its own property, and that the property is not inherited.

Here is how it works:

let object = {
  key1: 'value1',
  key2: 'value2'
};

console.log(object.hasOwnProperty('key1')); // Outputs: true
console.log(object.hasOwnProperty('key3')); // Outputs: falseobj
  1. Direct Property Access.

This is another way of checking if a property exists by checking if the property value is undefined.

This method can sometimes give false negative if the property exists in the object but has its value set as undefined.

let object = {
  key1: 'value1',
  key2: 'value2'
};

console.log(object.key1 !== undefined); // Outputs: true
console.log(object.key3 !== undefined); // Outputs: false

2.6. Iterating Over Object Properties

To iterate simply means to move through the object. We can iterate/move through the properties of an object using a for...in loop as shown below;

let object = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

for (let key in object) {
  if (object.hasOwnProperty(key)) {
    console.log(key + ': ' + object[key]);
  }
}

/*
    key1: value1
    key2: value2
    key3: value3
*/

The for ... in loop iterates over each property in the object, and the hasOwnProperty method ensures that the property belongs to the object itself and not its prototype.

At the end of the iterations, we get an output as shown below the code snippet.

2.7. JavaScript Object Methods

Methods in objects are functions that are stored within the object as object properties. The method name becomes the property while the body of the function is the value.

let object = {
  property1: 'value1',
  property2: 'value2',
  method: function() {
    console.log('This is a method!');
  }
};

// Call the method
object.method(); // Outputs: 'This is a method!'

method in this case is a method/function within the object object. You can call the method using the object name followed by the method name.

Moreover, you can use the this keyword in methods to refer to the object. This can be done as follows.

let object = {
  property1: 'value1',
  property2: 'value2',
  method: function() {
    console.log('Property1 is ' + this.property1);
  }
};

// Call the method
object.method(); // Outputs: 'Property1 is value1'

this.property1 within the method refers to the property1 of the object object.

  1. ES6+ Features

ES basically means ECMA Script. It is a standard for scripting languages. Meaning, when writing JavaScript, it is basically an implementation of ECMA Script.

The current version of ECMA Script is ES6 and is not supported in some other browsers.

In order to translate your code to ES5 which is compatible with most browsers, we use a transpiler which converts ES6 code to ES5. Examples of those include; Babel, Google Chrome Canary, which is google’s developing browser.

3.1. let and const

Before advent of ES6, var was the way to declare variable. Issues therefore started arising with the declaration of var and that is why it was necessary for new ways to declare variables to emerge

First let understand var and it’s problems.

  • Scope of var

Scope basically means where these variables are available for use. var declarations are globally scoped or function/locally scoped.

The scope is global when a var variable is declared outside a function. Meaning, any variable declared with var outside a function block is available for use in the whole window.

var is function scoped when it’s declared within a function. Meaning, the variable is available and can be accessed only within that function.

Here is an example to understand this further:

var greet = "Hello World";

    function newFunction() {
        var hello = "Hello";
    }

In the above snippet, greet is globally scoped because it exists outside a function while hello is function scoped. Therefore, we cannot access the variable hello outside of a function.

If we try accessing the hello variable outside the function, we get a not defined error as shown bellow.

    var greet = "hey hi";

    function newFunction() {
        var hello = "hello";
    }
    console.log(hello); // error: hello is not defined

var variables can also be re-declared and updated within the same scope without getting an error. This can be done as shown.

 var greeter = "hey hi";
 var greeter = "say Hello instead";

Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.

Meaning, if we do this:

    console.log (greeter);
    var greeter = "say hello"

it is interpreted as this:

 var greeter;
 console.log(greeter); // greeter is undefined
 greeter = "say hello"

so var variables are hoisted to the top of their scope and initialized with a value of undefined.

So, what exactly is the problem of var? Let’s use an example:

  var greeter = "hey hi";
    var times = 4;

    if (times > 3) {
        var greeter = "say Hello instead"; 
    }

    console.log(greeter) // "say Hello instead"

So, since times > 3 returns true, greeter is redefined to "say Hello instead". While this is not a problem if you knowingly want greeter to be redefined, it becomes a problem when you do not realize that a variable greeter has already been defined before.

If you have used greeter in other parts of your code, you might be surprised at the output you might get. This will likely cause a lot of bugs in your code. This is why let and const are necessary.

Let is now preferred for variable declaration. It's no surprise as it comes as an improvement to var declarations. It also solves the problem with var that we just covered. This is so because:

let is block scoped: A block is a chunk of code bounded by {}. i.e., the code lives in the curly braces. Therefore, a variable declared with let in only available for use within that block. Example:

  let greeting = "say Hi";
   let times = 4;

   if (times > 3) {
        let hello = "say Hello instead";
        console.log(hello);// "say Hello instead"
    }
   console.log(hello) // hello is not defined

Here, using hello outside its block (the curly braces where it was defined) returns an error because let variables are block scoped.

Furthermore, let can be updated but not re-declared as shown below:

let greeting = "say Hi";
greeting = "say Hello instead";

but this will return an error.

let greeting = "say Hi";
let greeting = "say Hello instead"; // error: Identifier 'greeting' has already been declared

If a variable is declared more than once with the same name but in different scopes, there will be no error. Let’s take an example:

    let greeting = "say Hi";
    if (true) {
        let greeting = "say Hello instead";
        console.log(greeting); // "say Hello instead"
    }
    console.log(greeting); // "say Hi"

There is no error because both instances are treated as different variables since they have different scopes. This fact makes let a better choice than var, and you as the programmer doesn’t have to bother if you have used a name for a variable before as a variable exists only within its scope.

For hoisting, let variables are hoisted to the top. But unlike var which is initialized as undefined, let keyword is not initialized and if you try to use a let variable before declaration, you’ll get a Reference Error.

Const variables maintain constant values, and share some similarities with let declarations. They can be accessed within the block they were declared, but cannot be updated or re-declared.

This further means that every const declaration must be initialized at the time of declaration.

While a const object cannot be updated, the properties of this object can be updated. Therefore, if we declare a const object as this:

    const greeting = {
        message: "say Hi",
        times: 4
    }

we can do this:

 greeting.message = "say Hello instead";

Differences between const, let, and var

  1. var declarations are globally scoped or function scoped while let and const are block scoped.

  2. var variables can be updated and re-declared within its scope; let variables can be updated but not re-declared; const variables can neither be updated nor re-declared.

  3. All the three are hoisted at the top of their scope. But while var are initialized to undefined, let and const variables are not initialized.

  4. While var and let can be declared without being initialized, const must be initialized during declaration.

    3.2. Arrow function

ES6 arrow functions provide an alternative way to write a shorter syntax compared to the regular function expressions. The following example defines a function expression that returns the sum of two numbers:

let add = function (x, y) {
  return x + y;
};

console.log(add(10, 20)); // 30

The following example is equivalent to the above add() function expression but uses an arrow function instead:

let add = (x, y) => x + y;

console.log(add(10, 20)); // 30;

Key concepts of Arrow function

  1. Arrow functions do not have the function keyword, they use => instead of the function keyword.

     // Traditional function expression
     const greet = function(name) {
       return "Hello, " + name;
     };
    
     // Arrow function expression
     const greet = (name) => "Hello, " + name;
    
  2. Single parameters in arrow functions do not need parenthesis. Zero or more than one parameters need the parenthesis though.

     // Single parameterized arrow function
     const double = x => x * 2;
    
     // Zero or more than one parameterized arrow function
     const sayHi = () => "Hi!";
     const multiply = (a, b) => a * b;
    
  3. JavaScript doesn’t allow a line break between the parameter definition and the arrow (=>) in an arrow function.

    For example:

     // This code causes a SyntaxError:
     let multiply = (x,y) 
     => x * y;
    
     // However, this code works perfectly fine
     let multiply = (x,y) => 
     x * y;
    
     // OR
     let multiply = (
       x,
       y
     ) => 
     x * y;
    

Arrow vs. Regular functions

FeatureArrow FunctionRegular Function
SyntaxConcise (with =>)Verbose (Uses function)
this bindingLexical (Outer scope)Dynamic (this depends on how its called.)
Can be used with newNoYes
Has arguments objectNoYes
Common use caseCallbacks, Array methodsConstructors, methods, complex logic.

3.3. Template Literals

Template literals are literals delimited with backtick(`) characters, allowing for multi-line strings, string interpolation with embedded expressions, and special constructs called tagged templates.

They can contain placeholders, indicated by the dollar sign and curly braces (`${expression}` ). The expressions in the placeholders and the text between them get passed to a function.

let name = "Lord";
let age = 27;
let greeting = `Hello, my name is ${name} and I am ${age} years old.`;

console.log(greeting); // Outputs: "Hello, my name is Lord and I am 27 years old."

Here, name and age are variables. The template literal is defined using backticks, and the variables are embedded in the string using `${}` syntax. The resulting string is stored in the greeting variable and then logged to the console.

The recommended way to format strings in JavaScript is by using Template Literals(Template Strings) introduced in ECMAScript 6 (ES6). Template Literals allow you embed expressions, create multi line strings, and use string interpolation features, making them a powerful tool for string formatting.

3.4. Destructuring

Destructuring in JavaScript is a syntax that allows one to unpack values from arrays or properties from objects, into distinct variables.

For example:

let fruits = ['apple', 'banana', 'cherry'];

let [fruit1, fruit2, fruit3] = fruits;

console.log(fruit1); // logs 'apple'
console.log(fruit2); // logs 'banana'
console.log(fruit3); // logs 'cherry'

In this example, the array fruits is destructured into three new variables. The first element of the array is assigned to fruit1, the second to fruit2, and the third to fruit3

Destructuring also allows you skip over elements that you do not need.

let [fruit1, , fruit3] = fruits;

console.log(fruit1); // logs 'apple'
console.log(fruit3); // logs 'cherry'

The second element of the array is skipped and not assigned to any variable.

You can also destructure objects as follows:

let person = {
    firstName: 'John',
    lastName: 'Doe'
};

let { firstName, lastName } = person;

console.log(firstName); // 'John'
console.log(lastName); // 'Doe'

In the above example, we declare two variables firstName and lastName and assign them properties of the person object in the same statement.

It is possible to separate the declaration and assignment. However, you must surround the variables in parentheses. If you don’t use the parentheses, the JavaScript engine will interpret the left hand side as a block and throw a syntax error.

When you assign a property that does not exist to a variable using the object destructuring, the variable is set to undefined. For example:

let { firstName, lastName, middleName } = person;
console.log(middleName); // undefined

In this example, the middleName property doesn’t exist in the person object, therefore, the middleName variable is undefined.

3.5. Spread and Rest Operators

Spread operator

ES6 provides us with a new operator called spread operator that consists of three dots ( . . . ). The spread operator allows us to spread out elements of an iterable object such as an array, map, or set. It allows an iterable such as an array expression or string to be expanded in place where zero or more arguments or elements are expected.

Spread operator can be used to create new array and combine to another array:

let fruits = ['apple', 'banana', 'cherry'];
let moreFruits = [...fruits, 'date', 'elderberry'];

console.log(moreFruits); // logs ['apple', 'banana', 'cherry', 'date', 'elderberry']

Here, we are using spread operator . . . fruits to expand the elements of fruits array into individual elements. The moreFruits array is a new array that contains all the elements of the fruits array, followed by date, and elderberry.

You can also use the spread operator to combine/concatenate two arrays as shown below:

let fruits1 = ['apple', 'banana'];
let fruits2 = ['cherry', 'date'];
let allFruits = [...fruits1, ...fruits2];

console.log(allFruits); // logs ['apple', 'banana', 'cherry', 'date']

...fruits1 and ...fruits2 expand the elements of the fruits1 and fruits2 arrays into individual elements. The allFruits array is a new array that contains all the elements of the fruits1 and fruits2 arrays.

The spread operator also enables us copy an array as shown below:

let fruits1 = ['apple', 'banana'];
let fruits2 = [...fruits1];

console.log(fruits2); // logs ['apple', 'banana']

Rest operator

Rest operator also has three dots just like spread operator, but it’s the opposite of spread operator. Since spread operator expands array into it’s individual element, the rest operator collects multiple elements and collects them into a single array element.

You can think of it as a vacuum that “sucks up the rest” of the items into a box (array).

Rest operator is commonly used in function parameters, array destructuring, and object desructuring.

When you do not know how many arguments a function will receive, the rest operator lets you collect all of them into one array as shown below:

function sum(...numbers) {
  console.log(numbers); // [1, 2, 3, 4]
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4)); // Output: 10

. . . numbers collect all arguments passed to sum() into an array called numbers, then it allows you to loop or reduce them easily. Without the rest operator, you’ll need special arguments object, which is less flexible and not available in arrow functions.

Rest operator can also be used to destructure/break an array to capture remaining element as shown below:

const fruits = ["apple", "banana", "mango", "orange"];

const [first, second, ...others] = fruits;

console.log(first);   // "apple"
console.log(second);  // "banana"
console.log(others);  // ["mango", "orange"]

first takes the first element, second takes the second element, and . . . others takes the rest into a new array.

In object destructuring, rest operator is also used to separate some properties from the rest in an object. For example:

const user = {
  name: "Alice",
  age: 25,
  email: "alice@example.com",
  location: "Nairobi"
};

const { name, ...otherDetails } = user;

console.log(name);          // "Alice"
console.log(otherDetails);  // { age: 25, email: "alice@example.com", location: "Nairobi" }

Here, name is taken out, . . . otherDetails collects the rest of the properties.

10
Subscribe to my newsletter

Read articles from Lord Abiolla directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Lord Abiolla
Lord Abiolla

Passionate Software Developer with a strong enthusiasm for data, technology, and entrepreneurship to solve real-world problems. I enjoy building innovative digital solutions and currently exploring new advancements in data, and leveraging my skills to create impactful software solutions. Beyond coding, I have a keen interest in strategic thinking in business and meeting new people to exchange ideas and collaborate on exciting projects.