JavaScript Concepts - Part2

 

Article Image

JAVASCRIPT

INTERVIEW

JavaScript Concepts - Part2

Explain "hoisting"

Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope. Note that the declaration is not actually moved - the JavaScript engine parses the declarations during compilation and becomes aware of declarations and their scopes.

console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1

Function declarations have the body hoisted while the function expressions only has the variable declaration hoisted.

// Function Declaration
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
  console.log('FOOOOO');
}
console.log(foo); // [Function: foo]

// Function Expression
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function () {
  console.log('BARRRR');
};
console.log(bar); // [Function: bar]

Variables defined with let and const are hoisted to the top of the block, but not initialized.Meaning that the block of code is aware of the variable, but it cannot be used until it has been declared.

What is a closure

JavaScript implements a scoping mechanism named lexical scoping (or static scoping). Lexical scoping means that the accessibility of variables is determined by the position of the variables inside the nested scopes.

Simpler, the lexical scoping means that inside the inner scope you can access variables of outer scopes.

Closures are functions that have access to the outer (enclosing) function's variables scope even after the outer function has returned.

function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // => logs "I am outside!"
  }
  return innerFunc;
}
function exec() {
  const myInnerFunc = outerFunc();
  myInnerFunc();
}
exec();

Now innerFunc() is executed outside of its lexical scope, but exactly in the scope of exec() function. And what's important: innerFunc() still has access to outerVar from its lexical scope, even being executed outside of its lexical scope.

JavaScript Rest vs Spread Operator

Rest Operator

The rest operator (...) is used to put the rest of some specific user-supplied values into a JavaScript array.

For instance, consider this code that uses rest to enclose some values into an array:

// Use rest to enclose the rest of specific user-supplied values into an array:
function myBio(firstName, lastName, ...otherInfo) {
  return otherInfo;
}

// Invoke myBio function while passing five arguments to its parameters:
myBio("Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male");

// The invocation above will return:
["CodeSweetly", "Web Developer", "Male"]

Spread Operator

The spread operator (...) helps you expand iterables into individual elements. A spread operator is effective only when used within array literals, function calls, or initialized properties objects.

Example 1: How Spread Works in an Array Literal

const myName = ["Sofela", "is", "my"];
const aboutMe = ["Oluwatobi", ...myName, "name."];

console.log(aboutMe);

// The invocation above will return:
[ "Oluwatobi", "Sofela", "is", "my", "name." ]

Suppose we did not use the spread syntax to duplicate myName’s content. For instance, if we had written const aboutMe = ["Oluwatobi", myName, "name."]. In such a case, the computer would have assigned a reference back to myName

Example 2: How to Use Spread to Convert a String into Individual Array Items

const myName = "Oluwatobi Sofela";

console.log([...myName]);

// The invocation above will return:
[ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]

Example 3: How the Spread Operator Works in a Function Call

const numbers = [1, 3, 5, 7];

function addNumbers(a, b, c, d) {
  return a + b + c + d;
}

console.log(addNumbers(...numbers));

// The invocation above will return:
16

Suppose the numbers array had more than four items. In such a case, the computer will only use the first four items as addNumbers() argument and ignore the rest.

const numbers = [1, 3, 5, 7, 10, 200, 90, 59];

function addNumbers(a, b, c, d) {
  return a + b + c + d;
}

console.log(addNumbers(...numbers));

// The invocation above will return:
16

Example 4: How Spread Works in an Object Literal

const myNames = ["Oluwatobi", "Sofela"];
const bio = { ...myNames, runs: "codesweetly.com" };

console.log(bio);

// The invocation above will return:

{ 0: "Oluwatobi", 1: "Sofela", runs: "codesweetly.com" }
  • Spread operators can’t expand object literal’s values
  • Since a properties object is not an iterable object, you cannot use the spread operator to expand its values.
  • However, you can use the spread operator to clone properties from one object into another.
