โณ javascript + typescript fundamentals

var vs let vs const

var car = "car"; // function scoped
let a = 5; // block scoped
const b = 1; // cannot reassign

๐ŸŒ€ scope, hoisting, closures

๐Ÿง™โ€โ™€๏ธ Hoisting in JavaScript

definition: hoisting is when JS "remembers" all your variables & function declarations before it actually runs the code.

memory allocation phase:

key insights:

๐Ÿงณ Closures

a closure is when an inner function remembers the variables from its outer function, even after the outer function has finished running.

they are not only in JS, but it means that functions enclose different variables that are there when the function is declared. And by enclosing, it means they remember them. So basically it means whenever you import or use a function, you also get by default all the scope where that function was created.

so if there is a variable at the root level, the function has access to that forever, until you never use that function again.

const name = "Olga";

export function sayHello() {
  console.log(name);
}

If someone imports and runs this, the function will still work because even if they provide an argument or not, it will actually look in the function scope and then in the global and find this variable.

Even if this variable is not present where this function is being called, as long as it's present in the module โ€” (name = "Olga" still shows).

Another way you could show this is if you return another function. Let's say you actually create:

export function createSayHello() {
  const name = "Olga"; // now 'name' is in the scope of createSayHello

  return function sayHello() {
    console.log(name);
  }
}

then:

const sayHelloOlga = createSayHello();
sayHelloOlga(); // still works โœ…

so scope is basically whatever you see that is surrounded by curly brackets {}. So if I use sayHello outside here, it will still work.

Let's rewrite the full example again just to make it clearer:

export function createSayHello() {
  // 'name' was enclosed by sayHello
  const name = "Olga";

  return function sayHello() {
    console.log(name);
  };
}

const sayHelloOlga = createSayHello();
sayHelloOlga(); // still works

i would get no exception, even if in the scope I do not have a name. So the function is using a variable called name. it is not present in this global scope. however, because the function was created in this scope, it has access to this variable.

so that is closure โ€” because we can say that name was enclosed by sayHello. you do not only get the function โ€” you get the whole scope, the whole lexical environment ๐ŸŒ

used for data privacy, stateful functions, and async logic.

โš ๏ธ what is the drawback of this?

it puts a lot of pressure on memory ๐Ÿง  because everything that is in the scope is still referenced by this function as long as the function is being used.

so we cannot remove it from memory, we cannot release that memory by the garbage collection algorithm.

so basically, JavaScript cannot clean it up โ€” it cannot get rid of anything that is in the closure scope of this function. and that means you put a lot of pressure on memory.

that is why traditionally we all know that JavaScript is not a super memory-efficient language โ€” it actually needs a lot of RAM memory ๐Ÿ’พ.


โžก๏ธ arrow functions

arrow functions are a shorter syntax for writing functions, introduced in ES6.

// traditional function
function add(a, b) { return a + b; }

// arrow function
const add = (a, b) => a + b;

// with a body (needs explicit return)
const multiply = (a, b) => {
  const result = a * b;
  return result;
};

// single parameter (no parentheses needed)
const double = x => x * 2;

key differences from regular functions:

const obj = {
  name: "olga",
  greetArrow: () => console.log(this.name), // โŒ undefined
  greetRegular() { console.log(this.name); } // โœ… "olga"
};

๐Ÿ”“ object / array destructuring

object destructuring

const user = { name: "olga", age: 25, city: "berlin" };
const { name, age, city } = user;

// renaming
const { name: userName, age: userAge } = user;

// default values
const { name, country = "unknown" } = user;

array destructuring

const colors = ["red", "green", "blue"];
const [first, second, third] = colors;
const [, , last] = colors; // skip values
const [head, ...rest] = colors; // rest

in function parameters (very common in react)

function greet({ name, age }) {
  console.log(name);
}

๐ŸŒŠ spread operator (...)

with arrays

const a = [1, 2, 3];
const b = [4, 5, 6];
const combined = [...a, ...b]; // [1, 2, 3, 4, 5, 6]
const copy = [...a]; // shallow copy

with objects

const user = { name: "olga", age: 25 };
const updated = { ...user, age: 26 };
// { name: "olga", age: 26 }

rest parameters

function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10

๐Ÿ“ฆ modules (import / export)

named exports

// utils.js
export const PI = 3.14;
export function add(a, b) { return a + b; }

// main.js
import { PI, add } from './utils.js';

default exports

export default function log(msg) { console.log(msg); }
import log from './logger.js'; // can name it anything

ES modules vs CommonJS

// ES modules (modern, browser + node 14+)
import { something } from './file.js';
export const value = 42;

// CommonJS (older, node.js traditional)
const { something } = require('./file.js');
module.exports = { value: 42 };

๐Ÿ“ types, interfaces, generics (typescript)

๐Ÿงฉ types

let age: number = 25;
let name: string = Olga;
let isAdmin: boolean = true;

๐Ÿงท Interfaces

interface User {
    id: number;
    name: string;
}

const user: User = {id: 1; name: "Olga" };

๐ŸŽ Generics

function wrapInArray<T>(value: T): T[] {
    return [value];
}

const nums = wrapInArray<number>(5); // [5]

๐Ÿ“ฆ objects, arrays, maps, sets

Objects

Key-value pairs

const user = { name: "Alex", age: 22};

Arrays

Ordered collections

const numbers = [1, 2, 3];

Maps

Key-value with any type keys

const map = new Map();
map.set("a", 1);

Sets

Unique values only

const set = new Set([1, 2, 2, 3]) // {1, 2, 3}

๐Ÿ›๏ธ classes, inheritance, this

class Animal {
    constructor(public name: string) {}

    speak() {
        console.log(`${this.name} makes a sound`);
    }
}

class Dog extends Animal {
    speak() {
        console.log(`${this.name} barks`);
    }
}

const d = new Dog("Rex");
d.speak(); // Rex barks

this keyword

common gotchas & tricks

โŒ Don't use == โ†’ use === for strict equality
โœ… Use ?. (optional chaining) to safely access properties
โœ… Use destructuring for cleaner code
โœ… Prefer const by default, only use let when needed
โŒ Avoid mutating objects/arrays directly โ€“ prefer immutability