1. What is a `Set
’ object and how does it work?
Set
object allows you to store unique values of any type, whether they are primitive values or object references.
We can create a Set
instance using the Set
constructor.
const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);
We can use the add
method to add a new value to a Set
instance. Since the add
method returns the Set
object, we can chain multiple add
calls together. If a value already exists in the Set
object, it will not be added again
set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
We can use the has
method to check if a specific value exists in a Set
instance.
set2.has("a") // true
set2.has("z") // true
We can use the size
property to get the length of a Set
instance.
set2.size // returns 10
The clear
method can be used to remove all data from a Set
.
set2.clear();
We can use a Set
object to remove duplicate elements from an array.
const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8]
2.What is a callback function?
Callback function is an executable code snippet that is passed as an argument to other code. Its purpose is to be invoked at a convenient time by the receiving code when needed.
In JavaScript, functions are a type of objects. Just like objects, functions can be passed as arguments to other functions. Therefore, a function that is passed as an argument to another function is called a callback function.
const btnAdd = document.getElementById('btnAdd');
btnAdd.addEventListener('click', function clickCallback(e) {
// do something useless
});
In this example, we are waiting for the click event on the element with the id btnAdd
. If it is clicked, the function clickCallback
will be executed. The callback function adds some functionality to certain data or events.
The reduce
, filter
, and map
methods in arrays require a callback as a parameter. A good analogy for a callback is when you call someone, and if they don’t answer, you leave a message, expecting them to call you back. The act of calling someone or leaving a message is the event or data, and the callback is the operation you expect to happen later.
3. What is ES6 module?
Modules allow us to split our codebase into multiple files for better maintainability and to avoid having all the code in one large file. Before ES6 introduced support for modules, there were two popular module systems.
- CommonJS-Node.js
- AMD (Asynchronous Module Definition) — Browser
Basically, using modules is quite simple. import
is used to retrieve functionality or multiple functionalities or values from another file, while export
is used to expose functionality or multiple functionalities or values from a file.
Export
Using ES5 (CommonJS)
// useing ES5 CommonJS - helpers.js
exports.isNull = function (val) {
return val === null;
}
exports.isUndefined = function (val) {
return val === undefined;
}
exports.isNullOrUndefined = function (val) {
return exports.isNull(val) || exports.isUndefined(val);
}
Using ES6 modules
// Using ES6 Modules - helpers.js
export function isNull(val){
return val === null;
}
export function isUndefined(val) {
return val === undefined;
}
export function isNullOrUndefined(val) {
return isNull(val) || isUndefined(val);
}
Importing functions from another file
//ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;
// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
-------------------------------------------------------
// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object
// or
import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';
// using "as" for renaming named exports
Exporting a single function or default export in a file
ES5 (CommonJS)
// ES5 (CommonJS) - index.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
module.exports = Helpers;
Using ES6 Modules
// using ES6 Modules - helpers.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
export default Helpers
Importing a single function from another file
Using ES5 (CommonJS)
//ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));
Using ES6 Modules
import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));
4. What is a Promise?
Promise is a solution for asynchronous programming. Syntax-wise, promise
is an object from which you can obtain the outcome of an asynchronous operation. Conceptually, it represents a commitment to provide a result after a certain period of time. promise
has three states: pending
, fulfilled
and rejected
. Once the state changes, it remains unchanged. After creating a Promise
instance, it executes immediately.
fs.readFile('somefile.txt', function (e, data) {
if (e) {
console.log(e);
}
console.log(data);
});
If we have another asynchronous operation within a callback, it leads to a problem. We end up with messy and unreadable code. This code is known as ‘callback hell’.
// callback hell
fs.readFile('somefile.txt', function (e, data) {
//your code here
fs.readdir('directory', function (e, files) {
//your code here
fs.mkdir('directory', function (e) {
//your code here
})
})
})
If we use promise
in this code, it will be more readable, understandable, and maintainable.
promReadFile('file/path')
.then(data => {
return promReaddir('directory');
})
.then(data => {
return promMkdir('directory');
})
.catch(e => {
console.log(e);
})
promise
has three distinct states:
- pending:the initial state, the state before fulfillment or rejection.
- fulfilled:Operation completed successfully
- rejected:Operation failed
pending
object in the pending
state triggers the fulfilled
/rejected
state, passing the resolved value/error message in its respective state handling methods. When the operation is successfully completed, the then
method of the Promise object is invoked. Otherwise, the catch
method is triggered. For example:
const myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve("Success!");
}, 250);
});
myFirstPromise.then((data) => {
console.log("Yay! " + data);
}).cat
5. What is async/await
and how does it work?
async/await
is a new method in JavaScript for writing asynchronous or non-blocking code. It is built on top of Promises and provides higher readability and conciseness for asynchronous code.
async/await
is a new method in JavaScript for writing asynchronous or non-blocking code. It is built on top of Promises and provides higher readability and conciseness compared to Promises and callbacks. However, before using this feature, it is necessary to learn the basics of Promises because, as mentioned earlier, async/await
is built on top of Promises, which means it still utilizes Promises behind the scenes.
Promise
function callApi() {
return fetch("url/to/api/endpoint")
.then(resp => resp.json())
.then(data => {
//do something with "data"
}).catch(err => {
//do something with "err"
});
}
async/await
In async/await
, we use the try/catch
syntax to catch exceptions.
async function callApi() {
try {
const resp = await fetch("url/to/api/endpoint");
const data = await resp.json();
//do something with "data"
} catch (e) {
//do something with "err"
}
}
Note: Using the ‘async’ keyword to declare a function implicitly returns a Promise.
const giveMeOne = async () => 1;
giveMeOne()
.then((num) => {
console.log(num); // logs 1
});
Note: await
keyword can only be used inside an async function
. Using the await
keyword in any non-async function will throw an error. await
keyword waits for the right-hand expression (which can be a Promise) to return before executing the next line of code.
const giveMeOne = async () => 1;
function getOne() {
try {
const num = await giveMeOne();
console.log(num);
} catch (e) {
console.log(e);
}
}
// Uncaught SyntaxError: await is only valid in async function
async function getTwo() {
try {
const num1 = await giveMeOne();
const num2 = await giveMeOne();
return num1 + num2;
} catch (e) {
console.log(e);
}
}
await getTwo(); // 2
6. What is the difference between the spread operator and the rest operator?
The spread operator is denoted by three dots …
and it can convert an array into a comma-separated sequence of arguments. In simpler terms, it is similar to breaking apart a large element into individual smaller elements, like how a palm strike disperses a solid object.
The rest operator, also denoted by three dots …
, may look similar to the spread operator, but it is used for destructuring arrays and objects. In a way, the rest operator is the opposite of the spread operator. While the spread operator ‘spreads’ an array into multiple elements, the rest operator ‘collects’ multiple elements and ‘compresses’ them into a single element.
function add(a, b) {
return a + b;
};
const nums = [5, 6];
const sum = add(...nums);
console.log(sum);
In this example, we used the spread operator when calling the add
function, which expanded the nums
array. Therefore, the value of parameter a
is 5, and the value of parameter b
is 6
, resulting in the sum
being 11
.
function add(...rest) {
return rest.reduce((total,current) => total + current);
};
console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4, 5)); // 15
In this example, we have an add
function that accepts any number of parameters, adds them all together, and then returns the total sum.
const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]
Here, we use the rest operator to extract all the remaining array values and put them into another array, excluding the first item.
7. What are default parameters?
Default parameters are a new way to define default variables in JavaScript, available in ES6 or ECMAScript 2015.
//ES5 Version
function add(a,b){
a = a || 0;
b = b || 0;
return a + b;
}
//ES6 Version
function add(a = 0, b = 0){
return a + b;
}
add(1); // returns 1
We can also use destructuring in default parameters.
function getFirst([first, ...rest] = [0, 1]) {
return first;
}
getFirst(); // 0
getFirst([10,20,30]); // 10
function getArr({ nums } = { nums: [1, 2, 3, 4] }){
return nums;
}
getArr(); // [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]
We can also use previously defined parameters before defining subsequent parameters.
function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
callback();
}
doSomethingWithValue(); //"Hello World"
8. What is a wrapper object?
Now let’s review the data types in JavaScript. JavaScript data types are divided into two major categories: primitive types and reference types.
Primitive types: Undefined
,Null
,Boolean
,Number
,String
,Symbol
,BigInt
reference types: Object
,Array
,Date
,RegExp
,etc. In simple terms, they are objects.
Among reference types, there are methods and properties, which are not available in primitive types. However, we often come across the following code:
let name = "maxwell";
console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MAXWELL"
The name
type is a string
and belongs to the primitive type, so it doesn’t have any properties or methods. However, in this example, we are calling the toUpperCase()
method, which doesn’t throw an error and returns the uppercase value of the string.
The reason is that the value of a primitive type is temporarily converted or coerced into an object, so the behavior of the name
variable is similar to that of an object. Every primitive type, except for null
and undefined
, has its own wrapper object: String
, Number
, Boolean
, Symbol
, and BigInt
. In this case, name.toUpperCase()
behind the scenes looks like this:
console.log(new String(name).toUpperCase()); // "MAXWELL"
After accessing properties or invoking methods, the newly created object is immediately discarded.
9. What is the difference between implicit and explicit type conversion?
Implicit type conversion is a method of converting a value to another type, which is done automatically without manual intervention.
Let’s assume we have the following example below.
console.log(1 + '6'); // 16
console.log(false + true); // 1
console.log(6 * '2'); // 12
The result of the first console.log
statement is 16. In other languages, this would throw a compilation error, but in JavaScript, 1
is converted to a string and then concatenated with the +
operator. We didn’t do anything; it is automatically done by JavaScript.
The result of the second console.log
statement is 1
. In JavaScript, false
is converted to a boolean
value of 0
, while true
is converted to 1
. Therefore, the result is 1
.
The result of the third console.log
statement is 12
. It converts ‘2’ to a number and then multiplies it by 6 * 2
, resulting in 12
.
Explicit type coercion, on the other hand, is a method of converting a value to another type where we need to manually perform the conversion.
console.log(1 + parseInt('6'));
In this example, we use the parseInt function to convert ‘6’ to a number, and then we use the + operator to add 1 and 6 together.
10. What is NaN? And how do you check if a value is NaN?
NaN
, short for “Not a Number,” is a value in JavaScript that arises as a result of numeric operations or conversions that cannot produce a meaningful numeric value. Therefore, when a numeric operation or conversion yields a non-numeric value, the result is NaN
.
let a;
console.log(parseInt('abc')); // NaN
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseInt(++a)); // NaN
console.log(parseInt({} * 10)); // NaN
console.log(parseInt('abc' - 2)); // NaN
console.log(parseInt(0 / 0)); // NaN
console.log(parseInt('10a' * 10)); // NaN
JavaScript has a built-in isNaN
method used to test if a value is NaN
. However, this function exhibits a peculiar behavior.
console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => { })); // true
All of these console.log
statements return true
, even if the values we pass are not NaN
.
In ES6, it is recommended to use the Number.isNaN
method because it genuinely checks if the value is NaN
. Alternatively, we can create our own helper function to check this issue, as in JavaScript, NaN
is the only value that is not equal to itself.
function checkIfNaN(value) {
return value !== value;
}
11. How can you determine if a value is an array?
We can use the Array.isArray
method to check if a value is an array. When passed an argument that is an array, it returns true
; otherwise, it returns false
.
console.log(Array.isArray(5)); // false
console.log(Array.isArray("")); // false
console.log(Array.isArray()); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray({ length: 5 })); // false
console.log(Array.isArray([])); // true
If the environment doesn’t support this method, you can implement a polyfill
.
function isArray(value){
return Object.prototype.toString.call(value) === "[object Array]"
}
Of course, you can also use traditional methods:
let a = []
if (a instanceof Array) {
console.log('is an array')
} else {
console.log('Non-Arrays')
}
12. How can you check if a property exists in an object?
There are three methods to check if a property exists in an object.
The first method is to use the in
operator:
const o = {
"prop" : "rabbit",
"prop2" : "tiger"
};
console.log("prop" in o); // true
console.log("prop1" in o); // false
The second method is to use the hasOwnProperty
method. The hasOwnProperty()
method returns a boolean
value indicating whether the object has the specified property as a direct property (not inherited).
console.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false
The third method is to use the bracket notation obj[‘prop’]
. If the property exists, it will return the value of that property; otherwise, it will return undefined
.
console.log(o["prop"]); // "rabbit"
console.log(o["prop1"]); // undefined