> ## Documentation Index
> Fetch the complete documentation index at: https://learn.narau.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript

JavaScript is a high-level, just-in-time compiled programming language. Technically, It is still initially interpreted for a quick startup, but it is compiled for long-term speed.

Three core pillars of JavaScript are types, scope, and objects.

## Types

Take a look at this example:

```js "++" "typeof" theme={null}
var n = '5'
console.log(`type of ${n++} is ${typeof(n)}`) // type of 5 is number
```

1. The `pre-increment` operator converts the string into number first, and then returns that number before incrementing.

2. The `typeof` operator returns a string indicating the data type of its operand.

3. That type conversion happened above is called `type coercion`.

```js theme={null}
let result = '5' - 2; // 3 (implicit type coercion to number)
result = '5' + 2 // 52 (weird?)
```

If you want to know more about `implicit type coercion`, see  <Card title="JavaScript Specification" href="https://tc39.es/ecma262/" icon="https://api.iconify.design/vscode-icons:file-type-js-official.svg" type="warning" horizontal>
ECMAScript Language Specification</Card>

4. In JavaScript, everything is an object is false actually. All JavaScript values, except `primitives`, are objects.

   1. Primitive types include `undefined`, `string`, `number`, `boolean`, `object`, `symbol`.

   2. Undeclared types include `null`, `function`, `array`, and `bigint`.

   3. `functions` are objects that are `callable`, combining function invocation with object capabilities.

      1. `typeof(Function)` is `function` even tho its not a primitive type and basically treated as object.

   4. `typeof(null)` is also object btw (historical bug in language?), just for backward compatibility reasons.

   5. `typeof([1,2,3])` is `object` not `array`.

      1. To check if a value is an array, use `Array.isArray([1,2,3])` , which returns a `boolean` value.

   6. `bigint` is also there btw, `typeof(42n)` is `bignint`.

5. States of 'emptiness' in JS can be `undefined`, `undeclared`, and `uninitialized` (in theory only ig?)

6. Temporal Dead Zone `(TDZ)` is a state where block-scoped variables are uninitialized and cannot be accessed.

7. `NaN` stands for 'Not a Number' but actually represents invalid number.

   1. `NaN === NaN` is `false` but `undefined === undefined` is `true`. 

   2. `Number.isNaN()` does not perform type coercion, and returns `true` only if the value is strictly `NaN`.

   ```js "true" "false" theme={null}
   isNaN('2a') // true

   Number.isNaN('2a') // false
   Number.isNaN(NaN) // true
   ```

   3. `typeof(NaN)` is `number` btw.

8. `-0` exists in JS btw, but for what? can represent a state where an object has stopped (at 0) but is still facing a specific direction (up or down).

   ```js "===" theme={null}
   -0 === 0 // true

   Object.is(0, -0) // false
   Object.is(-0, -0) // true
   ```

## Fundamental Objects

The JavaScript standard library includes several fundamental `built-in objects` used to represent core language constructs and manage data.

```js theme={null}
new Object(), Array(), Function(), Date(), RegExp(), Error()

// Dont use new with String(), Number(), Boolean()
```

## Type Coercion

```js "Number" theme={null}
+"29" // 29 

// + invokes Number('29'), can be used to avoid "14" + 1 = '141'
```

## Boxing

Boxing is the automatic process of wrapping a `primitive value` (e.g., a `string`, `number`, or `boolean`) into a temporary object of its corresponding `wrapper type` so that you can access methods and properties on it.

```js "length" theme={null}
let name = "John Doe"; // 'name' is a primitive string
console.log(name.length); // 8

/* JavaScript temporarily boxes "John Doe" into a String object to 
access the 'length' property, then discards the object. */
```

## Equality

```js "==" "===" "true" theme={null}
== allows coercion (types different) & prefers numeric comparison

=== disallows coercion (types same)

42 == [42] // true, coz String([]) is '' and 42 == '42' is true.
```

```js "converts" LooseEquality.js icon="js" theme={null}
[] == ![] // true?

[] == ![]    // ![] evaluates to false
[] == false  // Boolean false converts to Number 0
[] == 0      // Array converts to primitive string
"" == 0      // Empty string converts to Number 0
 0 == 0      // Comparison results in true
```

