JavaScript
==========
.. raw:: html
Arrow functions
---------------
What are arrow functions?
Arrow functions are a shorter syntax for writing functions in JavaScript. They use ``=>`` instead of the ``function`` keyword.
.. code-block:: javascript
// Regular function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => {
return a + b;
};
// Even shorter (implicit return for single expression)
const add = (a, b) => a + b;
When do you use the short form?
If the function body is a single expression, you can omit the braces and ``return`` keyword:
.. code-block:: javascript
// Multiple statements - need braces and return
const processUser = (user) => {
console.log('Processing:', user.name);
return user.id;
};
// Single expression - braces optional
const getUserId = (user) => user.id;
// Single parameter - parentheses optional
const double = x => x * 2;
// No parameters - parentheses required
const getRandom = () => Math.random();
How do arrow functions work as callbacks?
You often see arrow functions assigned to properties or passed directly. The syntax can be confusing at first.
.. code-block:: javascript
// This is a property assignment
worker.onmessage = (event) => {
console.log('FFT complete:', event.data);
};
**Breaking it down:**
1. ``worker.onmessage`` is a property on the worker object
2. ``=`` assigns a function to that property
3. ``(event) => { ... }`` is the arrow function being assigned
4. When the worker sends a message, it **calls** this function with an ``event`` parameter
**Equivalent regular function syntax:**
.. code-block:: javascript
// Same thing with function keyword
worker.onmessage = function(event) {
console.log('FFT complete:', event.data);
};
// Or defined separately
function handleMessage(event) {
console.log('FFT complete:', event.data);
}
worker.onmessage = handleMessage;
All three versions do the same thing: store a function that gets called later when the worker sends a message.
What about ``this``?
Arrow functions don't have their own ``this`` - they inherit it from the surrounding code. Regular functions create their own ``this``, which can cause confusion in callbacks.
.. code-block:: javascript
function Counter() {
this.count = 0;
// Regular function - 'this' is undefined in the callback
setInterval(function() {
this.count++; // Error: 'this' is not the Counter
}, 1000);
// Arrow function - 'this' refers to Counter
setInterval(() => {
this.count++; // Works: 'this' is the Counter
}, 1000);
}
Use arrow functions in callbacks when you need to access the outer ``this``.
Asynchronous programming
------------------------
Why care about asynchronous programming?
JavaScript is single-threaded - it executes one task at a time. Without async programming, slow operations (network requests, file reads) would freeze the entire page. Async programming keeps the page responsive while waiting for operations to complete.
Callbacks
^^^^^^^^^
What is a callback?
A callback is a function passed to another function. The receiving function executes your callback when it finishes its work.
.. code-block:: javascript
function fetchUser(userId, callback) {
setTimeout(function() {
const user = { id: userId, name: 'Bob' };
callback(user); // Execute callback with result
}, 1000);
}
fetchUser(123, function(user) {
console.log('Got user:', user.name); // Prints after 1 second
});
Your callback function is stored and executed **later**, not immediately. The outer function returns right away, but your callback waits until the async operation completes.
Why do callbacks lead to "callback hell"?
When operations depend on each other, callbacks nest deeper with each step.
Simple example - getting data in sequence:
.. code-block:: javascript
// Step 1: Get user
fetchUser(123, function(user) {
console.log('Got user:', user.name);
// Step 2: Need user.id from step 1
fetchPosts(user.id, function(posts) {
console.log('Got posts:', posts.length);
// Step 3: Need posts[0].id from step 2
fetchComments(posts[0].id, function(comments) {
console.log('Got comments:', comments.length);
// Each callback nests inside the previous one
});
});
});
**Why nesting happens:** Each step needs data from the previous step. You can't call ``fetchPosts`` until you have ``user.id``, and you can't call ``fetchComments`` until you have ``posts[0].id``. The only way to access that data is inside the callback where it's available.
With more steps, this becomes unmanageable:
.. code-block:: javascript
fetchUser(123, function(user) {
fetchPosts(user.id, function(posts) {
fetchComments(posts[0].id, function(comments) {
fetchLikes(comments[0].id, function(likes) {
fetchReplies(likes[0].id, function(replies) {
console.log('Done!'); // 5 levels deep!
});
});
});
});
});
Code shifts rightward, becomes hard to read, and error handling is painful.
Promises
^^^^^^^^
What is a Promise?
A Promise is a built-in JavaScript object (not a library) that represents a future value. When you start an async operation, you get a Promise immediately - it's a placeholder that says "I don't have the result yet, but I will eventually."
A Promise has three states:
- **Pending**: Operation still running
- **Fulfilled**: Operation succeeded, result is available
- **Rejected**: Operation failed, error is available
How do you create a function that returns a Promise?
.. code-block:: javascript
function fetchUser(userId) {
return new Promise(function(resolve, reject) {
const user = { id: userId, name: 'Bob' };
resolve(user); // Success - call resolve with the result
// Or if error: reject(new Error('User not found'))
});
}
The Promise constructor takes a function with two parameters:
- ``resolve(value)`` - Call this when operation succeeds
- ``reject(error)`` - Call this when operation fails
Usage - instead of passing a callback, functions return a Promise:
.. code-block:: javascript
// Old callback style
fetchUser(123, function(user) {
console.log(user);
});
// Promise style
const promise = fetchUser(123); // Returns immediately with a Promise
promise.then(function(user) { // Attach callback to Promise
console.log(user);
});
The key difference: you get a Promise object you can pass around and attach callbacks to later.
How does ``.then()`` work?
Every Promise has a ``.then()`` method. You pass it a function, and ``.then()`` returns a **new Promise**.
.. code-block:: javascript
const promise1 = fetchUser(123);
const promise2 = promise1.then(function(user) {
console.log(user.name);
return 'done'; // This value wraps in a new Promise
});
promise2.then(function(value) {
console.log(value); // Prints: 'done'
});
**Key insight:** If you return another Promise from ``.then()``, the next ``.then()`` waits for that Promise to resolve:
.. code-block:: javascript
fetchUser(123)
.then(function(user) {
// Return a Promise - next .then() waits for it
return fetchPosts(user.id);
})
.then(function(posts) {
// Receives posts after fetchPosts resolves
console.log(posts);
});
How do Promises help with nesting?
Promises chain with ``.then()``, avoiding deep nesting:
.. code-block:: javascript
fetchUser(123)
.then(function(user) {
return fetchPosts(user.id); // Return Promise - next .then() waits
})
.then(function(posts) {
return fetchComments(posts[0].id); // Return Promise - next .then() waits
})
.then(function(comments) {
console.log('Comments:', comments.length);
})
.catch(function(error) {
console.error('Error:', error); // Handles errors from any step
});
Each ``.then()`` returns a new Promise, allowing the chain to continue. This flattens the pyramid into a vertical chain.
Async/await
^^^^^^^^^^^
Why use ``async``/``await``?
``async``/``await`` is syntax built on top of Promises. It makes Promise-based code read like normal sequential code, eliminating both nested callbacks and ``.then()`` chains.
How does it compare?
Fetching user → posts → comments on first post.
**Callbacks** (nested, hard to read):
.. code-block:: javascript
fetchUser(123, function(user) {
fetchPosts(user.id, function(posts) {
fetchComments(posts[0].id, function(comments) {
console.log('Comments:', comments.length);
});
});
});
**Promises** (better, but still chaining):
.. code-block:: javascript
fetchUser(123)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log('Comments:', comments.length))
.catch(error => console.error('Error:', error));
This looks shorter, but chaining has problems:
- Hard to debug - can't set breakpoints in arrow functions easily
- Hard to use intermediate values - if you need both ``user`` and ``posts`` later, you must restructure the chain
- Not actually sequential-looking - your eye jumps from ``.then()`` to ``.then()``
**async/await** (reads top-to-bottom):
.. code-block:: javascript
async function getUserData() {
try {
const user = await fetchUser(123);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
console.log('Comments:', comments.length);
} catch (error) {
console.error('Error:', error);
}
}
Each ``await`` pauses execution until the Promise resolves, then continues with the result. No nesting, no chaining.
How do you run operations in parallel?
When operations are independent, use ``Promise.all`` to run them simultaneously:
.. code-block:: javascript
// Sequential (slow): 2s + 2s + 2s = 6 seconds
async function loadDataSlow() {
const users = await fetch('/users').then(r => r.json());
const posts = await fetch('/posts').then(r => r.json());
const comments = await fetch('/comments').then(r => r.json());
}
// Parallel (fast): all start at once = 2 seconds
async function loadDataFast() {
const [users, posts, comments] = await Promise.all([
fetch('/users').then(r => r.json()),
fetch('/posts').then(r => r.json()),
fetch('/comments').then(r => r.json())
]);
}
CPU-intensive operations
^^^^^^^^^^^^^^^^^^^^^^^^
Does ``async``/``await`` help with FFT or image processing?
**No.** ``async``/``await`` only helps with I/O operations (network, files) where you're waiting for external resources. CPU computations like FFT still block the main thread.
What are Web Workers?
Web Workers run JavaScript in a separate thread, parallel to your main page. This keeps heavy computations from freezing the UI.
**Key points:**
- Main thread and worker thread run simultaneously (true parallelism)
- Workers cannot access the DOM (no ``document.getElementById``, etc.)
- Communication happens via ``postMessage`` and ``onmessage``
- Each worker is a separate JavaScript file
Example - offloading FFT computation:
.. code-block:: javascript
// Main thread
const worker = new Worker('fft-worker.js');
worker.postMessage({ imageData: data });
worker.onmessage = (event) => {
console.log('FFT complete:', event.data);
};
.. code-block:: javascript
// fft-worker.js (runs in separate thread)
self.onmessage = (event) => {
const result = computeFFT(event.data.imageData);
self.postMessage(result); // Send result back to main thread
};
**When to use Web Workers:**
- ``async``/``await``: I/O operations (fetch, file read) waiting for external resources
- **Web Workers**: CPU-intensive code (FFT, matrix operations, image processing) that blocks execution
In browser JavaScript, you'd handle webhooks by having your backend receive them, then use WebSockets or polling to notify the frontend:
.. code-block:: javascript
// Frontend connects to your backend via WebSocket
const socket = new WebSocket('ws://yourserver.com');
socket.onmessage = (event) => {
console.log('Received webhook notification:', event.data);
};
// Your backend receives webhook from GitHub, then forwards to frontend via WebSocket
Common syntax
-------------
Destructuring
Extract values from objects or arrays:
.. code-block:: javascript
const { name, age } = props;
const [first, second] = array;
Optional chaining (``?.``)
Safely access properties when object might be ``null`` or ``undefined``:
.. code-block:: javascript
arr?.length // Returns undefined if arr is null/undefined
user?.address?.city
Nullish coalescing (``??``)
Fallback for ``null`` or ``undefined`` (but not ``0`` or ``''``):
.. code-block:: javascript
const max = user.maxPoints ?? 3;
Logical NOT (``!``)
Converts to boolean and inverts:
.. code-block:: javascript
!true // false
!0 // true (0 is falsy)
!"hello" // false (non-empty strings are truthy)
Ternary operator
Conditional expression:
.. code-block:: javascript
const result = condition ? valueIfTrue : valueIfFalse;
{isLoggedIn ? : }
Default parameters
Provide default values for function parameters:
.. code-block:: javascript
const greet = (name = "stranger") => `Hi ${name}`;
Shorthand object properties
When variable name matches property name:
.. code-block:: javascript
const user = { name, age }; // Same as { name: name, age: age }
Spread operator (``...``)
Clone or merge arrays/objects:
.. code-block:: javascript
const newArr = [...oldArr, newItem];
const newObj = { ...oldObj, updated: true };
Template literals
Multi-line strings and embedded expressions:
.. code-block:: javascript
const name = "Alice";
const age = 5;
const message = `Hello ${name}, you are ${age} years old`;
// Multi-line strings (preserves line breaks)
const html = `
`;
Array methods (``map``, ``filter``, ``reduce``)
Transform arrays without loops:
.. code-block:: javascript
const numbers = [1, 2, 3, 4, 5];
// map: transform each element
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter: keep elements that match condition
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
// reduce: combine all elements into single value
const sum = numbers.reduce((total, n) => total + n, 0); // 15
Object destructuring with renaming
Extract and rename in one step:
.. code-block:: javascript
const user = { id: 1, name: "Bob", email: "bob@example.com" };
// Rename 'name' to 'username' during destructuring
const { name: username, email } = user;
console.log(username); // "Bob"
Rest parameters (``...``)
Collect remaining arguments into array:
.. code-block:: javascript
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4); // 10
// Also works in destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first: 1, second: 2, rest: [3, 4, 5]
Object and array methods
.. code-block:: javascript
// Object.keys, Object.values, Object.entries
const user = { name: "Alice", age: 5 };
Object.keys(user); // ["name", "age"]
Object.values(user); // ["Alice", 5]
Object.entries(user); // [["name", "Alice"], ["age", 5]]
// find, some, every
const users = [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}];
users.find(u => u.id === 2); // {id: 2, name: "Bob"}
users.some(u => u.id === 2); // true (at least one matches)
users.every(u => u.id > 0); // true (all match)
Optional chaining with function calls
Safely call methods that might not exist:
.. code-block:: javascript
user.getProfile?.(); // Only calls if getProfile exists
data?.items?.[0]?.name // Safely access nested properties and array elements
Short-circuit evaluation (``&&`` and ``||``)
Conditional execution without if statements:
.. code-block:: javascript
// && executes right side only if left is truthy
isLoggedIn && redirectToDashboard(); // Only redirects if isLoggedIn is true
user && user.getName(); // Only calls if user exists
// || returns first truthy value (default fallback)
const name = user.name || "Guest"; // Use "Guest" if user.name is falsy
const config = loadConfig() || getDefaultConfig();
Array/Object destructuring with defaults
Provide fallback values during destructuring:
.. code-block:: javascript
const { name = "Unknown", age = 0 } = user; // Use defaults if missing
const [first = 1, second = 2] = arr; // Array destructuring with defaults
Dynamic property names
Compute property names using expressions:
.. code-block:: javascript
const key = "userName";
const user = {
[key]: "Alice", // Property name computed at runtime
[`${key}Age`]: 5
};
// Result: { userName: "Alice", userNameAge: 5 }
Method chaining
Chain operations on arrays:
.. code-block:: javascript
const result = users
.filter(u => u.age > 18)
.map(u => u.name)
.sort()
.slice(0, 10); // Get first 10 sorted names of adults
String methods
.. code-block:: javascript
"hello".toUpperCase(); // "HELLO"
" hello ".trim(); // "hello"
"hello".startsWith("he"); // true
"hello".includes("ll"); // true
"a,b,c".split(","); // ["a", "b", "c"]
["a", "b", "c"].join(", "); // "a, b, c"
"hello".repeat(3); // "hellohellohello"
``try``/``catch`` for error handling
Handle errors without crashing:
.. code-block:: javascript
try {
const data = JSON.parse(userInput);
processData(data);
} catch (error) {
console.error("Invalid JSON:", error.message);
}
// With async/await
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch:", error);
return null;
}
}
``setTimeout`` and ``setInterval``
Execute code after delay or repeatedly.
**How they work:**
- ``setTimeout(function, milliseconds)`` - Executes function once after delay
- ``setInterval(function, milliseconds)`` - Executes function repeatedly every X milliseconds
- Both return an ID that you can use to cancel them
- Time is in milliseconds (1000ms = 1 second)
.. code-block:: javascript
// setTimeout takes two parameters:
// 1. A function (arrow function that logs)
// 2. Delay in milliseconds (2000 = 2 seconds)
setTimeout(() => {
console.log("2 seconds passed");
}, 2000);
// With named function
function greet() {
console.log("Hello!");
}
setTimeout(greet, 1000); // Parameter 1: greet function, Parameter 2: 1000ms
// setInterval takes the same two parameters
// Parameter 1: function to execute, Parameter 2: 1000ms (1 second)
const intervalId = setInterval(() => {
console.log("Tick");
}, 1000);
// Stop the interval using the ID
clearInterval(intervalId);
// Cancel a timeout before it executes
const timeoutId = setTimeout(() => {
console.log("This might not run");
}, 5000);
clearTimeout(timeoutId); // Cancels it
**Common pattern - stop interval after condition:**
.. code-block:: javascript
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Count: ${count}`);
if (count >= 5) {
clearInterval(intervalId); // Stop after 5 executions
console.log("Done!");
}
}, 1000); // Runs every second, stops after 5 times
JSON operations
Parse and stringify JSON data:
.. code-block:: javascript
const obj = { name: "Alice", age: 5 };
const json = JSON.stringify(obj); // '{"name":"Alice","age":5}'
const parsed = JSON.parse(json); // { name: "Alice", age: 5 }
TypeScript
----------
TypeScript adds type checking to JavaScript. Types are checked at compile time, then removed when converted to JavaScript.
Basic types
.. code-block:: typescript
const name: string = "Bob";
const age: number = 5;
const isActive: boolean = true;
const items: number[] = [1, 2, 3];
const data: string[] = ["a", "b", "c"];
Type aliases
Define custom types:
.. code-block:: typescript
type Point = { x: number; y: number };
type User = { id: number; name: string; email?: string }; // ? means optional
const point: Point = { x: 10, y: 20 };
const user: User = { id: 1, name: "Alice" }; // email is optional
Function types
Specify parameter and return types:
.. code-block:: typescript
function add(a: number, b: number): number {
return a + b;
}
const multiply = (a: number, b: number): number => a * b;
// Function that returns nothing
const logMessage = (msg: string): void => {
console.log(msg);
};
Union types
Variable can be one of several types:
.. code-block:: typescript
type Status = "pending" | "success" | "error";
let status: Status = "pending"; // Can only be one of these three strings
type Result = string | number;
const value: Result = 42; // Can be string OR number
Interfaces
Similar to type aliases, commonly used for object shapes:
.. code-block:: typescript
interface Props {
title: string;
count: number;
onUpdate?: (newCount: number) => void; // Optional callback function
}
function MyComponent(props: Props) {
return {props.title}: {props.count}
;
}
Generics
Write flexible, reusable code with type parameters.
**What is a generic?** A generic is like a placeholder for a type. The function or hook doesn't know what specific type it will work with until you use it.
**React's `useState` example:**
.. code-block:: typescript
// useState is defined roughly like this:
// function useState(initialValue: T): [T, (newValue: T) => void]
// You tell useState what type to expect using
const [items, setItems] = useState([]);
// items: string[]
// setItems: (newValue: string[]) => void
const [count, setCount] = useState(0);
// count: number
// setCount: (newValue: number) => void
const [user, setUser] = useState(null);
// user: User | null
// setUser: (newValue: User | null) => void
**Breaking it down:**
- ``useState`` tells TypeScript: "I'm storing an array of strings"
- TypeScript then knows ``items`` is ``string[]`` and ``setItems`` only accepts ``string[]``
- ``useState`` means storing a number, so ``count`` is ``number`` and ``setItems`` only accepts ``number``
- ``useState`` means storing either a User object or null
**Writing a generic function:**
.. code-block:: typescript
// is the generic type parameter - a placeholder
function getFirst(array: T[]): T | undefined {
return array[0];
}
// When you call it, specify what T should be
const firstNumber = getFirst([1, 2, 3]); // T becomes number
const firstString = getFirst(["a", "b"]); // T becomes string
Without generics, you'd need separate functions for each type (``getFirstNumber``, ``getFirstString``, etc.). Generics let you write one function that works with any type.
React
-----
Framework design decisions
^^^^^^^^^^^^^^^^^^^^^^^^^^
Real-time manipulation of HTML/CSS in JavaScript is tedious. For interactive front-end applications, React simplifies development with the following design decisions:
- Modifying the DOM during rendering is not allowed. The DOM can only be modified after the component is declaratively rendered. React **automatically re-renders** components when their **props** or **state** change.
- React wants functions that produce UI components to be pure. Given the same inputs, outputs should always be identical, like a calculator, as long as the function contains only declarative operations such as multiply, add, and divide.
- Use ``useRef`` in two ways: (1) to access the DOM after rendering (e.g., focusing an input box or controlling playback), or (2) to store a mutable value that persists across renders but does not trigger a re-render when changed. Using ``useRef`` does NOT cause the component to re-render.
Naming conventions
^^^^^^^^^^^^^^^^^^
- Use ``UpperCase`` for component function names (e.g., ``MyButton``). Unlike Python, dashes are not allowed in JavaScript identifiers.
Tips on ``useEffect``
^^^^^^^^^^^^^^^^^^^^^
- Use ``useEffect`` to create side effects after a component is rendered. Use event handlers (e.g., ``onClick``) for side effects based on user actions. ``useEffect`` is useful for actions like loading user profiles or syncing with a server, even if the user hasn't interacted with the component.
- If you pass an empty array ``[]`` as the dependency list, the effect runs only once after the initial render (componentDidMount).
- If you include values in the array (e.g., ``[id]``), the effect re-runs whenever those values change.
How to avoid expensive recalculations on every render
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Use ``const myVariable = useMemo(() => { ... }, [values])`` to store the result of a computation. The value is recalculated only when ``values`` change.
- ``useCallback`` is similar to ``useMemo`` but for functions. It store the function instance, preventing unnecessary re-creations on every render.
- Other hooks like ``useContext``, ``useReducer``, and ``useLayoutEffect`` are also available for advanced use cases but I'm not familiar with them atm.
Use good syntax
^^^^^^^^^^^^^^^
.. code-block::
const limit = Number.isFinite(maxPoints) && maxPoints > 0 ? maxPoints : 3;
``isFinite`` checks whether ``maxPoints`` is a valid number (not ``null``, ``Infinity``, etc.). Then, it checks whether it's greater than 0. If both are true, use ``maxPoints``; otherwise, default to 3.
.. code-block::
return next.length <= limit ? next : next.slice(next.length - limit);
Check whether ``next.length`` is less than or equal to ``limit``. If yes, return ``next``; otherwise, return the last ``limit`` elements. For example, if ``next`` is ``[{a}, {b}, {c}, {d}]`` and the limit is 3, then ``next.slice(next.length - limit)`` returns ``[{b}, {c}, {d}]``.
Host via GitHub Pages
^^^^^^^^^^^^^^^^^^^^^
#. Create a new React app using a Vite template:
.. code-block:: bash
npm create vite@latest my-app -- --template react
Refer to https://react.dev/learn/build-a-react-app-from-scratch for more.
#. Install ``gh-pages`` as a development dependency:
.. code-block:: bash
npm install --save-dev gh-pages
#. Update ``package.json``: add a ``homepage`` field and ``predeploy``/``deploy`` scripts.
.. code-block:: json
{
"name": "my-app",
"homepage": "https://.github.io//",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview",
"predeploy": "npm run build",
"deploy": "gh-pages -d dist"
},
"dependencies": {
}
}
#. Add ``base:`` to ``vite.config``:
.. code-block:: javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
base: '//'
})
#. Run ``npm run deploy``.
This triggers GitHub Actions and publishes the site to the gh-pages branch. Wait 1 to 2 minutes for the site to appear. Check your repository :guilabel:`Settings-Pages` to confirm the deployment status. The website will be available on ``https://.github.io//``.