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 = `

${name}

Age: ${age}

`; 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//``.