Javascript

Javascript is a high-level, interpreted programming language that is primarily used for client-side web development. It is a versatile language that can be used for a wide range of applications, including web development, server-side development, and mobile app development.

Resources:

Topics

Variables

//A variable declared with var is limited
//to the function within which it is declared.
var name = 'Javascript';

//A variable declared with let is limited to the
//block (e.g., {}) within which it is declared.
let age = 25;

//const: immutable variable
const pi = 3.14;

Hoisting

Variables declared with var are hoisted to the top of their scope.

console.log(z); // undefined (hoisted)
var z = 3;
console.log(z); // 3

Variables declared with let are also hoisted, but they are not initialized.

console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 4;
console.log(a); // 4

Primitive Types

let number = 10;
let string = 'Hello, world!';
let boolean = true;
let nullValue = null;
let undefinedValue = undefined;
let object = {};
let array = [];
let symbol = Symbol('mySymbol'); // unique and immutable
let bigInt = BigInt(10); // BigInt is a new type in ES2020

Operators

//Arithmetic
let sum = 1 + 2;
let sub = 2 - 1;
let mul = 2 * 2;
let div = 4 / 2;
let mod = 5 % 2;

//Assignment
let a = 1;
a += 1;
a -= 1;
a *= 2;
a /= 2;
a %= 2;

//Comparison operators
5 == 5; // true
5 != 5; // false
5 > 3; // true
5 < 3; // false
5 >= 3; // true
5 <= 3; // false

//Logical operators
true && true; // true
true && false; // false
false || true; // true

//Bitwise operators
let a = 5;
let b = 3;
a & b; // 1
a | b; // 7
a ^ b; // 6
a << b; // 20
a >> b; // 1

//Unary operators
let num = +'5'; // 5
let num = -'5'; // -5
let a = 1;
a++; // a is now 2
let b = 1;
b--; // b is now 0
let c = true;
!c; // false

//Ternary operator
let x = true ? 1 : 0; // 1
let y = false ? 1 : 0; // 0

//typeof operator
typeof 'Hello'; // "string"
typeof 42; // "number"
typeof true; // "boolean"

//instanceof operator
let date = new Date();
date instanceof Date; // true

//spread and rest operator
let arr = [1, 2, 3];
let newArr = [...arr, 4, 5]; // [1, 2, 3, 4, 5]

Equal vs Strict Equal

Equal (==): Compares two values for equality after type conversion.

'5' == 5; // true

Strict equal (===): Compares two values for equality without type conversion.

'5' === 5; // false

Strings

let str = 'Hello, world!'; // single-quoted string
let str2 = "Hello, world!"; // double-quoted string
let str3 = `Hello, world!`; // template literal

//String concatenation
let str4 = str + ' ' + str2;
let str5 = str3 + ' ' + str2;

//String interpolation
let str6 = `Hello, ${str2}`;

Null safety

Checking for null and undefined

let value;
if (value !== null && value !== undefined) {
  // Safe to use `value`
}

The typeof Operator

let value;
if (typeof value !== 'undefined') {
  // Safe to use `value`
}

Optional Chaining (?.)

let user = {
  address: {
    city: 'New York'
  }
};

let city = user?.address?.city; // 'New York'
let street = user?.address?.street; // undefined (no error)

Short-Circuiting with Logical Operators (||)

let value = null;
let result = value || 'default'; // 'default'

Using Ternary Operator (?:)

let value = null;
let result = (value !== null && value !== undefined)
? value
: 'default'; // 'default'

Nullish coalescing operator (??)

let value = null;
let result = value ?? 'default'; // 'default'

Nullish coalescing operator with multiple values

let value = null;
let result = value ?? 'default1' ?? 'default2'; // 'default2'

Nullish coalescing assignment (??=)

let value = null;
value ??= 'default'; // 'default'

Null assertion operator (!)

let user = {
  address: {
    city: 'New York'
  }
};

let city = user.address.city; // 'New York'
let street = user.address.street; // undefined (no error)

let city2 = user.address!.city; // 'New York'
let street2 = user.address!.street; // undefined (error)

Collections

Array: is a collection of objects. It is an ordered list of values. Arrays can be created using the Array() constructor or using array literals.

//literal declaration
var arr = [1, 2, 3];

//object declaration
var arr3 = new Array(5);

//some methods
arr.push(4);
arr.pop();
arr.shift();
arr.unshift(1);
arr.splice(1, 1);
arr.sort();