1. You dont really need `TypeScript` if you know JavaScript in depth.

2. Using `===` everywhere means you not sure about the types (trust issues?)

3. Making `types` known and obvious leads to better code. If types are known, `==` is best.

## Scope

Scope basically means where to look for things. JavaScript organizes scopes with `functions` and `blocks`.

1. `Lexical scope` means that the scope of variables and functions is determined by their physical location.
   * It does not depend on where the function is called at runtime.

```js LexicalScope.js icon="js" theme={null}
let a = 1;

function foo() {
  console.log(a);
}

function bar() {
  let a = 2;
  foo();
}

bar(); // 1
```

2. `Strict mode` enforces stricter parsing and error handling, helping catch common bugs and prevent unsafe behavior.

```js del="ReferenceError" StrictMode.js icon="js" theme={null}
"use strict";

x = 10;      // ReferenceError

function show() {
  console.log(this);
}

show(); // undefined
```

3. `Nested scope` means inner scopes can access variables from outer scopes, but outer scopes cannot access inner variables.

```js del="ReferenceError: b is not defined" "accessible" NestedScope.js icon="js"  theme={null}
function outer() {
  let a = 10;

  function inner() {
    var b = 20
    console.log(a); // accessible
  }

 // [!code --]
  console.log(b)  // ReferenceError: b is not defined

  inner();
}

outer();
```

4. `Function declarations` load before any code is executed (hoisted, can be used before definition).

```js theme={null}
#function-declaration.js
function sum(a, b) {
  return a + b;
}
```

5. `Function expressions` load only when the interpreter reaches that line of code (only the variable is hoisted).

   1. It can be `named` or `anonymous`, but named function expressions are generally preferred for better debugging, self-documenting code and reliable function self-reference (e.g., `recursion`).

```js "named" "anonymous" theme={null}
#function-expression.js
const sum = function(a, b) { // anonymous
  return a + b;
}

const sum = function sum(a, b) { // named
  return a + b;
}
```

6. `Arrow functions` should be used only when necessary for the same reasons as above.

```js theme={null}
#arrow-function.js
const greet = (name) => {
  return `Hello, ${name}!`;
};

```

```java theme={null}
#order-of-precedence.js
Function Declaration 
        ^
Named Function Expression
        ^
Anonymous Function Expression
```

7. The `this` keyword is determined by how a function is called, rather than where it is defined, giving it behavior similar to `dynamic scoping`.

```js "this" del="undefined" theme={null}
#this.js
function show() {
  console.log(this.name);
}

const user1 = { name: "Alice", show };
const user2 = { name: "Bob", show };

user1.show(); // Alice
user2.show(); // Bob

const f = user1.show;
f(); // undefined
```

7. In JavaScript, the `call()`, `apply()`, and `bind()` methods are all used to explicitly set the value of the `this` keyword inside a function.

   1. `call()` invokes the function immediately with a specified `this` value and arguments provided individually.

   ```js "call" "this" "Hello, my name is Adam!" theme={null}
   #call.js
   const person = {
     name: "Adam"
   };

   function greet(greeting, punctuation) {
     console.log(`${greeting}, ${this.name}${punctuation}`);
   }

   greet.call(person, "Hello", "!"); // Hello, Adam!
   ```

   2. `apply()` is similar to `call()`, but it takes arguments as an array instead of individually.

   ```js "apply" "this" "Hello, my name is Adam!" theme={null}
   #apply.js
   greet.apply(person, ["Hello", "!"]); // Hello, Adam!
   ```

   3. `bind()` returns a new function with a fixed `this` value, but does not invoke it immediately.

   ```js "bind" "this" "Hello, my name is Adam!" theme={null}
   #bind.js
   const hello = greet.bind(person);

   hello("Hello", "!"); // Hello, Adam!
   ```

   ```js "bind" "this" "Hello, my name is Adam!" theme={null}
   #bind-arguments.js
   const hello = greet.bind(person, "Hello", "!");

   hello(); // Hello, Adam!
   ```

