When do you know “enough” Vanilla JS to be able to move onto React?

Jeff P
29 min readOct 18, 2023

--

This question is definitely up for debate, but in my opinion, you should definitely be aware of the fundamentals of vanilla JS before attempting to learn React, or else it just won’t make sense.

The following are a hole list of questions that you should be able to answer, and will also serve as a “memory jog” for myself, as I spent a lot of time studying JavaScript so I want to make sure I don’t forget it!

Q1. What is a template literal?

A template literal, often denoted by backticks (`), is a feature in JavaScript that allows you to create strings with embedded expressions.

e.g.

const variable = 'world';
const greeting = `Hello, ${variable}!`;

or

const x = 5;
const y = 10;
const result = `The sum of ${x} and ${y} is ${x + y}.`;

Q2. What is type coercion?

In JavaScript, if you try to add a string and a number, JavaScript will implicitly convert the number to a string and concatenate the two values:

var x = "5";
var y = 10;
var result = x + y; // result is "510"

Examples of explicit type coercion include using functions like parseInt() or parseFloat() in JavaScript to convert strings to numbers:

var x = "5";
var y = 10;
var result = parseInt(x) + y; // result is 15

Q3. How does a switch statement work?

A switch statement requires an expression that evaluates to a value. This expression is placed within the parentheses after the switch keyword.

var day = "Monday";

switch (day)

You must provide one or more case labels within the switch statement. These labels define the values or conditions against which the expression is compared. Each case label is followed by a colon :

It is important to note that in a switch statement, the case expressions should be constant values, not conditions.

var day = "Monday";

switch (day) {
case "Monday":

Within each case, you write the code that should execute when the expression matches the case label.

var day = "Monday";

switch (day) {
case "Monday":
console.log("It's Monday.");

After each case block, it’s common to use a break statement to exit the switch statement. This prevents the code from falling through to subsequent case blocks.

var day = "Monday";

switch (day) {
case "Monday":
console.log("It's Monday.");
break;

Here is an example of a complete switch statement…

var day = "Monday";

switch (day) {
case "Monday":
console.log("It's Monday.");
break;
case "Tuesday":
console.log("It's Tuesday.");
break;
case "Wednesday":
console.log("It's Wednesday.");
break;
default:
console.log("It's some other day.");
}

Note the “default” case is optional, and handles all other cases not explicitly specified, which acts acts as a catch-all.

Q4. What is the Ternary operator?

The ternary operator, often referred to as the conditional operator, is a concise way to write conditional expressions. It allows you to evaluate a condition and return one of two values based on whether the condition is true or false. For example:

var age = 18;
var isAdult = (age >= 18) ? "Yes" : "No"; // results in "Yes"

var age = 17;
var isAdult = (age >= 18) ? "Yes" : "No"; // results in "No"

Q5. What is ‘strict mode’?

Strict mode is a feature in JavaScript that was introduced in (ES5) to help developers write more reliable and error-free code by catching and preventing common coding mistakes and “unsafe” actions.

To enable strict mode, you can add the following line at the beginning of a script or a function:

"use strict";

An example of how this would be useful to catch errors is as follows:

// Non-strict mode

function nonStrictFunction() {
// the following variable is NOT declared with var/const/let
var1 = "Hello, strict mode!";
return var1;
}

console.log(nonStrictFunction());
// Outputs: "Hello, strict mode!"

// Enabling strict mode
"use strict";

function strictFunction() {
// the following variable is NOT declared with var/const/let
var2 = "Hello, strict mode!";
return var2;
}

console.log(strictFunction());
// Throws a ReferenceError

Q6. What is the difference between a function expression and a function declaration?

Function declarations are hoisted, which means they are moved to the top of their containing scope during the compilation phase. You can call a function before its actual declaration in the code, and it will work.

Function expressions are not hoisted, so you cannot call them before they are defined.

// Function Declaration

function add(a, b) {
return a + b;
}


// Function Expression (named)

const multiply = function multiply(a, b) {
return a * b;
};

// Function Expression (Anonymous)
const subtract = function(a, b) {
return a - b;
};

Q7. How do arrow functions work?

An arrow function in JavaScript is a concise way to write functions. If the function body consists of a single expression, you can omit the curly braces {} and the return keyword, and the result of the expression will be implicitly returned.

// Regular function expression
const add = function(a, b) {
return a + b;
};

// Arrow function
const add = (a, b) => a + b;

If the arrow function has a single parameter, you can even omit the () around the parameter if you want to:

// birthYear passed as single parameter - With parentheses
const calcAge = (birthYear) => 2037 - birthYear;

// birthYear passed as single parameter - Without parentheses
const calcAge = birthYear => 2037 - birthYear;

Q8. Difference between dot notation and bracket notation?
With dot notation, you already know the name of the property, and it’s a valid JavaScript identifier:

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

console.log(person.firstName); // Outputs: 'John'

Bracket notation is more flexible and can be used when you need to access properties with dynamic or non-standard names.

const person = {
'first name': 'John',
'last name': 'Doe'
};

console.log(person['first name']); // Outputs: 'John'

const propertyName = 'first name';
console.log(person[propertyName]); // Outputs: 'John'

Q9. What is an object method?

An object method is a function that is defined as a property of an object. Methods are essentially functions that are associated with an object and can perform actions or provide functionality related to that object.

const person = {
firstName: "John",
lastName: "Doe",
// object method
fullName: function() {
return this.firstName + " " + this.lastName;
}
};

console.log(person.fullName()); // Outputs "John Doe"

Q10. What is ‘break’ and ‘continue’ used for in loops?

‘break’ and ‘continue’ are control flow statements used within loops to modify the flow of execution.

The break statement is used to exit a loop prematurely when a certain condition is met. When encountered, it terminates the loop immediately, and the program continues with the code following the loop.

It is commonly used to exit a loop early if a specific condition is satisfied, saving unnecessary iterations.

for (let i = 0; i < 6; i++) {
if (i === 3) {
break; // Exit the loop completely when i equals 3
}
console.log(i);
}
// Output: 0 1 2

The continue statement is used to skip the current iteration of a loop and proceed to the next iteration.

for (let i = 0; i < 6; i++) {
if (i === 3) {
continue; // Skip the current iteration when i equals 3
}
console.log(i);
}
// Output: 0 1 2 4 5

Q11. difference between a for loop and a while loop?

For loops require a counter, and while loops don’t While loops will continue to run until a condition is no longer true.

For loop:

for (let i = 0; i < 5; i++) {
console.log(i);
}
// Output: 0 1 2 3 4

While loop:

let i = 0;
while (i < 5) {
console.log(i);
i++;
}
// Output: 0 1 2 3 4

Q12. What is global scope, function scope and block scope?

Scopes are like containers for your code, where you can put your variables and functions.

Variables and functions declared in the global scope are accessible from anywhere in your code.

const globalVariable = 10;

function sayHello() {
console.log("Hello, world!");
}

When you declare a variable or function inside a function, it’s only accessible inside that function (function scope)

function myFunction() {
const insideVariable = 5;
console.log(insideVariable); // You can use insideVariable here
}

console.log(insideVariable); // You can't use insideVariable here

Variables declared in a block scope are only accessible within that block. For example:

if (true) {
const insideBlock = "I'm inside!";
console.log(insideBlock); // You can use insideBlock here
}

console.log(insideBlock); // You can't use insideBlock here

It’s important to note that only let and const variables can be block-scoped. The var variable is not block scoped, but is instead function scoped, even if the variable itself is inside a block scope.

function example() {
if (true) {
var varVariable = "I am a function-scoped var variable inside an if block";
}

console.log(varVariable); // This works
}

example();
console.log(varVariable); // Error

In the scope chain, a variable can be used in its parent scope, but not by sibling scopes or child scopes.

Q13. What is the ‘this’ keyword?

In JavaScript, the this keyword is a special identifier that refers to the current context or object. The value of this depends on how and where it is used within your code.

In the global scope (outside of any function or object), this refers to the global object, which is window in a web browser or global in Node.js.

console.log(this === window); // In a web browser, this is true
  • Inside a regular function (not an arrow function), the value of this depends on how the function is called.
  • If the function is called as a method of an object (i.e. a function inside the object), this refers to that particular object.
  • If the function is called without any specific context, this refers to the global object (window in a web browser).
const person = {
name: "John",
// method
sayHello: function() {
console.log(`Hello, my name is ${this.name}`); // i.e. person.name
}
};

person.sayHello(); // "Hello, my name is John"

When you create objects using constructor functions (with the new keyword), this refers to the newly created instance of the object.

function Person(name) {
this.name = name;
}

const john = new Person("John");
console.log(john.name); // "John"

const bob = new Person("Bob");
console.log(bob.name); // "Bob"

Arrow functions behave differently regarding this. They capture the value of this from the surrounding context, meaning that this inside an arrow function retains the value of this from the containing function or context.

const person = {
name: "John",
sayHello: () => {
console.log(`Hello, my name is ${this.name}`);
}
};

person.sayHello(); // "Hello, my name is undefined"

In this case, the arrow function is inside a standard object, and thus the value of ‘this’ is taken from person, which is in the global scope (i.e. ‘this’ === window) therefore in this instance, this.name actually refers to window.name, which doesn’t exist.

Q.14 How are primitives and objects different in terms of memory management?

Primitive values, such as numbers, strings, booleans, null, undefined, and symbols, are typically stored on the stack. The stack is a region of memory that manages function execution context and stores primitive data types.
When you create a primitive variable, its actual value is stored directly in the stack memory.

When you pass a primitive value as a function argument or assign it to another variable, you are copying the actual value. Changes to one copy do not affect the others.

let age = 30; // The value 30 is stored directly on the stack.

When you pass an object as a function argument or assign it to another variable, you are passing the reference, not a copy of the object itself. This means changes made to the object through one reference affect all references to the same object.

let person1 = { name: "John" };
let person2 = person1; // Both 'person1' and 'person2' point to the same object in the heap.
person1.name = "Jane"; // Changes 'name' property in the shared object.
console.log(person2.name); // Outputs "Jane" because 'person2' reflects the change.

When you work with objects, you are working with references to the underlying data, which can lead to different behavior compared to primitives when it comes to assignment and modification.

Q.15 What is “nested” de-structuring?

Nested de-structuring is a technique used to extract values from nested objects and arrays in a concise and structured way. It allows you to de-structure objects or arrays that are themselves properties or elements of other objects or arrays.

nested de-structuring objects:

const person = {
name: 'John',
address: {
city: 'New York',
zip: '10001'
}
};

// nested de-structuring city and zip from address
const { name, address: { city, zip } } = person;

console.log(name); // 'John'
console.log(city); // 'New York'
console.log(zip); // '10001'

nested de-structuring arrays:

const numbers = [1, [2, 3], 4];

// nested de-structuring second and third
const [first, [second, third], fourth] = numbers;

console.log(first); // 1
console.log(second); // 2
console.log(third); // 3
console.log(fourth); // 4

Q.16 Difference between de-structuring and the spread operator

De-structuring allows you to extract values from objects and arrays into separate variables. It can be used for both objects and arrays.
It creates new variables and assigns values to them based on the structure of the object or array being destructured. De-structuring syntax uses patterns on the left side of an assignment.

// a standard array
const numbers = [1, 2, 3];

//de-structuring the array
const [a, b, c] = numbers;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

The spread operator is used to spread or expand the elements of an iterable (e.g., an array or a string, or a set) into a new context, such as another array or function arguments. It is often used to make shallow copies of arrays or objects, combine arrays, and pass variable numbers of arguments to functions.
It does NOT create new variables or assign values to them but rather copies values or elements from one place to another. The spread operator operates on the right side of an assignment or within a function argument list.

// a standard array
const arr1 = [1, 2, 3];

// spreading the array values out
const arr2 = [...arr1, 4, 5];
console.log(arr2); // [1, 2, 3, 4, 5]

// a standard string
const str = "Hello";

// spreading out the string
const spreadStr = [...str];
console.log(spreadStr); // ['H', 'e', 'l', 'l', 'o']

Q.17 Difference between spread and rest

The spread operator spreads out the iterables, but the rest does the opposite, and in fact collects the variables and bundles them into an array:

// using the rest operator
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // result is 1
console.log(rest) // result is [2, 3, 4, 5]

// using the rest operator
const [monday, ...otherDays] = [1, 2, 3, 4, 5, 6, 7];
console.log(monday); // result is 1
console.log(otherDays) // result is [2, 3, 4, 5, 6, 7]

Q.18 What is short circuit operations?

Short-circuiting is a behavior in JavaScript where the evaluation of an expression stops as soon as the result is determined. This behavior is often used with the logical OR (||) and logical AND (&&) operators to write concise and efficient code.

using || (first truthy value, or if no truthy value exists, it will be the default, which is the last falsey value)

isAdmin || logOutUser();
// If isAdmin is falsy, logOutUser() will be called.

using &&(first falsey value, or if no falsey value exists, it will be the default, which is the last truthy value)

isLoggedIn && showDashboard();
// If isLoggedIn is truthy, showDashboard() will be called.

There is also the nullish coalescing operator (??) which specifically checks for null or undefined values and short-circuits only in those cases. So in this case

function greet(name) {
name = name ?? 'Guest';
console.log(`Hello, ${name}!`);
}
greet(); // Outputs "Hello, Guest!" because no name is passed in (undefined).

Q.19 The difference between a for loop and a for… of loop

The “for…of loop” is more concise and is ideal for simple iteration over the elements of iterable objects like arrays, strings, maps, and sets. It automatically handles the iteration details for you.

A traditional “for loop” in JavaScript has the following syntax:

for (initialization; condition; iteration) {
// code to be executed in each iteration
}

Example of a standard for loop:

const numbers = [1, 2, 3, 4, 5];
// standard for loop
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]); // returns 1 2 3 4 5
}

The syntax of a “for…of loop” is simpler. It has the following syntax:

for (const element of iterable) {
// code to be executed in each iteration with 'element'
}

Example of a “for…of loop” iterating over an array:

const numbers = [1, 2, 3, 4, 5];
for (const number of numbers) {
console.log(number); // returns 1 2 3 4 5
}

Q.20 Benefits of enhanced object literals

Objects have been “enchanced” since ES6 to make working with objects in JavaScript more concise and expressive. These enhancements simplify the process of creating and manipulating objects. The key features of enhanced object literals are as follows:

Property Shorthand:

You can now omit the property name if the variable name you want to assign as a property has the same name:

const firstName = 'John';
const lastName = 'Smith';
const age = 30;

// new "enhanced" object
const person = {
firstName,
lastName,
age
};

// Equivalent to the old way:
// const person = {
// firstName: firstName,
// lastName: lastName,
// age: age
// };

Method Shorthand:

You can define methods inside objects using a shorter syntax, omitting the colon and the word ‘function’:

// enhanced syntax of methods in objects
const calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
}
};

// old way
var calculator = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
};

Computed Property Names:

You can now use expressions inside square brackets to compute property names dynamically:

const dynamicKey = 'color';
const car = {
brand: 'Toyota',
[dynamicKey]: 'blue' // Property name is dynamically computed
};

Object Method Bindings:

You can define functions within objects without using the function keyword, and these functions automatically bind to the object:

// new way
const person = {
name: 'Alice',
greet() {
console.log(`Hello, ${this.name}!`);
}
};

// old way
var person = {
name: 'Alice',
greet: function() {
console.log('Hello, ' + this.name + '!');
}
};

Q.21 Benefit of optional chaining

Optional Chaining helps prevent errors and simplify code when dealing with potentially missing or nested properties in objects and deeply nested data structures.

Optional chaining is particularly useful when working with data from external sources, APIs, or user input, where the structure of the data may be incomplete or unpredictable.

const person = {
name: 'Alice',
address: {
city: 'New York'
}
};

const city = person.address?.city;
// city will be 'New York' if the address object and city property both exist.
// If address happens to be null or undefined,
// city will also be undefined, and no error occurs.
// remember that undefined is NOT an error!

Q.22 Object.keys and Object.values

The Object.keys() method takes an object obj as its argument and returns an array of the object’s own enumerable property names (keys).

The Object.values() method, like Object.keys(), takes an object obj as its argument and returns an array. However, this array contains the values of the object's own enumerable properties.

const obj = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj);
const values = Object.values(obj);

console.log(keys); // ['a', 'b', 'c']
console.log(values); // [1, 2, 3]

Q.23 What are JavaScript sets?

A JavaScript Set is a built-in data structure introduced in (ES6) that represents a collection of unique values. Sets are used to store values, and each value within a Set can occur only once; duplicate values are automatically removed.

You typically create a new, empty set, and then add to the set:

const mySet = new Set();

mySet.add(1);
mySet.add(2);
mySet.add(1); // This does not create a duplicate value
mySet.add(3);

console.log(mySet); // Set { 1, 2, 3 }

You can determine the number of elements in a Set using the size property.

const mySet = new Set([1, 2, 3]);
console.log(mySet.size); // 3

You can add values to a Set using the add() method and remove values using the delete() method.

const mySet = new Set();

mySet.add(1);
mySet.add(2);

mySet.delete(1); // Removes the value 1 from the Set

console.log(mySet); // Set { 2 }

You can check whether a value exists in a Set using the has() method.

const mySet = new Set([1, 2, 3]);

console.log(mySet.has(2)); // true
console.log(mySet.has(4)); // false

Sets are often used to remove duplicates from arrays by converting the array to a Set, and then back to an array.

Q.24 What are JavaScript maps?

A JavaScript Map is a built-in data structure introduced in (ES6) that allows you to store key-value pairs, where each key is associated with a value. Maps are similar to objects in that they can be used to store and retrieve data, but they have some key differences and advantages.

You use set and get to add to, and retrieve from maps:

const myMap = new Map();

// string as key
myMap.set('name', 'Alice');
// number as key (not possible in objects)
myMap.set(42, 'Answer to the Ultimate Question');

console.log(myMap.get('name')); // 'Alice'
console.log(myMap.get(42)); // 'Answer to the Ultimate Question'

As with sets, you can use “has” to check for the presence of data.

const myMap = new Map();

myMap.set('name', 'Alice');

console.log(myMap.has('name')); // true
console.log(myMap.has('age')); // false

You can determine the number of key-value pairs in a Map using the size property.

const myMap = new Map([
['name', 'Alice'],
[42, 'Answer to the Ultimate Question']
]);

console.log(myMap.size); // 2

Q.25 What do the common string methods do?

concat(): Combines two or more strings and returns a new string.

const str1 = 'Hello';
const str2 = 'World';
const result = str1.concat(' ', str2);
console.log(result); // Output: Hello World

indexOf(): Returns the index of the first occurrence of a substring within a string, or -1 if not found.

const sentence = 'The quick brown fox jumps over the lazy dog';
const index = sentence.indexOf('fox');
console.log(index); // Output: 16

charAt(): Returns the character at a specified index in a string.

const text = 'JavaScript';
const character = text.charAt(2);
console.log(character); // Output: v

substring(): Returns a portion of the string between two specified indices, which are indexStart and indexEnd. The substring() method swaps its two arguments if indexStart is greater than indexEnd, meaning that a string is still returned.

const text = 'Hello, World';
const string1 = text.substring(0, 5);
const string2 = text.substring(5, 0);
console.log(string1); // Output: Hello
console.log(string2); // Output: Hello

slice(): Returns a portion of the string based on start and end indices. The crucial difference between slice and substring is that if the indexStart and indexEnd are flipped, slice will return an empty string

const text = 'Hello, World';
const string1 = text.slice(0, 5);
const string2 = text.slice(5, 0);
console.log(string1); // Hello
console.log(string2); // [empty]

replace(): Replaces a specified substring with another string.

const sentence = 'I love JavaScript!';
const replaced = sentence.replace('JavaScript', 'coding');
console.log(replaced); // Output: I love coding!

replaceAll(): Replaces all occurrences of a specified substring with another string.

const sentence = 'I love JavaScript, JavaScript is great!';
const replaced = sentence.replaceAll('JavaScript', 'coding');
console.log(replaced); // Output: 'I love coding, coding is great!'

trim(): Removes whitespace from both ends of a string.

const text = '   Hello, World   ';
const trimmed = text.trim();
console.log(trimmed); // Output: Hello, World

includes(): Checks if a string contains a specified substring and returns a boolean.

const sentence = 'The quick brown fox jumps over the lazy dog';
const containsFox = sentence.includes('fox');
console.log(containsFox); // Output: true

padStart(): Pads the string with a specified character until the output (including the string itself) it reaches a specified length.

const text = '42';
const paddedText = text.padStart(5, '0');
console.log(paddedText); // Output: 00042

padEnd(): Also pads the string with a specified character until the output (including the string itself) it reaches a specified length.

const text = '42';
const paddedText = text.padEnd(5, '0');
console.log(paddedText); // Output: 42000

search(): Searches for a substring or regular expression pattern in a string and returns the index of the first match.

const text = 'The quick brown fox jumps over the lazy dog';
const index = text.search(/fox|dog/); // regex
console.log(index); // Output: 16

split(): Used to split a string into an array of substrings based on a specified separator. It takes one argument, which is the separator, and splits the string wherever it finds that separator. The result is an array of substrings from the original string. It’s important to note that the separator itself is not included as an element in the resulting array.

const str = "Hello, World";

const parts = str.split("o");

console.log(parts); // ['Hell', ', W', 'rld']

If you include nothing between quotes (not even a space) then it will treat every character as an array element. Essentially this method lets you convert a string to an array of characters.

const str = "Hello, World";

const parts = str.split(""); // no gap between quotes

console.log(parts);
// ['H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd']

If you don’t supply anything to the .split() method, it will treat the entire input string as one element in the resulting array. Each character in the string will not become a separate element in the array, and nothing will change.

const str2 = "Hello, World";
const parts2 = str2.split(); // no seperator supplied

console.log(parts2); // Output: [ 'Hello', 'World' ]

join() is used to join the elements of an array into a string, using a specified separator between each element.

const arr = ['apple', 'banana', 'cherry'];
const joinedStr = arr.join(', '); // Join the array elements with a comma and a space

console.log(joinedStr); // Output: "apple, banana, cherry"

If you don’t supply a separator to the .join() method in JavaScript, it will join the elements of the array into a single string without any separator between them. In other words, the elements will be concatenated directly without any spaces or characters in between.

const arr = ['apple', 'banana', 'cherry'];
const joinedStr = arr.join();

console.log(joinedStr); // Output: "applebananacherry"

Q.26 What is a higher-order function?

Higher-order function is a function that takes one or more functions as arguments and/or returns a function as its result.

// This is a higher-order function that takes a function as an argument
function operateOnNumbers(a, b, operation) {
return operation(a, b);
}

function add(a, b) {
return a + b;
}

function subtract(a, b) {
return a - b;
}

console.log(operateOnNumbers(5, 3, add)); // Passing a function as an argument
console.log(operateOnNumbers(5, 3, subtract)); // Passing a different function as an argument

Q.27 What is ‘call’, ‘apply’ and ‘bind’?

If you’re in strict mode (“use strict”;), calling a function without using .call or .apply will set the this context to undefined. This behavior is designed to help catch potential errors and make JavaScript code more predictable.

// strict mode

"use strict";

function greet(name) {
console.log(`Hello, ${name}! My name is ${this.name}.`);
}

const person = { name: "John" };

greet("Alice"); // Cannot read property 'name' of undefined
// not using strict mode

function greet(name) {
console.log(`Hello, ${name}! My name is ${this.name}.`);
}

const person = { name: "John" };

greet("Alice");
// In a browser, it will run without errors, but 'this.name'
// will refer to the global object ('window' in a browser).
// bad practice!

The call method is a built-in JavaScript method that allows you to invoke a function with a specified this value

function greet(name) {
console.log(`Hello, ${name}! My name is ${this.name}.`);
}

const person = { name: "John" };

// The first arg is the value to be used as the 'this' context inside the function.
greet.call(person, "Alice"); // Outputs: Hello, Alice! My name is John.

The apply method is similar to call, but it takes arguments as an array or an array-like object.

const person = {
firstName: "John",
lastName: "Doe",
getFullName: function () {
return this.firstName + " " + this.lastName;
},
};

const anotherPerson = {
firstName: "Alice",
lastName: "Johnson",
};

// Using apply to call the getFullName method of the 'person' object
// with the 'anotherPerson' object as 'this'
const fullName = person.getFullName.apply(anotherPerson);

console.log(fullName); // Outputs: "Alice Johnson"

null can also be passed as the first argument to the apply method when apply expects the first argument to be the value that will be used as the this context inside the sum function.

function sum(a, b) {
return a + b;
}

const numbers = [3, 5];

const result = sum.apply(null, numbers); // Equivalent to sum(3, 5)

console.log(result); // Outputs: 8

In the sum function example, there is no usage of this, so the actual value of the first argument (null in this case) which doesn't have any effect on the behavior of the sum function.

It’s worth noting that the apply method is rarely used nowadays because you can simply use the spread operator to achieve the same thing along with the call method.

function sum(a, b) {
return a + b;
}

const numbers = [3, 5];

const result = sum.call(null, ...numbers); // Equivalent to sum(3, 5)

console.log(result); // Outputs: 8

The bind method in JavaScript is used to create a new function that, when called, has its this value set to a specific context, and it can optionally pre-set some arguments.

const person = {
firstName: "John",
lastName: "Doe",
};

function getFullName(greeting) {
return greeting + " " + this.firstName + " " + this.lastName;
}

const greetJohn = getFullName.bind(person, "Hello");

console.log(greetJohn()); // Outputs: "Hello John Doe"

The primary use case for bind is to create functions with a fixed this context, which is especially useful when you want to pass functions as callbacks or event handlers while ensuring that the this context is preserved correctly.

Using the bind method for event handlers is a common practice in JavaScript, especially when working with DOM (Document Object Model) elements. It allows you to ensure that the this context inside the event handler function refers to a specific object or context, rather than the element that triggered the event. This is particularly useful when you want to access properties or methods of a particular object within the event handler.

const button = document.getElementById("myButton");
const myObject = {
message: "Button clicked!",
showMessage: function () {
alert(this.message);
},
};

// Using bind to set the 'this' context for the event handler
button.addEventListener("click", myObject.showMessage.bind(myObject));

Q.28 What are closures?

A closure is a function that retains access to variables from its outer (enclosing) scope even after the outer function has finished executing. This means that a closure “closes over” its surrounding lexical scope, allowing you to access those variables even when the outer function is no longer in execution.

// outer function
function createCounter() {
let count = 0; // This variable is private and only accessible within the closure

// inner function
function increment() {
count++;
console.log(count);
}

// inner function
function decrement() {
count--;
console.log(count);
}

return {
increment,
decrement
};
}

// assign function to new variable
const counter = createCounter();

counter.increment(); // Output: 1
counter.increment(); // Output: 2
counter.increment(); // Output: 3

counter.decrement(); // Output: 2
counter.decrement(); // Output: 1

closures ensure that functions retain access to variables from their outer (enclosing) scope, even after the outer function has finished executing. This prevents the loss of connections to those variables, allowing functions to access and manipulate them as needed.

Q.29 Common array methods

forEach(): Executes a provided function once for each array element.

const numbers = [1, 2, 3, 4];
numbers.forEach((num) => console.log(num * 2));
// Output: 2, 4, 6, 8

forEach() does not create a new array or modify the original array. It is used for side effects like logging or performing operations on the elements but doesn't produce a new array with the results of those operations. To create a new array you would instead use map().

map(): Creates a new array with the results of calling a provided function on every element in the array

const numbers = [1, 2, 3, 4];
const doubled = numbers.map((num) => num * 2);
// doubled is [2, 4, 6, 8]

filter(): Creates a new array with all elements that pass a test provided by a given function.

const numbers = [1, 2, 3, 4];
const evenNumbers = numbers.filter((num) => num % 2 === 0); i.e. // mod 2
// evenNumbers is [2, 4]

It’s also a great way to quickly filter out truthy (or falsey) values from an array, using the “Boolean” built-in function

// return only truthy values
function bouncer(arr) {

let newArr;
newArr = arr.filter(Boolean);
console.log(newArr);
return arr;
}

bouncer([7, "ate", "", false, 9]);
// output 7, "ate" 9


// return only falsey values
function bouncer(arr) {

let newArr;
newArr = arr.filter(value => !Boolean(value));
console.log(newArr);
return arr;
}

bouncer([7, "ate", "", false, 9]);
// output "", false

reduce(): Applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((accumulator, currentElement) => accumulator + currentElement, 0);
// sum is 10

To break this down further…

An initial value for an accumulator variable is provided. In this case, the initial value provided is 0.

  • During the first iteration, accumulator is 0, and currentElement (numbers[0])is 1. So, 0 + 1 equals 1, and the accumulator becomes 1.
  • During the second iteration, accumulator is 1, and currentElement (numbers[1])is 2. So, 1 + 2 equals 3, and the accumulator becomes 3.
  • During the third iteration, accumulator is 3, and currentElement is 3. So, 3 + 3 equals 6, and the accumulator becomes 6.
  • Finally, during the fourth iteration, accumulator is 6, and currentElement is 4. So, 6 + 4 equals 10, and the accumulator becomes 10.

After processing all elements in the array, the reduce() method returns the final value of the accumulator, which is 10.

find(): Returns the first element in an array that satisfies a provided testing function.

const numbers = [1, 2, 3, 4];
const found = numbers.find((num) => num > 2);
// found = 3, which was the FIRST number in the array that was greater than 2

includes(): Checks if an array includes a certain element and returns atrue or false.

const fruits = ['apple', 'banana', 'cherry'];
const includesBanana = fruits.includes('banana');
// includesBanana is true

every(): Checks if all elements in an array satisfy a provided testing function and returns true if they do, otherwise false.

const numbers = [2, 4, 6, 8];
const allEven = numbers.every((num) => num % 2 === 0);
// allEven is true

some(): Checks if at least one element in an array satisfies a provided testing function and returns true if there's at least one match, otherwise false.

const numbers = [1, 3, 5, 8];
const hasEven = numbers.some((num) => num % 2 === 0);
// hasEven is true

slice(): Creates a new array by extracting a portion of an existing array without modifying the original array. Includes the start value, but does NOT include the end value

const fruits = ['apple', 'banana', 'cherry', 'date'];
const slicedFruits = fruits.slice(1, 3);
// slicedFruits is ['banana', 'cherry']

splice(): Changes the contents of an array by removing, replacing, or adding elements in place.

const fruits = ['apple', 'banana', 'cherry'];
fruits.splice(1, 1, 'orange'); // Removes 'banana' and adds 'orange'
// fruits has now become ['apple', 'orange', 'cherry']

concat(): Combines two or more arrays and returns a new array.

const fruits1 = ['apple', 'banana'];
const fruits2 = ['cherry', 'date'];
const combinedFruits = fruits1.concat(fruits2);
// combinedFruits is ['apple', 'banana', 'cherry', 'date']

join(): Joins all elements of an array into a string, with an optional separator.

const fruits = ['apple', 'banana', 'cherry'];
const joinedString = fruits.join('# ');
// joinedString is 'apple# banana# cherry'

indexOf(): Returns the first index at which a given element can be found in the array, or -1 if it’s not found.

const fruits = ['apple', 'banana', 'cherry'];
const index = fruits.indexOf('cherry');
// index is 2

lastIndexOf(): Returns the last index at which a given element can be found in the array, or -1 if it’s not found.

const fruits = ['apple', 'banana', 'cherry', 'banana'];
const lastIndex = fruits.lastIndexOf('banana');
// lastIndex is 3

reverse(): Reverses the order of elements in an array in place.

const numbers = [1, 2, 3, 4];
numbers.reverse();
// numbers is now [4, 3, 2, 1]

sort(): Sorts the elements of an array in place, optionally with a sorting function.

const numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
numbers.sort(); // Sorts in lexicographic order: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

Q.30 what is prototypal inheritance?

Prototypal inheritance allows objects to inherit properties and methods from other objects. In JavaScript, objects can serve as prototypes for other objects, which means you can create new objects that inherit properties and methods from existing objects.

Prototype Chain: Each object in JavaScript has an internal property called [[Prototype]]. This property points to another object, which is that object's prototype. When you try to access a property or method on an object, JavaScript will first look for it on the object itself. If it doesn't find it, it will then follow the prototype chain and look for it on the prototype object, and so on, until it either finds the property/method or reaches the end of the prototype chain.

Constructor functions are used to create objects with shared properties and methods. These functions typically start with a capital letter by convention. When you create an object using a constructor function, the [[Prototype]] of the new object is set to the prototype property of the constructor function.

function Person(name) {
this.name = name;
}

Person.prototype.sayHello = function () {
console.log(`Hello, my name is ${this.name}`);
};

// set to prototype of Person
const person1 = new Person('Alice');
// also set to prototype of Person
const person2 = new Person('Bob');

person1.sayHello(); // Outputs: "Hello, my name is Alice"
person2.sayHello(); // Outputs: "Hello, my name is Bob"

You can also explicitly create objects with a specified prototype using the Object.create() method.

const parent = {
greet: function () {
console.log('Hello from the parent object!');
},
};

const child = Object.create(parent);
child.greet(); // Outputs: "Hello from the parent object!"

Inheritance: Inheritance occurs when an object inherits properties and methods from its prototype. Any changes made to the prototype object are reflected in all objects that inherit from it.

parent.newProperty = 'I am a new property';

// now check child
child.newProperty; // "I am a new property"

Q.31 What are ES6 classes?

ES6 classes, introduced in (ES6), are a “syntactical sugar” over JavaScript’s existing prototype-based inheritance. They provide a more structured and familiar way to create and work with constructor functions and prototypes, making object-oriented programming in JavaScript more approachable and organized.

In ES6, you can declare a class using the class keyword, followed by the class name. A class typically includes a constructor method and other methods.

class Person {
constructor(name) {
this.name = name;
}

sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}

Class Instances: You can create instances (objects) of a class using the new keyword.

const person = new Person('Alice');
person.sayHello(); // Outputs: "Hello, my name is Alice"

Inheritance: ES6 classes support inheritance through the extends keyword. You can create a subclass (a class that inherits from another class) and override or extend its behavior.

class Student extends Person {
constructor(name, studentId) {
super(name); // Calls the constructor of the parent class
this.studentId = studentId;
}

study() {
console.log(`${this.name} is studying.`);
}
}

const student = new Student('Bob', '12345');
student.sayHello(); // Outputs: "Hello, my name is Bob"
student.study(); // Outputs: "Bob is studying."

Static Methods: You can define static methods on a class, which are methods that are called on the class itself, not on instances of the class.

class MathUtils {
static square(x) {
return x * x;
}
}

const result = MathUtils.square(5); // result is 25

Getters and Setters: ES6 classes also support getter and setter methods for defining computed properties.

class Circle {
constructor(radius) {
this.radius = radius;
}

get diameter() {
return this.radius * 2;
}

set diameter(value) {
this.radius = value / 2;
}
}

const circle = new Circle(5);
console.log(circle.diameter); // 10
circle.diameter = 12;
console.log(circle.radius); // 6

Q.32 Common properties of objects

.length

const myArray = [1, 2, 3, 4, 5];
const arrayLength = myArray.length;
console.log(arrayLength); // Output: 5

const myString = "Hello";
const stringLength = myString.length;
console.log(stringLength); // Output: 5

.name (returns the actual name of the function)

function greet(name) {
console.log(`Hello, ${name}!`);
}

const functionName = greet.name;
console.log(functionName); // Output: "greet"

.constructor

function Person(name) {
this.name = name;
}


const newPerson = new Person("John");
const constructorName = newPerson.constructor.name; // constructor function name
console.log(constructorName); // Output: "Person"

// not to be confused with accessing an objects property, also called "name"
const personName = newPerson.name;
console.log(personName); // Output: "John"

.prototype

function Dog(name) {
this.name = name;
}

Dog.prototype.bark = function() {
console.log(`${this.name} says woof!`);
};

const myDog = new Dog("Buddy");
myDog.bark(); // Output: "Buddy says woof!"

In the example above, myDog does not have its own copy of the bark function. Instead, it accesses and shares the bark function defined in the Dog.prototype. This is one of the benefits of using prototypes in JavaScript, as it allows for efficient memory usage when creating multiple instances of objects.

Q.33 RegExp Object Methods

exec() Method:

Used to search for a specified pattern in a string. It returns an array of information if the pattern is found, or null if no match is found. The returned array includes information about the match and capture groups.

const text = "Hello, my email is example@email.com.";
const regex = /(\w+)@(\w+\.\w+)/; // two regex capture groups -
// before and after @ - (\w+) is the first capture group and
// (\w+\.\w+) is the second capture group
const result = regex.exec(text);

if (result) {
console.log("Match found:");
console.log("Full match:", result[0]); // first element is always the full match
console.log("Username:", result[1]); // capture group #1
console.log("Domain:", result[2]); // capture group #2
} else {
console.log("No match found.");
}

// output
Match found:
Full match: example@email.com
Username: example
Domain: email.com

test() Method:

Checks if a string contains a specified pattern and returns true or false based on whether the pattern is found.

const text = "The quick brown fox jumps over the lazy dog.";
const myRegex = /fox/;
const hasFox = myRegex.test(text);
console.log(hasFox); // Output: true

toString() Method:

Simply returns the string representation of the regex pattern.

const regex = /abc/gi;
const myRegexString = regex.toString();
console.log(myRegexString); // Output: "/abc/gi"

Q.34 Difference between for …of and forEach() ?

for...of: It is a modern iteration construct introduced in ES6. It can be used with any iterable object, including arrays, strings, maps, sets, and more.

You can break out of the loop early using break if a certain condition is met.

forEach(): It is a method available on arrays and is specifically designed for iterating over the elements of an array.

It continues iterating over all elements of the array, even if a condition is met. You cannot break out of it prematurely.

const arr = [1, 2, 3, 4, 5];

// Using for...of
for (const num of arr) {
if (num === 3) {
break; // Breaks out of the loop when num is 3
}
console.log(num);
}


// Using for...of in a function context
function findValue(arr) {
for (const num of arr) {
if (num === 3) {
return num; // Returns 2 and exits the function
}
}
return undefined; // Return a default value if the value is not found
}


// Using forEach
arr.forEach((num) => {
if (num === 3) {
return; // Continues to the next iteration when num is 3
}
console.log(num);
});

Q.35 Loose equality vs strict equality ( == vs ===)

In JavaScript, loose equality allows type coercion. When you use

bool == true

JavaScript will try to coerce both bool and true to the same type before comparing them. In this case, null and false are both falsy values, so the comparison null == false evaluates to true.

With the strict equality operator (===), the code will correctly return false because null is not the same as true or false.

// using loose
function boolComparison(input) {
if (input == true | input == false) {
console.log(true);
return true;
}
return false;
}

boolComparison(null); // Output: true
boolComparison(undefined); // Output: true



// using strict
function boolComparison(input) {
if (input === true | input === false) {
console.log(true);
return true;
}
return false;
}

boolComparison(null); // Output: false
boolComparison(undefined); // Output: false


--

--

Jeff P
Jeff P

Written by Jeff P

I tend to write about anything I find interesting. There’s not much more to it than that really :-)

No responses yet