const myName = { firstName: "Oluwatobi", lastName: "Sofela" };
const bio = { ...myName, website: "codesweetly.com" };

console.log(bio);

// The invocation above will return:
{ firstName: "Oluwatobi", lastName: "Sofela", website: "codesweetly.com" };

Pure vs Impure Functions in JavaScript

  1. Pure Functions:
  • They must be predictable
  • They must have no side effects

Identical inputs will always return identical outputs, no matter how many times a pure function is called.

  1. Impure Function:
  • Unpredictable
  • Has side-effects

Side Effects can be:-

  • Modifying a global variable
  • Modifying an argument
  • External dependency (APIs, outer variables)
  • DOM manipulation
  • Reading/writing files
//IMPURE FUNCTION
const impureAddToArray = (arr1, num) => {
  //altering arr1 in-place by pushing
  arr1.push(num);
  return arr1;
};

// PURE FUNCTION
// Adding a value to an array via a pure function instead can be achieved using the spread operator, which makes a copy of the original array without mutating it.

const pureAddToArray = (arr1, num) => {
  return [...arr1, num];
};
// IMPURE FUNCTION
const impureAddToObj = (obj, key, val) => {
  obj[key] = val;
  return obj;
};

Because we're modifying the object in-place, the above approach is considered impure. Below is its pure counterpart, utilising the spread operator again.

// PURE FUNCTION
const  pureAddToObj = (obj, key, val) => {
  return { ...obj, [key]: val };
}

Higher Order Functions

A higher order function is a function that takes a function as an argument, or returns a function.

  • Some examples of higher order functions are .map() , .filter() and .reduce(). Both of them take a function as an argument.

Map Method

Using map method in javaScript creates an array by calling a specific function on each element present in the parent array.It returns a new array and elements of arrays are result of callback function.

Syntax:
arr.map(function(element, index, array){  }, this);
The this argument will be used inside the callback function. By default, its value is undefined .
Example:
let arr = [2, 3, 5, 7]

arr.map(function(element, index, array){
	console.log(this) // 80
}, 80);

Filter Method

The filter() method takes in a callback function and calls that function for every item it iterates over inside the target array. It entails filtering out one or more items (a subset) from a larger collection of items (a superset) based on some condition/preference.

Syntax:
arr.filter(function(element, index, array){  }, this);
The this argument will be used inside the callback function. By default, its value is undefined .
  • Example: Filter items out of an array
let people = [
    {name: "aaron",age: 65},
    {name: "beth",age: 2},
    {name: "cara",age: 13},
    {name: "daniel",age: 3},
    {name: "ella",age: 25},
    {name: "fin",age: 1},
    {name: "george",age: 43},
]

let toddlers = people.filter(person => person.age <= 3)

console.log(toddlers)

/*
[{
  age: 2,
  name: "beth"
}, {
  age: 3,
  name: "daniel"
}, {
  age: 1,
  name: "fin"
}]
*/
  • Example: How to access the context object with this
let people = [
    {name: "aaron", age: 65},
    {name: "beth", age: 15},
    {name: "cara", age: 13},
    {name: "daniel", age: 3},
    {name: "ella", age: 25},
    {name: "fin", age: 16},
    {name: "george", age: 18},
]

let range = {
  lower: 13,
  upper: 16
}


let teenagers = people.filter(function(person) {
	return person.age >= this.lower && person.age <= this.upper;
}, range)

console.log(teenagers)

/*
[{
  age: 15,
  name: "beth"
}, {
  age: 13,
  name: "cara"
}, {
  age: 16,
  name: "fin"
}]
*/

Reduce Method

Syntax
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

Array.reduce takes two parameters.

  • The reducer

  • An initial value (optional)

  • The reducer is the function doing all the work. As reduce loops over your list, it feeds two parameters to your reducer.

  • An accumulator

Accumulator is the eventual return value When you're looping through the users, how are you keeping track of their total age? You need some counter variable to hold it. That's the accumulator

  • The current value