Map: is a collection of key-value pairs. It is an unordered collection of key-value pairs. Maps can be created using the Map() constructor or using map literals.

var map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');

//some methods
map.get('key1');
map.has('key1');
map.delete('key1');
map.clear();

WeakMap: are similar to Maps but the keys must be objects, and they are weakly referenced.

var weakMap = new WeakMap();
weakMap.set(obj1, 'value1');
weakMap.set(obj2, 'value2');

//some methods
weakMap.get(obj1);
weakMap.has(obj1);
weakMap.delete(obj1);
weakMap.clear();

Set: is a collection of unique values. It is an unordered collection of values. Sets can be created using the Set() constructor or using set literals.

var set = new Set();
set.add('value1');
set.add('value2');
set.add('value2');

//some methods
set.has('value1');
set.delete('value1');
set.clear();

WeakSet: are similar to Sets but the values must be objects, and they are weakly referenced.

var weakSet = new WeakSet();
weakSet.add(obj1);
weakSet.add(obj2);

//some methods
weakSet.has(obj1);
weakSet.delete(obj1);
weakSet.clear();

Conditional Statements

//if-else
if (condition) {
  // code to execute if condition is true
} else {
  // code to execute if condition is false
}

//if-else if
if (condition1) {
  // code to execute if condition1 is true
} else if (condition2) {
  // code to execute if condition2 is true
} else {
  // code to execute if none of the conditions are true
}

//switch
switch (expression) {
  case value1:
    // code to execute if expression is value1
    break;
  case value2:
    // code to execute if expression is value2
    break;
  default:
    // code to execute if expression is not equal to value1 or value2
}

//ternary
var result = condition ? 'yes' : 'no';

Loops

//for
let a = [1, 2, 3];
for (let i = 0; i < a.length; i++) {
  console.log(a[i]);
}

//for-in
let obj = {
  a: 1,
  b: 2,
  c: 3
};

for (let key in obj) {
  console.log(key, obj[key]);
}

//for-of
let arr = [1, 2, 3];
for (let value of arr) {
  console.log(value);
}

//while
let i = 0;
while (i < 5) {
  console.log(i);
  i++;
}

//do-while
let i = 0;
do {
  console.log(i);
  i++;
}
while (i < 5);

//for-await
let list = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3),
]

for await (let value of list) {
  console.log(value)
}

Literal Objects

//object declaration
var obj = {
  name: 'John',
  age: 30,
  city: 'New York'
}

//nested object declaration
var obj2 = {
  name: 'John',
  age: 30,
  address: {
    street: '123 Main St',
    city: 'New York'
  }
}

//object with method
var obj3 = {
  name: 'John',
  age: 30,
  sayHello: function() {
    console.log('Hello');
  }
}

//object with computed property
var obj4 = {
  [`name${Math.random()}`]: 'John',
  age: 30
}

//shorthand property names
let firstName = "Jane";
let lastName = "Doe";
let age = 25;

let obj5 = {
    firstName,
    lastName,
    age
};
console.log(person.firstName); // Output: Jane

//using this to access properties
var calc = {
  value: 0,
  add: function (num) {
    this.value += num
    return this
  },
  sub: function (num) {
    this.value -= num
    return this
  },
  mult: function (num) {
    this.value *= num
    return this
  },
  div: function (num) {
    this.value /= num
    return this
  },
}

calc.add(5).sub(2).mult(3).div(2) // 4.5

Delete object property

let obj = {
  name: 'John',
  age: 30,
  city: 'New York'
}

delete obj.name
console.log(obj) // { age: 30, city: 'New York' }

Functions

//function declaration
function fn() {
  console.log('Hello');
}

//function expression
let fn = function() {
  console.log('Hello');
}

//function with parameters
function fn(name) {
  console.log('Hello ' + name);
}

//function with multiple parameters
function fn(name, age) {
  console.log('Hello ' + name + ' ' + age);
}

//function with default parameters
function fn(name = 'John', age = 30) {
  console.log('Hello ' + name + ' ' + age);
}

//function with rest parameters
function fn(...names) {
  console.log('Hello ' + names.join(' '));
}

//function with async/await
async function fn() {
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log('Hello');
}

//Arrow function
let fn = () => {
  console.log('Hello');
}

//passing a function as a parameter
function fn(callback){
  callback();
}
fn(() => { console.log('Hello') })