8. `Function scoping` ensures variables declared inside a function are local to it, preventing external access and name conflicts

   1. `IIFE` is a function that is defined and executed immediately, creating a new scope to avoid polluting the `global namespace`.

```js "// Hi!" "// Hello!" theme={null}
#IIFE.js
(function greet() {
  var text = "Hi!"
  console.log(text); // Hi!
})();

(() => {
    var text = "Hello!"
    console.log(text); // Hello!
})()
```

9. `Block Scoping` means that variables declared with `let` and `const` are only accessible within the block they are defined in.
   * `var` is function-scoped and can lead to unintended consequences if used inside blocks.

```js del="ReferenceError: a is not defined" theme={null}
#block-scope.js
{
    let a = 1;
    var b = 2;
}

console.log(a) // ReferenceError: a is not defined
console.log(b) // 2
```

10. `var` is not always bad or replaced by `let` totally.

```js "x is still accessible here" theme={null}
#var.js
function example() {
  if (true) {
    var x = 10;
  }

  // x is still accessible here
  console.log(x); // 10
}

example();
```

11. `const` should be used for variables that are not meant to be reassigned, but it can be misleading when used with objects and arrays.
    * It only prevents reassignment of the variable itself, not the `mutation` of the object or array it references.

```js theme={null}
const teachers = ["Kyle", "Suzy"];

teachers[1] = "Brian"; teachers.push('Adam');
```

> \[!WARNING] Arrow Functions and `this`
> An arrow function is `this` bound (aka `.bind()`) to its parent function.

```js del="undefined" theme={null}
#arrow-function-this.js
const object = {
  a: 2,
  print: () => {
    console.log(this.a);
  }
}

object.print(); // undefined
```

```js "2" "get this from print's scope" theme={null}
#arrow-function-this.js
const object = {
  a: 2,
  print(){
    (() => console.log(this.a))() // get this from print's scope
  }
}

object.print(); // 2
```

```js "2" "undefined" theme={null}
#timeout-this.js
const object = {
  a: 2,
  print(){
    setTimeout(() => {
      console.log(this.a);
    }, 1000); // 2

    setTimeout(function() {
      console.log(this.a);
    }, 1000); // undefined
  }
}

object.print();
```

## Closures

A `closure` is the combination of a function and the lexical environment within which that function was declared.

* It allows an `inner function` to access variables from its `outer scope`, even after the outer function has finished executing.

* Essentially, `every function` in JavaScript is a closure, as it automatically preserves the scope where it was created.

* Closure can prevent `garbage collection` of an entire scope, variables and data might be retained in memory even if they are not directly used by the closure.

```js del="undefined (inaccessible from outside)" "1" theme={null}
#closure.js
function createCounter() {
  let count = 0; // Private variable
  return {
    increment() {
      console.log(++count);
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();

counter.increment(); // 1

console.log(counter.count); // undefined (inaccessible from outside)
```

```js theme={null}
#function.js
function ask(question) {
    setTimeout(function waitASec() {
        console.log(question)
    }, 100)
}

ask("What is closure?"); // What is closure?
```

1. `Closures` basically preserve a scope, not `values`.

```js theme={null}
#var.js
for(var i = 1; i <= 3; i++){
    setTimeout(function() {
        console.log(`i: ${i}`);
    }, i * 1000);

} // 4, 4, 4
```

```js theme={null}
#let.js
for(let i = 1; i <= 3; i++){
    setTimeout(function() {
        console.log(`i: ${i}`);
    }, i * 1000);

} // 1, 2, 3
```

## Module Pattern

In JavaScript, it is a design pattern used to `encapsulate` code, create private and public members (variables and methods), and avoid polluting the global scope.

* It can be achieved in the following ways:

  1. `IIFE` to create a private scope and return an object with public members.

```js theme={null}
#using-closure.js
const counterModule = (function () {
  let count = 0; // private

  function increment() {
    count++;
  }

  function getCount() {
    return count;
  }

  return {
    increment,
    getCount, // public API
  };
})();

```

