Table of contents
- What is Hoisting?
- What is Scope?
- What are Closures?
- What is the event.currentTarget?
- What are the falsy values in JavaScript?
- What does "use strict" do?
- What's the value of "this" in JavaScript?
- What is an Event loop?
- What is an IIFE, and what is its use of it?
- What are Higher Order Functions?
- What are Promises & how it's working?
What is Hoisting?
In javascript, Hosting is referred to the process where the interpreter allocated memory for variables and functions even before declaring it.
Example of Variable lifecycle:
let a; // Declaration
a = 100; // Assignment
console.log(a); // Usage
Example of Hoisting:
console.log(a); // undefined
console.log(fun); // [Function: fun]
var a = 10; // Declaration
function fun() {
console.log('fun');
}
console.log(a); // reference error
console.log(fun); // [Function: fun]
const a = 10;
function fun() {
console.log('fun');
}
What is Scope?
Scope in JavaScript refers to the current context of code, which determines the accessibility of variables to JavaScript.
// global scope
let var1 = 10;
function fun() {
// function scope / local scope
let var1 = "var1";
console.log(var1);
}
fun(); // var1
console.log(var1); // 10
What are Closures?
In JavaScript, a closure is created whenever a function is defined inside another function. A closure allows the inner function to access the outer function's variables and parameters, even after the outer function has returned. This is possible because the inner function has access to the scope chain of the outer function.
Here is an example of a closure in JavaScript:
function outerFunction() {
const outerVariable = "I am outside!";
function innerFunction() {
console.log(outerVariable); // logs "I am outside!"
}
return innerFunction;
}
const innerFunc = outerFunction();
innerFunc(); // logs "I am outside!"
In this example, the inner function innerFunction()
is defined inside the outer function outerFunction()
. The outerFunction()
creates a variable outerVariable
and defines innerFunction()
, which logs the outerVariable
. The outerFunction()
then returns the innerFunction()
.
When outerFunction()
is called and the returned innerFunction()
is executed, it has access to the outerVariable
even though it is technically outside of its scope. This is because the innerFunction()
forms a closure around the outerVariable
, allowing it to access it even after outerFunction()
has returned.
Closures are often used in JavaScript to create private variables and methods, as well as to create functions that "remember" previous values of their arguments or variables.
What is the event.currentTarget?
event.currentTarget is a property of an Event object that refers to the element on which the event is currently being handled. This property is useful when you have numerous elements with event listeners and we want to know which element triggered the event.
Consider the following HTML code:
<div id="parent">
<button id="child">Click me!</button>
</div>
Now let's add a click event listener to both the parent and the child elements:
const parent = document.querySelector('#parent');
const child = document.querySelector('#child');
parent.addEventListener('click', handleClick);
child.addEventListener('click', handleClick);
function handleClick(event) {
console.log(event.currentTarget.id);
}
When we click the button, the handleClick
function is called and passed an event object as an argument. The event.currentTarget
property refers to the element to which the event listener was attached, regardless of which element triggered the event. In this case, clicking the child button triggers both the parent and child event listeners, but the event.currentTarget.id
will always log "parent"
since the event listener was attached to the parent element.
If you want to access the element that actually triggered the event, you can use the event.target
property instead of event.currentTarget
. The event.target
property refers to the actual element that triggered the event, which in this case would be the child button.
What's the difference between \== and ===?
Both ==
and ===
are used as a compare operator for comparing two values in a program. The main difference between the two is the type of comparison they perform.
The ==
the operator performs an abstract equality comparison. It compares two values for equality after converting both values to a common type. If the types of the two values are different, the values are coerced to a common type before comparison. For example, the string "5" would be equal to the number 5 if compared.
Here are some examples of using the ==
operator:
1 == "1" // true
null == undefined // true
true == 1 // true
On the other hand, the ===
an operator performs a strict equality comparison. It compares two values for equality without performing any type of conversion. If the types of the two values are different, the values are considered unequal.
Here are some examples of using the ===
operator:
1 === "1" // false
null === undefined // false
true === 1 // false
In general, it is a common practice to use the ===
operator as it avoids type coercion and can help avoid unexpected results or errors in your code. However, there may be cases where you want to use the ==
operator for loose equality comparison.
How to evaluate multiple expressions in one line?
In the case of JavaScript, we can evaluate numerous expressions in one line by separating them with the comma operator (,
). The comma operator allows you to include multiple expressions in a single statement and evaluates them from left to right. The value of the last expression is returned as the result of the entire expression.
Here's an example of using the comma operator to evaluate numerous expressions in one line:
let x = 1, y = 2, z = 3;
x++, y++, z++; // evaluates to 2, 3, 4
console.log(x, y, z); // outputs "2 3 4"
In this example, the expressions x++
, y++
, and z++
are separated by commas, allowing them to be evaluated in a single line. Each expression increments its corresponding variable by 1, and the final values of x
, y
, and z
are logged to the console.
You can also use the comma operator in other ways, such as to initialize multiple variables in a single statement:
let a = 1, b = 2, c = 3;
In this example, the comma operator is used to separate the initialization of three variables (a
, b
, and c
) in a single statement.
Note that while using the comma operator can make your code more straightforward, it can also make it less readable and more difficult to debug.
What are the falsy values in JavaScript?
A value is considered falsy in Javascript. if it is treated as false when evaluated in a boolean context. Here is a list of the falsy values in JavaScript:
false
: the boolean value false0
: the number zeronull
: a null valueundefined
: an undefined valueNaN
: Not-a-Number
In addition, the empty string (""
) is also considered falsy.
Here's an example that shows how falsy values work in JavaScript:
if (0 || null || undefined || NaN || "") {
// This code will not execute because all values are falsy
} else {
// This code will execute because all values are falsy
}
In the above example, the if
statement checks if any of the values inside the parentheses is truthy. However, all of the values (0
, null
, undefined
, NaN
, and ""
) are falsy values, so the code inside the if
block will not execute. Instead, the code inside the else
block will execute because all of the values are falsy.
What does "use strict" do?
"use strict"
is a directive that enables strict mode, a feature introduced in ECMAScript 5 (ES5) that allows us to place our code in a stricter operating context.
When strict mode is enabled, the JavaScript engine implements stricter rules for code syntax and behavior, which can help catch coding mistakes and make your code more secure.
Here are some of the effects of enabling strict mode with "use strict"
:
Prevents the use of undeclared variables: In strict mode, any attempt to use a variable that has not been declared with
var
,let
, orconst
will throw aReferenceError
. This helps prevent the accidental creation of global variables, which can cause unexpected behavior.Prohibits duplicate property names: In strict mode, any attempt to define multiple properties with the same name in an object literal or class declaration will throw a
SyntaxError
.Disables the use of the
with
statement: In strict mode, thewith
statement is not allowed. This is because it can make code harder to read and understand, and can also cause security vulnerabilities.Prevents assignment to non-writable properties: In strict mode, any attempt to assign a value to a non-writable property or a getter-only property will throw a
TypeError
.Disallows the deletion of variables and functions: In strict mode, the
delete
operator cannot be used to delete variables, functions, or function arguments.Makes
eval()
it is safer: In strict mode, theeval()
function is executed in its own scope, rather than in the current scope. This helps prevent unexpected variable declarations or changes to existing variables.
To enable strict mode in a JavaScript file, you can simply add the "use strict"
directive at the top of the file or within a function.
For example:
"use strict";
function myFunction() {
// function code here
}
By enabling strict mode, you can write safer, more reliable JavaScript code.
What's the value of "this" in JavaScript?
The value of the this
keyword is determined by how a function is called. It refers to the object that the function is a method of or the global object if the function is called a standalone function. The value this
is often referred to as the "context" of the function.
Here are some common rules that determine the value of this
in different contexts:
- Global context: When a function is called in the global context, i.e. not as a method of an object,
this
refers to the global object in Node.js.
console.log(this); // logs the global object
- Object method context: When a function is called as a method of an object,
this
refers to the object that the method is called on.
const myObject = {
name: "John",
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};
myObject.greet(); // logs "Hello, my name is John"
In this example, greet()
is called a method of myObject
, so this
refers to myObject
.
- Constructor context: When a function is called with the
new
keyword,this
refers to the newly created object.
function Person(name) {
this.name = name;
}
const john = new Person("John");
console.log(john.name); // logs "John"
In this example, Person()
is called with the new
keyword, so this
refers to the newly created Person
object.
- Explicit context: You can also set the value of
this
explicitly using methods likecall()
orapply()
. In this case,this
refers to the object passed as the first argument to the method.
function greet() {
console.log(`Hello, my name is ${this.name}`);
}
const person = { name: "John" };
greet.call(person); // logs "Hello, my name is John"
In this example, call()
is used to call greet()
with person
as the value of this
.
The value of this
can be a little tricky to understand in some cases, so it's important to be familiar with the rules that determine its value in different contexts.
What is an Event loop?
The event loop is a mechanism that allows Javascript to handle asynchronous tasks, such as I/O operations(API calls or DB calls) or timers (setTimeout), in a non-blocking way.
The event loop is part of the JavaScript runtime environment and is responsible for managing the execution of tasks in a single-threaded environment.
When an asynchronous task is initiated, such as a timer or an API request, it is added to a queue. The event loop constantly checks this queue for tasks to execute. When a task is ready to be executed, the event loop takes it out of the queue and runs it. If the task is a long-running operation, such as a complex computation, the event loop will continue checking the queue for other tasks to execute while the operation runs.
Here is a simplified example of how the event loop works:
The JavaScript runtime environment is initialized, and the event loop starts running.
An asynchronous task, such as a timer or an API request, is initiated and added to the task queue.
The event loop checks the task queue for tasks to execute. If there is a task in the queue, the event loop takes it out and runs it.
If the task is a long-running operation, such as a complex computation, the event loop continues checking the task queue for other tasks to execute while the operation runs.
When the task is completed, its callback function is added to the task queue.
The event loop checks the task queue for callback functions to execute. If there is a callback function in the queue, the event loop takes it out and runs it.
If there are no more tasks or callback functions in the queue, the event loop waits for new tasks to be added to the queue.
The event loop allows JavaScript to handle asynchronous tasks without blocking the main thread, which can improve the performance and responsiveness of web applications. Nevertheless, it also introduces some complexity to the language, as developers need to understand how the event loop works in an order to write code.
Link to Javascript Eventloop Visualizer: https://www.jsv9000.app/
What is the prototype of an object?
A prototype is a template or blueprint for an object that defines its properties and behaviors. It serves as the basis for creating new instances of the object.
In JavaScript, prototypes are used to implement inheritance. When an object is created, it inherits properties and behaviors from its prototype. If a property or behavior is not defined in the object itself, the runtime system looks for it in the prototype chain.
For example, consider a prototype for a car object. The car prototype might define properties such as the make, model, and color of the car, as well as methods such as drive and stop. When a new car object is created from the prototype, it inherits these properties and methods, but can also have its own unique properties and methods.
In summary, a prototype is a template or blueprint for an object that defines its properties and behaviors and serves as the basis for creating new instances of the object.
What is an IIFE, and what is its use of it?
IIFE or Immediately Invoked Function Expression is a design pattern in JavaScript that allows a function to be executed immediately after it is defined.
An IIFE is defined as an anonymous function expression that is wrapped in parentheses and is immediately followed by another set of parentheses. The inner set of parentheses is used to invoke the function instantly after it is defined.
Here's an example of an IIFE:
(function () {
// code to be executed immediately
})();
The code inside the function is executed instantly after the function is defined. The outer parentheses around the function expression are necessary to indicate that it is a function expression and not a function declaration.
The main use of an IIFE is to create a private scope for the code inside the function. This means that any variables or functions defined inside the IIFE are not accessible from outside the IIFE, and do not pollute the global namespace. This can help avoid naming collisions and improve the overall organization and maintainability of the code.
IIFEs are commonly used in JavaScript libraries and frameworks to define modules and encapsulate functionality. They can also be used to create closures and pass arguments to a function without exposing them to the global scope.
What are Higher Order Functions?
In JavaScript, higher-order functions are functions that can take one or more functions as arguments, and/or return a function as their result. This means that higher-order functions can operate on other functions, either by taking them as arguments or by returning them as results.
Here's an example of a higher-order function that takes a function as an argument:
function apply(func, arg) {
return func(arg);
}
In this example, the apply
function takes two arguments: a function func
, and an argument arg
. The apply
function then applies the func
function to the arg
argument and returns the result. This allows us to apply different functions to different arguments, without having to write separate functions for each use case.
Here's an example of a higher-order function that returns a function as its result:
function multiplyBy(num) {
return function(x) {
return x * num;
}
}
In this example, the multiplyBy
the function takes a number num
as its argument, and returns another function that takes a number x
as its argument, and multiplies it by num
. This allows us to create different functions that multiply a number by a specific factor, without having to repeat the same code multiple times.
Higher-order functions are commonly used in functional programming, where they can be used to create powerful abstractions and compose complex behavior from simpler building blocks. They are also commonly used in JavaScript libraries and frameworks, where they can be used to create reusable and extensible code.
What are Promises & how it's working?
In JavaScript, Promises are a way to handle asynchronous operations, such as network requests or file I/O, in a more readable and manageable way. Promises are objects that represent the eventual completion (or failure) of an asynchronous operation and allow you to attach callbacks to be executed when the operation succeeds or fails.
A Promise has three states:
pending
: The initial state, before the operation has been completed.fulfilled
: The state reached when the operation has been completed successfully, and the result (or value) of the operation is available.rejected
: The state reached when the operation has failed, and the reason for the failure (or error) is available.
A Promise can be created using the Promise
constructor, which takes a single argument: a function (often called the executor function) that takes two arguments: resolve
and reject
. The resolve
function is used to fulfill the Promise with a value, and the reject
function is used to reject the Promise with a reason (usually an error).
Here's an example of creating and using a Promise:
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
resolve(randomNumber);
} else {
reject(new Error('Number is greater than 0.5'));
}
}, 1000);
});
myPromise.then((result) => {
console.log('Promise fulfilled with result:', result);
}).catch((error) => {
console.error('Promise rejected with error:', error);
});
In this example, we create a Promise that simulates an asynchronous operation by waiting for 1 second and then resolving or rejecting with a random number. We attach a then
callback to be executed when the Promise is fulfilled, and a catch
callback to be executed when the Promise is rejected.
When the Promise is fulfilled, the then
callback is executed with the result (the random number) as its argument. When the Promise is rejected, the catch
callback is executed with the reason (the error) as its argument.
Promises allow you to write asynchronous code in a more readable and manageable way, by providing a standardized way to handle asynchronous operations and their results. They also allow you to chain multiple asynchronous operations together and handle errors in a consistent way.