โณ javascript + typescript fundamentals
var vs let vs const
- var: old, function scoped, hoisted
- let: block-scoped, reassignable
- const: block-scoped, not reassignable
var car = "car"; // function scoped
let a = 5; // block scoped
const b = 1; // cannot reassign
๐ scope, hoisting, closures
- scope = where variables live
- hoisting = var + functions get "lifted" to top
- closures = inner function "remember" outer variables
๐งโโ๏ธ Hoisting in JavaScript
definition: hoisting is when JS "remembers" all your variables & function declarations before it actually runs the code.
memory allocation phase:
- all var, function, and class declarations are stored in memory.
- JS does this automatically, before executing line-by-line.
key insights:
- only declarations are hoisted - not initializations
- let and const are hoisted too, but not accessible before declaration
- function declarations are hoisted fully, function expressions are not.
๐งณ 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:
- no own
thisโ inherits from surrounding scope - cannot be used as constructors โ no
new - no
argumentsobject โ use rest parameters
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 };
- ES modules: static imports, tree-shakeable, async loading
- CommonJS: dynamic imports, synchronous, no tree-shaking
๐ 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
- refers to the current context(object, function or global)
- arrow function don't have their own this
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