2. `ES6 Modules` using `export` and `import` statements to define and use modules.

```js "export" theme={null}
#counter.mjs
let count = 0;

export function increment() {
  count++;
}

export function getCount() {
  return count;
}
```

```js "import" theme={null}
#main.js
import { increment, getCount } from "./counter.js";

import * as counter from "./counter.js";
```

3. `CommonJS Modules` using `module.exports` and `require()` for server-side JavaScript (Node.js).

```js "module.exports" theme={null}
#counter.cjs
let count = 0;

function increment() {
  count++;
}

function getCount() {
  return count;
}

module.exports = {
  increment,
  getCount,
};
```

```js "require" theme={null}
#main.js
const { increment, getCount } = require("./counter");

const counter = require("./counter");
```

## Hoisting

JavaScript does not actually `'hoist'` code at runtime, hoisting is a conceptual model.

1. During the `compilation` (parsing) phase, the engine registers declarations in memory before executing the code, which creates the appearance of hoisting.

## Event Loop

<Card title="Event Loop" type="warning" horizontal>Due to shortage of time, I'll just explain later.</Card>

## Asynchronous Programming

JavaScript is `single-threaded`, meaning it can only execute one operation at a time.

1. Asynchronous programming allows handling time-consuming operations without blocking the `main thread`.

### Callbacks

Before promises, callbacks were the primary way to handle asynchronous operations, but they often led to `callback hell` or `pyramid of doom`.

```js "callback hell" theme={null}
#callback-hell.js
getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      getMoreData(c, function(d) {
        console.log(d);
      });
    });
  });
});
```

### Promises

A `Promise` is an object representing the eventual completion or failure of an asynchronous operation.

* It is a placeholder for a future value that will eventually be available.

```js "resolve" "reject" theme={null}
#promise-basics.js
const promise = new Promise((resolve, reject) => {
  const success = true;
  
  if (success) {
    resolve("Operation successful!");
  } else {
    reject("Operation failed!");
  }
});
```

1. A promise can be in one of three states: `pending`, `fulfilled`, and `rejected`.

   * Once a promise is `settled` (fulfilled or rejected), its state cannot change.

```js "fulfilled" "pending" "<pending>" theme={null}
#promise-states.js
const p1 = Promise.resolve(42); // fulfilled immediately

const p2 = new Promise(resolve => {
  setTimeout(() => resolve(100), 1000);
}); // pending for 1 second

console.log(p1); // Promise { 42 }
console.log(p2); // Promise { <pending> }
```

### Then, Catch, Finally

The `.then()` method is used to handle the resolved value of a promise, while `.catch()` handles rejections.

```js "then" "catch" theme={null}
#then-catch.js
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
```

1. `.then()` accepts two optional `arguments:` a callback for success and one for failure.

```js "fulfilled" "rejected" theme={null}
#then-two-callbacks.js
promise.then(
  value => console.log('fulfilled:', value),
  error => console.log('rejected:', error)
);
```

2. `.finally()` runs regardless of whether the promise was fulfilled or rejected.

```js "finally" theme={null}
#finally.js
fetch('/api/data')
  .then(response => response.json())
  .catch(error => console.error(error))
  .finally(() => console.log('Request completed'));
```

### Promise Chaining

Each `.then()` returns a new promise, enabling method chaining and avoiding callback hell.

1. If you return a value in `.then()`, it wraps it in a resolved promise.

```js "chaining" theme={null}
#promise-chaining.js
Promise.resolve(5)
  .then(value => value * 2) // 10
  .then(value => value + 3) // 13
  .then(value => {
    console.log(value); // 13
    return value;
  });
```

### Promise Combinators

JavaScript provides several static methods to work with multiple promises.

1. `Promise.all()` waits for all promises to resolve, or rejects if any promise rejects.

```js "Promise.all" theme={null}
#promise-all.js
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])
  .then(values => console.log(values)); // [1, 2, 3]

// If any promise rejects, the entire Promise.all rejects
Promise.all([p1, Promise.reject('error'), p3])
  .catch(error => console.log(error)); // 'error'
```