The current value is just like when you use array[i] in a regular loop.

  • Example:
var euros = [29.76, 41.85, 46.5];

var sum = euros.reduce( function(total, amount){
  return total + amount
}, 0);

sum // 118.11

Using call(), apply() and bind()

We can have objects that have their own properties and methods. But object1 cannot use the methods of object2 and vice versa.

CAB

We can use call(), apply(), and bind() methods to tie a function into an object and call the function as if it belonged to that object.

Call() Method in JavaScript

The call() method invokes a function with a specified context.

var obj = { firstName: "a", lastName:"b" };

function fullName(){
  return this.firstName + this.lastName;
}

use the call() method to tie the function add() to the object obj:

add.call(obj, 3);

Use Call() with Multiple Arguments

function fullName(a, b){
  return this.firstName + this.lastName + a + b;
}

console.log(add.call(obj, "x", "y"));

Apply() Method in JavaScript

The apply() method does the exact same as call(). The difference is that call() accepts an argument list, but apply() accepts an array of arguments.

var obj = { firstName: "a", lastName:"b" };

function fullName(){
  return this.firstName + this.lastName;
}

console.log(add.apply(obj, ["x", "y"]));

Bind() Method in JavaScript

call() and apply() methods are executed immediately when called (and returned a value). But instead of executing a function immediately, bind() returns a copy of a function that can be executed later on.

var obj = { num: 2 };

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

const func = add.bind(obj, 3, 5);
func(); // Returns 10

Async and Defer

  • Without using async and defer The parsing is paused until the script is fetched, and executed. Once this is done, parsing resumes.

Async-Defer

  • Page loading a script with async The script is fetched asynchronously, and when it’s ready the HTML parsing is paused to execute the script, then it’s resumed.

Async

  • Page loading With defer The script is fetched asynchronously, and it’s executed only after the HTML parsing is done.

Defer

Event Throttling and debouncing

Debouncing and Throttling techniques enhance the performance of your website, also prevent unnecessary API calls and load on the server.

Debouncing and throttling techniques are used to limit the number of times a function can execute.

Throttling

Throttling is a technique in which, no matter how many times the user fires the event, the attached function will be executed only once in a given time interval.

Example

let timer;

const handleInput = (val, delay) => {
  if (timer) {
    return;
  }

  timer = setTimeout(() => {
    console.log(val);
    timer = undefined;
  }, delay);
};

document.getElementById("search-box").addEventListener("keypress", (e) => {
  handleInput(e.target.value, 1000);
});

Debouncing

In the debouncing technique, no matter how many times the user fires the event, the attached function will be executed only after the specified time once the user stops firing the event.

Example

let timer;

const handleInput = (val, delay) => {
  clearTimeout(timer);

  timer = setTimeout(() => {
    console.log(val);
  }, delay);
};

document.getElementById("search-box").addEventListener("keypress", (e) => {
  handleInput(e.target.value, 1000);
});

Event Bubbling and Capturing

eventflow

Event Bubbling

When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.

Let’s say we have 3 nested elements FORM > DIV > P with a handler on each of them

<form onclick="alert('form')">FORM
  <div onclick="alert('div')">DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>

A click on the inner <p> first runs onclick:

On that <p>.
Then on the outer <div>.
Then on the outer <form>.
And so on upwards till the document object.

event-order-bubbling

Event Capturing

In event capturing, an event propagates from the outermost element to the target element.

event-order-capture

Clicking on the p element calls the click event handlers of all parent elements, starting from the outer and propagating inside to the target element p:
html → body → article → div → p.

Stopping bubbling

event.stopPropagation()

event.stopPropagation() stops the move upwards, but on the current element all other handlers will run.

Thanks for reading!!!

No comments:

Post a Comment

The Importance of Cybersecurity in the Digital Age

 The Importance of Cybersecurity in the Digital Age Introduction: In today's digital age, where technology is deeply intertwined with ev...