- Question #1: undefined Vs null
- Question #2: var Vs let Vs const
- Question #3: forEach() vs map()
- Question #4: for...in Vs for...of
- Question #5: find() vs filter()
- Question #6: call() Vs bind() Vs apply()
- Question #7: shallow copy Vs deep copy
- Question #8: == vs ===
- Question #9: parseInt() Vs Number()
- Question #10: normal/regular function vs arrow function
- Question #11: pure function vs impure function
- Question #12: setTimeout() Vs setInterval()
- Question #13: Q #13: spreed operator (...) Vs rest parameter (...)
undefined
and null
are two special values in JavaScript that represent the absence of a value. However, there is a difference between them in both defination and type:
undefined
is a value that is automatically assigned to a variable that has been declared but has not been assigned a value, , on the other hand,null
is a value that can be explicitly assigned to a variable to represent the absence of a value.undefined
has a type ofundefined
, whilenull
has a type ofobject
.
var x;
console.log(x); // Output: undefined
var y = null;
console.log(y); // Output: null
console.log(typeof x); // Output: undefined
console.log(typeof y); // Output: object
var
, let
, and const
are all used to declare variables, but they have some differences in terms of how they behave
and the scope they have:
1.var
:
- Variables declared with var can be
re-declared
andupdated
anywhere in the program.
function example() {
var x = 10;
if (true) {
var x = 20; // this will overwrite the previous declaration of x
console.log(x); // output: 20
}
console.log(x); // output: 20
}
- Varibles declared with var are
hoisted
.Hoisting
is moving declarations to the top of the current scope.
x = 5 //intilize x (assign value to x)
console.log(x); //output: 5
var x; //declaring x with var
//the previous code is similar to:
var y; // declare y with var
y = 5; // intilize y (assign value to y)
console.log(y); //output: 5
- var is a
function-scoped
, variables declared with var inside a function can be used anywhere witin a function.
function sayHello() {
a = 'hello';
console.log(a);
var a;
}
//'a' is hoisted to the top of the function and become local variable
sayHello(); //output: 'hello'
console.log(a); //ERROR: a is not defined cause
//'a' is only accessible inside the function and doesn't become a global variable cause it is declared with var
- Redeclaring a varible with var in a block scope will change the value too in the outer scope. A block is any code surrounded by curly braces
{ }
.
var c = 5;
console.log(c); //output: 5
{
var c = 10;
console.log(c); //output: 10
}
console.log(c); //output: 10
- When a variable declared with var in a loop, the value of that variable is changed.
for (var y = 0; y < 3; y++) {
//processing some data
}
console.log(y); //output: 3
2.let
:
- Variables declared with let can be updated but not re-declared.
function example() {
let x = 10;
if (true) {
let x = 20; // this will create a new variable x that only exists inside the block
console.log(x); // output: 20
}
console.log(x); // output: 10
}
- Varibles declared with let are
not hoisted
. In other words cannot be used untill they are declared.
x = 5 //intilize x (assign value to x)
console.log(x); //ERROR: Cannot access 'x' before initialization
let x; //declaring x with let
- let is
block-scoped
, which means that if you declare a variable with let inside a block, it's only accessible within that block.
let c = 5;
console.log(c); //output: 5
{
let c = 10;
console.log(c); //output: 10
}
console.log(c); //output: 5
- When a variable declared with let in a loop, the value of that variable doesn't change cause let is blocked scope.
let y = 100;
for (let y = 0; y < 3; y++) {
//processing some data
}
console.log(y); //output: 100
3.const
:
- Like let, const is also block-scoped.
- Variables declared with const cannot be re-declared or updated.
const x = 100;
x = 200; //ERROR: Assignment to constant variable.
console.log(x);
forEach()
is used to execute a provided function once for each element in an array, in order. It doesn't return anything and simply iterates over the array. Here's an example:
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(num => console.log(num));
In this example, forEach()
is used to iterate over the numbers array and log each element to the console.
map()
, on the other hand, is used to create a new array with the results of calling a provided function on every element in the original array. It returns a new array with the same length as the original array, but with each element transformed by the provided function. Here's an example:
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map(num => num * num);
console.log(squaredNumbers); // Output: [1, 4, 9, 16, 25]
The forEach()
method returns undefined always, regardless of what the callback function returns.
const numbers = [1, 2, 3];
const nums1 = numbers.forEach(num => num * 2); //undefined
const nums2 = numbers.map(num => num * 2); //[2, 4, 6]
Use forEach()
when you want to execute a function for each element in an array without modifying the array. Use map()
when you want to create a new array with the results of calling a function on each element of the original array.
for...in
and for...of
are two different types of loops used to iterate over data structures such as objects and arrays.
for...in
: this loop is used to iterate over the properties of an object
. It's typically used to loop over object keys
.
const obj = {a: 1, b: 2, c: 3};
for (let prop in obj) {
console.log(prop + ": " + obj[prop]);
}
/*
output:
a: 1
b: 2
c: 3
*/
for...of
: this loop is used to iterate over the values of an array
or other iterable object such as string
or set
, and it doesn't work with objects.
const arr = [1, 2, 3];
for (let value of arr) {
console.log(value);
}
/*
output:
1
2
3
*/
find
is used to find the first element in an array that matches a given condition, and returns that element. If no element matches the condition, find
returns undefined
.
const numbers = [1, 2, 3, 4, 5];
const result = numbers.find(num => num > 3);
console.log(result); // Output: 4
filter
, on the other hand, is used to create a new array containing all elements that match a given condition. if no element matches the condition, filter
will return an empty array
.
const numbers = [1, 2, 3, 4, 5];
const result = numbers.filter(num => num > 3);
console.log(result); // Output: [4, 5]
1.call
:
The call method is used to invoke a function with a given this
value and arguments provided individually. It accepts the this
context as its first argument, followed by arguments as comma-separated values if they are existed.
example 1:
function add(a, b) {
return a + b + this.value;
}
const obj = { value: 10 };
const result = add.call(obj, 5, 2); // passing `this` as `obj` and `5` and `2` as arguments
console.log(result); // output: 17
example 2:
const employee1 = {
name: "ahmed",
salary: 4000,
calcSalaryWithBonus: function () {
this.salary = this.salary + 500;
console.log(`${this.name}'s salary after bonus is ${this.salary}`);
}
}
employee1.calcSalaryWithBonus(); //output: ahmed's salary after bonus is 4500
const employee2 = {
name: "Aya",
salary: 3000
}
//we want to call calcSalaryWithBonus in the context of emplyee2
//in other word reuse calcSalaryWithBonus for other object that doesn't have this method
employee1.calcSalaryWithBonus.call(employee2); //output: Aya's salary after bonus is 3500
2.apply
:
The apply method is similar to call but takes arguments as an array
. It is used to invoke a function with a given this
value and an array of arguments.
example 1:
function add(a, b) {
return a + b + this.value;
}
const obj = { value: 10 };
const result = add.apply(obj, [5, 2]); // passing `this` as `obj` and `[5, 2]` as arguments array
console.log(result); // output: 17
example 2:
function calcSalaryWithBonus(bonus) {
this.salary = this.salary + bonus;
console.log(`${this.name}'s salary after bonus is ${this.salary}`);
}
const employee = {
name: "Mohamed",
salary: 7000
}
calcSalaryWithBonus.apply(employee, [500]); //output: Mohamed's salary after bonus is 7500
3.bind
:
The bind method is used to create a new function
that, when called, has its this
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
example 1:
function add(a, b) {
return a + b + this.value;
}
const obj = { value: 10 };
const addWithObj = add.bind(obj); // creating a new function with `this` bound to `obj`
const result = addWithObj(5, 2); // calling the new function with arguments `5` and `2`
console.log(result); // output: 17
example 2:
function calcSalaryWithBonus(bonus) {
this.salary = this.salary + bonus;
console.log(`${this.name}'s salary after bonus is ${this.salary}`);
}
const employee = {
name: "Mohamed",
salary: 7000
}
const calSalFunc = calcSalaryWithBonus.bind(employee, 500);
calSalFunc(); //output: Mohamed's salary after bonus is 7500
calSalFunc(); //output: Mohamed's salary after bonus is 8000
calSalFunc(); //output: Mohamed's salary after bonus is 8500
console.log(employee.salary); //output: 8500
shallow and deep copying
can occur with objects
and arrays
, because both are reference
types that are stored as references to their location in memory.
Shallow Copy
: means that only the first
level of the object/array is copied, while any deeper levels of the object/array are still referenced by the original object/array.
A shallow copy of an object can be achieved using the spread operator (…)
or using Object.assign()
method.
Here's an example that demonstrates how a shallow copy works in objects:
const obj1 = {
name: 'ahmed',
age: 33,
hobbies: ['reading', 'cooking', 'hiking']
};
//const obj2 = {...obj1}
const obj2 = Object.assign({}, obj1);
obj2.hobbies.push('coding');
console.log(obj1); // { name: 'John', age: 30, hobbies: ['reading', 'cooking', 'hiking', 'coding'] }
console.log(obj2); // { name: 'John', age: 30, hobbies: ['reading', 'cooking', 'hiking', 'coding'] }
In this example, obj1
is an object with three properties: name, age, and hobbies
. obj2
is a shallow copy
of obj1
created using Object.assign()
. When we modify the hobbies property of obj2
by pushing a new hobby, that modification is reflected in both obj1
and obj2
. This is because obj2.hobbies
is still referencing the same memory
location as obj1.hobbies
.
You can create a shallow copy of an array using the spreed operator(...)
and the slice()
, Array.from()
and concat()
methods.
Here's an example that demonstrates how a shallow copy works with an array:
const arr1 = [1, 2, [3, 4]];
const arr2 = arr1.slice();
arr2[2].push(5);
console.log(arr1); // [1, 2, [3, 4, 5]]
console.log(arr2); // [1, 2, [3, 4, 5]]
Whenever the original array contains objects or arrays, those objects or arrays will still be referenced by both the original array and the copy, because the copy is only a shallow copy.
Deep copy
: means that all
levels of the array or object are copied. This is a true copy of the object. A deep copy creates a completely new object/array with all properties and values copied to new memory
locations.
If you want to create a deep copy of an object/array, you can use JSON.parse(JSON.stringify())
.
// Deep copy of an object
const obj1 = {
name: 'ahmed',
age: 30,
address: {
city: 'Cairo',
country: 'Egypt'
}
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.address.city = 'Alex';
console.log(obj1.address.city); // 'Cairo'
console.log(obj2.address.city); // 'Alex'
// Deep copy of an array of objects
const arr1 = [
{ name: 'Wafaa', age: 30 },
{ name: 'Ahmed', age: 25 },
{ name: 'Hend', age: 35 }
];
const arr2 = JSON.parse(JSON.stringify(arr1));
arr2[0].age = 40;
console.log(arr1[0].age); // 30
console.log(arr2[0].age); // 40
==
and ===
are comparison operators that are used to compare two values for equality
. However, they differ in how they perform the comparison:
The ==
performs a loose comparison
, also known as type coercion
. It compares the values for equality after converting their data types if necessary. For example, 5 == '5'
would return true
, because the string '5' is converted to the number 5 before the comparison is made. However, this can sometimes lead to unexpected results or or subtle bugs in your code.
The ===
operator performs a strict comparison
, also known as type checking
. It compares the values for equality without converting their data types. For example, 5 === '5'
would return false
, because the two values have different data types.
Using ===
is generally considered to be safer and more reliable than using ==
, because it avoids the potential pitfalls of type coercion.
parseInt
and Number
are both built-in functions in JavaScript that convert a given value to a number.
parseInt
: If the string does not represent a valid integer, parseInt returns NaN
(Not a Number).
var age = "30";
var intAge = parseInt(age);
console.log(intAge); // 30
console.log(parseInt("Hello")); // Output: NaN
console.log(parseInt(true)); // Output: NaN
console.log(parseInt(false)); // Output: NaN
console.log(parseInt({})); // Output: NaN
Number
It can also be used as a function to convert strings or other values to numbers and can be used to convert values in other number systems (such as binary and hexadecimal) to numbers.
console.log(Number("30")); // Output: 1050
// Using Number to convert non-string values
// Converting a boolean to a number
console.log(Number(true)); // Output: 1
console.log(Number(false)); // Output: 0
// Converting an object to a number
console.log(Number({})); // Output: NaN (Not a Number)
console.log(Number("Hello")); // Output: NaN (Not a Number)
// convert a binary string to a number
var binaryStr = "1010";
var num = Number.parseInt(binaryStr, 2);
console.log(num); //Output: 10
// convert a hexadecimal string to a number
var hexStr = "FF";
var num = Number.parseInt(hexStr, 16);
console.log(num); //Output: 255
When working with decimal numbers, parseInt
only returns the integer part of the decimal number, discarding any decimal part. Number
, on the other hand, returns the decimal number as is.
// Using parseInt to convert a decimal number to an integer
console.log(parseInt(3.14)); // Output: 3
console.log(parseInt(3.99)); // Output: 3
// Using Number to convert a decimal number to a number
console.log(Number(3.14)); // Output: 3.14
console.log(Number(3.99)); // Output: 3.99
Arrow functions and normal functions are both used in JavaScript to define and execute functions, but they differ in their syntax and behavior
Normal functions
have a more verbose syntax
, which is much longer and often using more characters, symbols, and keywords than necessary.
Arrow functions
have a more concise syntax
, which is a shorter and more readable way of writing code.
// normal function
function add(a, b) {
return a + b;
}
// arrow function
const add = (a, b) => a + b;
Normal functions
When a regular method is called on an object, the this
keyword inside the method is automatically set to the object that the method is called on.
const person = {
name: "ahmed",
age: 30,
sayHi: function () {
console.log(`Hi, my name is ${this.name}`)
}
};
person.sayHi(); //Hi, my name is ahmed
sayHi
method is defined as a regular function within the person object, and regular functions in JavaScript are automatically bound to the object they are called on.
Arrow functions
don't create their own scope and don't bind this
to that scope. Instead, they use the this
value of their parent scope
- example 1:
const person = {
name: "ahmed",
age: 30,
sayHi: () => {
console.log(`Hi, my name is ${this.name}`)
}
};
person.sayHi(); //Hi, my name is undefined
in the previous example, the arrow function is defined inside the person
object, but this
inside the arrow function refers to the global
object, not the person
object.
The this
value refer to the enclosing scope where the arrow function is defined, not the object that the method is a property of.
- example 2:
const person = {
name: 'ahmed',
sayHi: function() {
setTimeout(() => {
console.log(`Hi, my name is ${this.name}`);
}, 1000);
}
};
person.sayHi() //"Hi, my name is ahmed" after 1 second
In the above example, this.name
is correctly referring to the name property of the person object, because the arrow function used with setTimeout is defined inside the sayHi
method.
In contrast, in the first example, the arrow function is defined at the top level and is not part of any object or method. Therefore, this inside the arrow function refers to the global object, which does not have a name property, resulting in undefined
.
A pure function
is a function that always returns the same result if the same arguments(input) are passed in the function, and does not cause any side effects. In other words, a pure function has no external dependencies, no side effects, and does not modify its inputs.
An impure function
, on the other hand, is a function that does not satisfy these criteria. It can return different results for the same inputs, cause side effects such as modifying global variables or making API calls, or depend on external state.
example to demonstrate the difference between a pure and an impure function:
// Pure function
function add(a, b) {
return a + b;
}
// Impure function
let counter = 0;
function incrementCounter() {
counter++;
}
the add
function is pure because it always returns the same result for the same inputs and does not cause any side effects. The incrementCounter
function is impure because it modifies a global variable counter, causing a side effect.
setTimeout
is a JavaScript function that sets a timer to execute a function or code snippet after a specified amount of time has passed. For example:
setTimeout(function(){ console.log("Hello, World!"); }, 1000);
The previous code sets a timer to execute the function console.log("Hello, World!")
after 1,000 milliseconds (or 1 second) have passed.
setInterval
is similar to setTimeout, but it repeatedly executes the function or code snippet at a specified interval of time. For example:
setInterval(function(){ console.log("Hello, World!"); }, 1000);
the previous code sets a timer to execute the function console.log("Hello, World!")
every 1,000 milliseconds (or 1 second) indefinitely.
The spread operator (...)
is used to spread the elements of an iterable object (like an array or a string) into separate elements or arguments. It allows you to:
- concatenate multiple arrays into a single array (the same action can be done with objects).
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2];
console.log(arr3); // Output: [1, 2, 3, 4, 5, 6]
- spread an array into a list of elements when calling a function.
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // Output: 6
The rest parameter (...)
syntax allows a function to accept an infinite number of arguments as an array. The rest parameter must be the last parameter in a function.
function sum(...args) {
return args.reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3)); // Output: 6
console.log(sum(1, 2, 3, 4, 5)); // Output: 15
✔️ Variable Scoping in Javascript (coming soon)
✔️ Looping Methods in Javacript (coming soon)
✔️ Mutated vs Non-Mutated Array Methods
✔️ Soft Binding vs Hard Binding in javascript (coming soon)
✔️ The Magic of Spreed Operator (...) in Javacript (coming soon)
🕴️ Linkedin: Dragon Slayer 🐲
📝 Articles: All Articles written by D.S