2. `Promise.allSettled()` waits for all promises to settle (fulfilled or rejected) and returns their results.

```js "Promise.allSettled" theme={null}
#promise-allsettled.js
const promises = [
  Promise.resolve(1),
  Promise.reject('error'),
  Promise.resolve(3)
];

Promise.allSettled(promises)
  .then(results => console.log(results));

/* [
  { status: 'fulfilled', value: 1 },
  { status: 'rejected', reason: 'error' },
  { status: 'fulfilled', value: 3 }
] */
```

3. `Promise.race()` returns the result of the first promise to settle, fulfilled or rejected.

```js "Promise.race" theme={null}
#promise-race.js
let slow = new Promise(res => setTimeout(() => res('slow'), 20));
let fast = new Promise(res => setTimeout(() => res('fast'), 10));

Promise.race([slow, fast])
  .then(value => console.log(value)); // 'fast'
```

4. `Promise.any()` returns the first promise that fulfills, or rejects if all promises reject.

```js "Promise.any" theme={null}
#promise-any.js
const p1 = Promise.reject('error1');
const p2 = Promise.resolve('success');
const p3 = Promise.reject('error2');

Promise.any([p1, p2, p3])
  .then(value => console.log(value)); // 'success'

// If all reject, throws AggregateError
Promise.any([Promise.reject('error1'), Promise.reject('error2')])
  .catch(error => console.log(error)); // AggregateError
```

### Async/Await

`async/await` is syntactic sugar over promises, making asynchronous code look synchronous.

* An `async` function always returns a promise.
* `await` pauses execution until the promise resolves.

```js "async" "await" theme={null}
#async-await.js
async function fetchUser() {
  const response = await fetch('/api/user');
  const user = await response.json();
  return user;
}

fetchUser().then(user => console.log(user));
```

1. `await` can only be used inside `async` functions.

2. Error handling with `async/await` uses `try/catch`.

```js "try" "catch" theme={null}
#async-error-handling.js
async function getData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
}
```

### Promises with Array Methods

Working with promises in `array methods` requires careful handling to avoid common pitfalls.

1. `Array.forEach()` does not wait for promises and cannot be awaited.

```js del="This doesn't wait for promises to resolve" del="doesn't wait!" "Logs before fetches complete" theme={null}
#foreach-promise.js
const urls = ['/api/user/1', '/api/user/2', '/api/user/3'];

// This doesn't wait for promises to resolve
urls.forEach(async (url) => {
  const response = await fetch(url);
  console.log(await response.json()); // doesn't wait!
});

console.log('Done'); // Logs before fetches complete
```

2. `Array.map()` with promises returns an array of promises that can be used with `Promise.all()`.

```js "Promise.all" "Correct way: map returns array of promises" "Or with async/await" theme={null}
#map-promise.js
const urls = ['/api/user/1', '/api/user/2', '/api/user/3'];

// Correct way: map returns array of promises
const promises = urls.map(async (url) => {
  const response = await fetch(url);
  return response.json();
});

Promise.all(promises)
  .then(users => console.log(users));

// Or with async/await
async function fetchAllUsers() {
  const promises = urls.map(url => fetch(url).then(r => r.json()));
  const users = await Promise.all(promises);
  console.log(users);
}
```

3. Sequential processing with `for...of` loop ensures promises execute one after another.

```js "sequential" theme={null}
#sequential-promises.js
const urls = ['/api/user/1', '/api/user/2', '/api/user/3'];

async function fetchSequentially() {
  const results = [];
  
  for (const url of urls) {
    const response = await fetch(url);
    const data = await response.json();
    results.push(data);
  }
  
  return results;
}

fetchSequentially().then(users => console.log(users));
```

4. `Array.reduce()` can be used for sequential promise chaining.

```js "reduce" theme={null}
#reduce-promise-chain.js
const urls = ['/api/user/1', '/api/user/2', '/api/user/3'];

urls.reduce((promiseChain, url) => {
  return promiseChain.then(results => {
    return fetch(url)
      .then(response => response.json())
      .then(data => [...results, data]);
  });
}, Promise.resolve([]))
  .then(users => console.log(users));
```