//imediately invoked function expression
(function(){
  console.log('Hello');
})()

//function with arguments property
function fn() {
  console.log(arguments)
}
fn(1, 2, 3) // [Arguments] { '0': 1, '1': 2, '2': 3 }

Differences between traditional Function and arrow functions

  1. Syntax:

    • Traditional: function name(args) { body }
    • Arrow: const name = (args) => expression
  2. this Binding:

    • Traditional: Depends on the call site.
    • Arrow: Lexical, inherits from parent scope.
  3. arguments Object:

    • Traditional: Available.
    • Arrow: Not available, use rest parameters.
  4. Constructor:

    • Traditional: Can be used as constructors.
    • Arrow: Cannot be used as constructors.
  5. Methods:

    • Traditional: Suitable for methods.
    • Arrow: Not suitable for methods.
  6. Implicit Return:

    • Traditional: Requires return statement.
    • Arrow: Implicit return for single expressions.

Functions as Constructors

When a function is used as a constructor, it initializes new objects with specific properties and methods. This is done using the new keyword, which creates an instance of the object with a unique prototype chain linking back to the constructor’s prototype.

function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.sayHello = function () {
  console.log('Hello, my name is ' + this.name)
}

const person = new Person('John', 30)
person.sayHello() // Output: Hello, my name is John

Example with returning an object:

function Person(name, age) {
    this.name = name;
    this.age = age;

    // Returning an object explicitly
    return {
        name: name,
        age: age,
        greet: function() {
            console.log(`Hello, my name is ${name} and I am ${age} years old.`);
        }
    };
}

const person1 = new Person("Alice", 30);
person1.greet(); // Output: Hello, my name is Alice and I am 30 years old.

Closures

A closure is a feature where an inner function has access to the outer (enclosing) function’s variables even after the outer function has finished executing. This happens because JavaScript functions form closures around the data they reference.

function createCounter() {
    let count = 0; // count is a private variable

    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount());  // 1

Classes

Key Features of JavaScript Classes.

  1. Class Declaration: Defines a class using the class keyword.
  2. Constructor: A special method for creating and initializing an object.
  3. Methods: Functions defined within a class.
  4. Inheritance: Creating a new class based on an existing class.
  5. Getters and Setters: Methods to get and set the values of properties.
  6. Static Methods: Methods defined on the class itself, not on instances.
// 1. Class Declaration
class Animal {
  _name

  // 2. Constructor
  constructor(name) {
    this._name = name
  }

  // 3. Method
  speak() {
    console.log(`${this.name} makes a sound.`)
  }

  // 5. Getter
  get name() {
    return this._name
  }

  // 5. Setter
  set name(value) {
    if (value.length < 3) {
      console.log('Name is too short.')
      return
    }
    this._name = value
  }

  // 6. Static Method
  static identify() {
    console.log('This is an Animal class.')
  }
}

Inheritance:

// 4. Inheritance
class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Call the parent class constructor
    this.breed = breed;
  }

  // Overriding a method
  speak() {
    console.log(`${this.name} barks.`);
  }
}

let dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // Rex barks.
console.log(dog.breed); // German Shepherd

Promises

Promises are used to handle asynchronous operations. A promise represents an operation that hasn’t completed yet but is expected to in the future.

// Creating a Promise
let promise = new Promise((resolve, reject) => {
  let success = true; // Simulating a condition
  if (success) {
    resolve("Operation successful!");
  } else {
    reject("Operation failed.");
  }
});

// Handling Promises
promise
  .then((message) => {
    console.log(message); // Operation successful!
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    console.log("Operation complete.");
  });

// Chaining Promises
let promiseChain = new Promise((resolve, reject) => {
  setTimeout(() => resolve(10), 1000);
});

promiseChain
  .then((result) => {
    console.log(result); // 10
    return result * 2;
  })
  .then((result) => {
    console.log(result); // 20
    return result * 3;
  })
  .then((result) => {
    console.log(result); // 60
  })
  .catch((error) => {
    console.log(error);
  })

// Promise resolve
let promise1 = Promise.resolve(3);

let promise2 = new Promise((resolve, reject) =>
  setTimeout(resolve, 2000, "foo")
);

let promise3 = 42;

// Promise all
Promise
  .all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // [3, "foo", 42]
  });

Async/Await

