Web Development Interview Preparation
(function(){
// Isolate Code
})();
A immediately invoked function expression (IIFE) is a JavaScript function that runs as soon as it is defined. It is used to create an anonymous closure that ensures that the variables within are not accessible or colliding with other variables outside of the scope.
It is possible to pass arguments to the IIFE.
(function(name){
// Isolate Code
})("John Doe");
Immutablility
Similar to Java, if you declare a variable with const, you cannot change the value of the variable. But, you can still change the reference of the object that the variable is pointing to. This is most obvious when the variable is an array or an object. One way to deal with this is to keep to the use of functions like map, filter, and reduce. These operations will not change the original content of the variable.
Reduce
Reduce is used to succinctly combine multiple values in each iteration into a single value.
const numbers = [1, 2, 3, 4, 5];
numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0);
use strict
As compared to "sloppy" code, "strict" mode is more restrictive. For example:
"use strict";
myVar = "Hello World"; // Error: myVar is not defined
- forgetting to declare the variable properly with
const
orlet
will result in an error. - invalid assignment to reserved variable names such as
undefined
orNaN
will result in an error. - modifying properties that cannot be modified will result in an error.
- etc
Wrapping the code in use strict
via an IFFE will ensure that strict mode does not impact other scripts that are combined with the current script. This is to avoid declaring strict mode globally and perhaps affect code that you imported.
Promises
Callbacks are functions passed as arguments, mostly used asynchronously. Promises guarantee that a function will be called only once, and that the function will be called in the future.
new Promise(function(resolve, reject) {
// Do something
if (condition) {
resolve("Success!");
} else {
reject("Failure!");
}
}).then(function(successMessage) {
// Do something
// successMessage is the value that was passed to resolve
}).catch(function(failureMessage) {
// Do something
});
Virtual DOM
The traditional DOM is a tree representation of a webpage. Updating the DOM is expensive and can cause performance issues. The virtual DOM is a representation of the DOM that is not updated until the virtual DOM is compared to the actual DOM.
Hoisting
Move all declarations to the top of the current scope. Assignments are not hoisted.
One way to deal with it is to use IFFE. Variables inside the for loop are also hoisted if declare with var
.
// Declarations are hoisted
var x;
// Assignments are not hoisted
x = 1;
// Declare and assign
var x = 1;
// Functions are hoisted
foo() // OK
function foo() {
// Do something
}
// Assigned functions are not hoisted
bar() // Error
const bar = function() {
// Do something
}
Ems and Rems
They are more flexible, but based on the default font size of 16px(could be different). Rems are relative to the default font size.
div {
font-size: 2rem; // 2rem = 16px * 2
}
Ems are relative to the container.
h1 {
font-size: 2em; // 2em = 16px * 2 / 16px
}
Pseudo Classes and Pseudo Elements
Pseudo classes are used to select existing elements based on a state or a property.
Pseudo classes: :hover
, :focus
, :first-child
Pseudo elements: ::before
, ::after
, ::first-letter
Array assignment
Javascript arrays have undefined as their default value. When assigning a value to an array at a out-of-bound index, the array will pad undefined
in the middle such that the
length of the array is until the last added item that is not undefined.
var arr = [1, 2, 3];
arr[4] = 4;
arr; // [1, 2, 3, undefined, 4]
Sharing data between tabs
localStorage, cookie, indexedDB can be used to share data across different tabs. For sessionStorage, opening a page in a new tab or window will cause a new session to be initiated.
Async and Await
Event loop:
- call stack
- task queue
- microtask queue
- macrotask queue
console.log('Sync 1');
setTimeout(() => { // queue to macrotask queue
console.log('Timeout 2');
}, 0);
Promise.resolve().then( // queue to microtask queue
() => {
console.log('Promise 3');
}
);
console.log('Sync 4');
// Result
// Sync 1
// Sync 4
// Promise 3
// Timeout 2
The creation of the promise is still happening in the main thread, it's the resolving of the value that is happening in the microtask queue.
const codeBlocker = () => {
// Blocking
let i = 0;
while(i < 1000000000) { i++;}
return '🐷 billion loops done';
// Async blocking
return new Promise((resolve, reject) => {
let i = 0;
while(i < 1000000000) { i++;}
resolve('🐷 billion loops done');
})
// Non-blocking
return Promise.resolve().then(v => {
let i = 0;
while(i < 1000000000) { i++; }
return '🐷 billion loops done';
})
}
console.log("1");
setTimeout(function () {
console.log("timeout");
}, 0)
new Promise(function (resolve) {
console.log("in promise");
resolve()
}).then(function () {
console.log("chained then")
})
console.log("2");
// Result
// 1
// in promise
// 2
// chained then
// timeout