### Common Pitfalls

1. Forgetting to `return` a promise in a `.then()` breaks the chain.

```js del="// undefined!" del="Forgot to return!" theme={null}
#forgot-return.js
fetch('/api/user')
  .then(response => {
    response.json(); // Forgot to return!
  })
  .then(data => console.log(data)); // undefined!
```

2. Mixing `async/await` with `.then()` unnecessarily.

```js del="Unnecessary mixing" theme={null}
#mixing-async-then.js
// Unnecessary mixing
async function getUser() {
  return fetch('/api/user')
    .then(response => response.json())
    .then(data => data);
}

async function getUser() {
  const response = await fetch('/api/user');
  return response.json();
}
```

3. Not handling promise `rejections`.

```js "catch" del="UnhandledPromiseRejectionWarning" "Always handle rejections" "Or with async/await" theme={null}
#unhandled-rejection.js
Promise.reject('error'); // UnhandledPromiseRejectionWarning

// Always handle rejections
Promise.reject('error').catch(err => console.error(err));

// Or with async/await
async function safeFunction() {
  try {
    await Promise.reject('error');
  } catch (error) {
    console.error(error);
  }
}
```

4. Using `await` in a loop unnecessarily makes operations sequential instead of parallel.

```js "sequential vs parallel" del="Slow: Sequential execution" "Fast: Parallel execution" theme={null}
#sequential-vs-parallel.js
// Slow: Sequential execution
async function fetchSequential(urls) {
  const results = [];
  for (const url of urls) {
    results.push(await fetch(url)); // Waits for each request
  }
  return results;
}

// Fast: Parallel execution
async function fetchParallel(urls) {
  const promises = urls.map(url => fetch(url));
  return Promise.all(promises);
}
```

5. Creating promises inside promises `(nested promises)` defeats the purpose of promise chaining.

```js del="nested" del="Promise nesting (callback hell 2.0)" "Flat promise chain" theme={null}
#nested-promises.js
// Promise nesting (callback hell 2.0)
fetch('/api/user')
  .then(response => {
    response.json().then(user => {
      fetch(`/api/posts/${user.id}`).then(response => {
        response.json().then(posts => {
          console.log(posts); // nested
        });
      });
    });
  });

// Flat promise chain
fetch('/api/user')
  .then(response => response.json())
  .then(user => fetch(`/api/posts/${user.id}`))
  .then(response => response.json())
  .then(posts => console.log(posts));
```

## Generators

A `generator` is a special function that can pause its execution and resume later, allowing you to produce a sequence of values over time.

1. Uses the `yield` keyword to pause execution and return a value.
2. It returns an `iterator` object with `next()`, `return()`, and `throw()` methods.

```js "function*" "yield" "next()" theme={null}
#generator-basics.js
function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numberGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
```

### Generator Methods

The iterator returned by a generator has three methods for controlling execution.

1. `next()` resumes execution until the next `yield`.

2. `return()` terminates the generator and returns the provided value.

```js "return" theme={null}
#generator-return.js
function* numbers() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numbers();

console.log(gen.next()); // { value: 1, done: false }

console.log(gen.return('done')); // { value: 'done', done: true }

console.log(gen.next()); // { value: undefined, done: true }
```

3. `throw()` throws an error inside the generator that can be caught with `try/catch`.

```js "throw()" theme={null}
#generator-throw.js
function* errorHandler() {
  try {
    yield 1;
    yield 2;
  } catch (error) {
    console.log('Caught:', error);
  }
}

const gen = errorHandler();
console.log(gen.next());
console.log(gen.throw('Error!'));

/* Caught: Error!
{ value: undefined, done: true } */
```

### Passing Values to Generators

The `next()` method can accept an argument that becomes the value of the `yield` expression.

```js "next(10)" theme={null}
#generator-input.js
function* calculator() {
  const a = yield 'Enter first';
  const b = yield 'Enter second';
  yield a + b;
}

const gen = calculator();
console.log(gen.next());   // { value: 'Enter first', done: false }
console.log(gen.next(1));  // { value: 'Enter second', done: false }
console.log(gen.next(5));  // { value: 6, done: false }
```