Async/await is a way to write asynchronous code that looks like synchronous code. It allows you to write asynchronous code in a more readable and maintainable way.

// Async function
async function fetchData() {
  try {
    let response = await fetch('https://server/endpoint/1');
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  } finally {
    console.log('Fetch attempt completed.');
  }
}

fetchData();

// Concurrency with Promise.all
async function fetchMultipleData() {
  try {
    let [post1, post2] = await Promise.all([
      fetch('https://server/endpoint/1')
      .then(res => res.json()),
      fetch('https://server/endpoint/2')
      .then(res => res.json())
    ]);
  } catch (error) {
    console.error('Error fetching multiple data:', error);
  }
}

fetchMultipleData();

All async functions are a promise generator, which means they return a promise that resolves to the result of the async function.

async function asyncMessage() {
  return 'hello world'
}

asyncMessage()
  .then((message) => console.log(message))
  .catch((error) => console.error(error))
  .finally(() => console.log('Async message complete.'))

Error Handling

// Basic try...catch
try {
  let result = 10 / 0;
  console.log(result);
  nonExistentFunction();
} catch (error) {
  console.error('An error occurred:', error.message);
}

// Using finally
try {
  let result = 10 / 0;
  console.log(result);
} catch (error) {
  console.error('An error occurred:', error.message);
} finally {
  console.log('This runs regardless of an error.');
}

// Custom Error
try {
  let age = 15;
  if (age < 18) {
    throw new Error('Age must be 18 or above.');
  }
} catch (error) {
  console.error('Custom Error:', error.message);
}

// Handling Promises with .catch
let promise = new Promise((resolve, reject) => {
  let success = false;
  if (success) {
    resolve('Operation successful!');
  } else {
    reject('Operation failed.');
  }
});

promise
  .then((message) => {
    console.log(message);
  })
  .catch((error) => {
    console.error('Promise Error:', error);
  });

// Handling async/await with try...catch
async function fetchData() {
  try {
    let response = await fetch('https://server/endpoint/1');
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Async/Await Error:', error.message);
  }
}

fetchData();

Generators

a generator is a special type of function that can pause its execution and later resume from where it paused. This is achieved using the function* syntax and the yield keyword.

// Generator function example
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

// Using the generator
const gen = numberGenerator();

console.log(gen.next().value); // Output: 1
console.log(gen.next().value); // Output: 2
console.log(gen.next().value); // Output: 3
console.log(gen.next().value); // Output: undefined

// Generator for Fibonacci sequence
function* fibonacciGenerator() {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const fib = fibonacciGenerator();
console.log(fib.next().value); // Output: 0
console.log(fib.next().value); // Output: 1
console.log(fib.next().value); // Output: 1
console.log(fib.next().value); // Output: 2
console.log(fib.next().value); // Output: 3
console.log(fib.next().value); // Output: 5

Keywords

KeywordDescription
awaitPauses async function execution until a Promise is resolved
breakExits a loop or switch statement
caseDefines a case in a switch statement
catchHandles exceptions in try…catch blocks
classDeclares a class
constDeclares a block-scoped, read-only named constant
continueSkips the rest of the current loop iteration and continues with the next one
debuggerInvokes any available debugging functionality
defaultSpecifies the default case in a switch statement
deleteDeletes an object’s property
doExecutes a block of code while a condition is true
elseSpecifies the block of code to be executed if a condition is false
exportExports a module, making it available for imports in other modules
extendsSets up inheritance between classes
falseRepresents a boolean false value
finallyExecutes code after try…catch regardless of the result
forCreates a loop that consists of three optional expressions
functionDeclares a function
ifExecutes a block of code if a specified condition is true
importImports functions, objects, or primitives exported from a module
inChecks if a property exists in an object
instanceofTests if an object is an instance of a class or constructor function
letDeclares a block-scoped variable
newCreates an instance of a user-defined object type or class
nullRepresents the intentional absence of any object value
returnExits a function and optionally returns a value
staticDefines a static method for a class
superRefers to the parent class
switchExecutes a block of code among many alternatives
thisRefers to the current object
throwThrows an exception
trueRepresents a boolean true value
tryImplements error handling by wrapping code that might throw an error
typeofReturns the type of a variable, object, function, or expression
varDeclares a variable
voidSpecifies an expression to be evaluated without returning a value
whileCreates a loop that executes as long as the condition is true
withExtends the scope chain for a statement
yieldPauses and resumes a generator function