### Generators with Loops

Generators can be iterated with `for...of` loops, which automatically calls `next()` until `done` is `true`.

```js "for...of" theme={null}
#generator-loop.js
function* numbers() {
  yield 1
  yield 2
  yield 3
}

for (const num of numbers()) {
    console.log(num)
}
```

### Async Generators

`Async generators` combine generators with async/await.

```js "async function*" theme={null}
#async-generator.js
async function* asyncGenerator() {
  yield await Promise.resolve(1);
  yield await Promise.resolve(2);
  yield await Promise.resolve(3);
}

(async () => {
  for await (const value of asyncGenerator()) {
    console.log(value); // 1, 2, 3
  }
})();
```

## Classes

JavaScript `classes` are syntactic sugar over prototypes, introduced in ES6. They provide a cleaner syntax for creating objects and implementing inheritance.

1. Classes are not hoisted like function declarations.
2. Code inside classes runs in `strict mode` by default.

```js "class" theme={null}
#class-basics.js
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    return `Hi, I'm ${this.name}`;
  }
}

const john = new Person('John', 30);

console.log(john.greet()); // Hi, I'm John
```

The `instanceof` operator tests whether an object is an instance of a class.

```js "instanceof" theme={null}
#instanceof.js
class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
```

### Constructor

The `constructor` method is a special method called when creating a new instance with the `new` keyword.

```js "constructor" theme={null}
#constructor.js
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  get area() {
    return this.width * this.height;
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.area); // 50
```

### Class Methods

<Card title="Constructor vs `prototype`" type="tip">
  1. Methods defined inside `constructor` are re-created for every new instance of the object.
  2. Methods defined on the `prototype` are created only once and shared among all instances, providing better performance.
  3. Prototype is like a `blue-print` for objects.
</Card>

```js "methods" "Calculator" theme={null}
#class-methods.js
class Calculator {
  add(a, b) {
    return a + b;
  }
}

const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
```

```js "Generic Animal makes a sound" "this" theme={null}
#constructor-methods.js
class Animal {
  constructor(name) {
    this.name = name;

    this.speak = function(){
      return `${this.name} makes a sound`;
    }
  }
}

const animal = new Animal('Generic Animal');
console.log(animal.speak()); // Generic Animal makes a sound
```

### Static Methods

`Static methods` belong to the class itself, not to instances. They're called on the class directly.

```js "static" theme={null}
#static-methods.js
class MathUtils {
  static PI = 3.14159;
  
  static square(x) {
    return x * x;
  }
}

console.log(MathUtils.square(5)); // 25
```

### Getters and Setters

`Getters` and `setters` allow you to define methods that are accessed like properties.

```js "get" "set" "5" theme={null}
#getters-setters.js
class Circle {
  constructor(radius) {
    this._radius = radius;
  }
  
  get radius() {
    return this._radius;
  }
  
  set radius(value) {
    if (value <= 0) {
      throw new Error('Radius must be positive');
    }
    this._radius = value;
  }
}

const circle = new Circle(5);
console.log(circle.radius); // 5
circle.radius = 10;
```

### Private Fields

`Private fields` start with `#` and are only accessible within the class.

```js "#balance" del="SyntaxError: Private field" theme={null}
#private-fields.js
class BankAccount {
  #balance = 0;
  
  constructor(initialBalance) {
    this.#balance = initialBalance;
  }
  
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }
  
  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500

// console.log(account.#balance); // SyntaxError: Private field
```

### Private Methods

Private methods are also prefixed with `#` and can only be called from within the class.

```js "#processData" "[2, 4, 6]" theme={null}
#private-methods.js
class DataProcessor {
  #processData(data) {
    return data.map(x => x * 2);
  }
  
  process(data) {
    return this.#processData(data);
  }
}

const processor = new DataProcessor();
console.log(processor.process([1, 2, 3])); // [2, 4, 6]
```

### Inheritance

Classes can extend other classes using the `extends` keyword, inheriting properties and methods.

```js "extends" "Call parent constructor" "Rex barks" theme={null}
#inheritance.js
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Call parent constructor
    this.breed = breed;
  }
  
  speak() {
    return `${this.name} barks`;
  }
}

const dog = new Dog('Rex', 'Labrador');
console.log(dog.speak()); // Rex barks
```

### Super Keyword

The `super` keyword is used to access the parent class.

* `super()` calls the parent constructor.
* `super.method()` calls a parent method.

```js "super" "Call parent constructor" "Call parent method" theme={null}
#super-keyword.js
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  area() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  constructor(side) {
    super(side, side); // Call parent constructor
  }
  
  area() {
    return super.area(); // Call parent method
  }
}
```

### Static Inheritance

Static methods are also inherited by child classes.

```js "static" theme={null}
#static-inheritance.js
class Animal {
  static kingdom = 'Animalia';
  
  static describe() {
    return `This is an animal from ${this.kingdom}`;
  }
}

class Dog extends Animal {
  static species = 'Canis familiaris';
}

console.log(Dog.kingdom); // Animalia
console.log(Dog.describe()); // This is an animal from Animalia
console.log(Dog.species); // Canis familiaris
```

> \[!warning] Method Overriding
>
> 1. Child classes can override parent methods while still accessing the original via `super`.

> \[!warning] Class Expressions
> Classes can be defined as expressions, `named` or `anonymous`, and assigned to variables

### Prototype Chain

Classes in JavaScript still use the prototype chain under the hood.

```js "prototype" "true" del="false" "__proto__" theme={null}
#prototype-chain.js
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  bark() {
    return `${this.name} barks`;
  }
}

const dog = new Dog('Rex');

console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Dog.prototype === Animal.prototype); // false

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
```

## Prototypes

In JavaScript, every object has a hidden internal property called `[[Prototype]]` that references another object. This forms the `prototype chain`, which is the foundation of inheritance.

1. The prototype chain is `checked` when accessing properties or methods on an object.
2. If a property isn't found on the object, JavaScript looks up the prototype chain.
3. All JavaScript objects ultimately inherit from `Object.prototype`.

### \_\_proto\_\_ vs prototype

`__proto__` and `prototype` are different but related concepts.

* `prototype` is a property of constructor functions.
* `__proto__` is the actual object used in the lookup chain, deprecated tho, use `Object.getPrototypeOf()` instead.

```js "__proto__" "prototype" theme={null}
#proto-vs-prototype.js
function Animal(name) {
  this.name = name;
}

const dog = new Animal('Rex');

// dog.__proto__ points to Animal.prototype
console.log(dog.__proto__ === Animal.prototype); // true

// Animal.prototype has a constructor property pointing back
console.log(Animal.prototype.constructor === Animal); // true
```

### Dynamic Prototype Modification

You can `add` methods to a prototype at any time, and all `instances`, even existing ones, will have access to the new method.

```js "__proto__" "The dog's name is Rex" "The dog's name is Buddy" "This also works btw" theme={null}
#dynamic-prototype.js
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {}

const dog = new Dog('Rex');
const dog2 = new Dog('Buddy');

dog.__proto__.printName = function() {
  console.log(`The dog's name is ${this.name}`);
};

Animal.prototype.printName = function() {
  console.log(`The dog's name is ${this.name}`);
}; // This also works btw

dog.printName(); // The dog's name is Rex
dog2.printName(); // The dog's name is Buddy
```

## Object.create( )

`Object.create()` creates a new object with a specified prototype, allowing explicit control over inheritance without using classes or constructor functions.

```js "Object.create" "from counter prototype" "1" "2" "obj.count" "counter.count" theme={null}
#object-create.js
const counter = {
  count: 0,
  increment() {
    this.count++;
  }
}

const obj = Object.create(counter);
counter.increment();
console.log(obj.count); // 1, from counter prototype

obj.increment(); // count is incremented on obj, not counter

console.log(counter.count); // 1
console.log(obj.count); // 2 (shadowing counter's count)

/* from now on obj.count is independent of counter.count, 
they are separate properties */
```
