JavaScript Programming
Use when asked to write, explain, or debug general-purpose JavaScript code — language fundamentals (scoping, closures, `this`, prototypes/classes), async code with Promises/async-await, modules (ESM vs CommonJS), or common runtime pitfalls — independent of any testing framework or UI library.
Covers the JavaScript language itself (ECMAScript), for Node.js or the browser. A browser-automation skill (Playwright/Selenium) or a UI-framework skill (React, Vue, …) builds on top of this, not instead of it.
Scoping and closures
let count = 0;
function makeCounter() {
let n = 0; // captured by the closure below
return () => ++n;
}
const counter = makeCounter();
counter(); // 1
counter(); // 2 — n persists between calls, private to this counter
let/constare block-scoped;varis function-scoped and hoists with anundefinedinitial value — preferlet/constalways,varessentially never in new code.- A closure is a function bundled with the lexical scope it was created in — it keeps that scope's variables alive even after the outer function returns. This is the mechanism behind private state, memoization, and event handler factories.
this and function types
const obj = {
name: 'a',
regular() { return this.name; }, // `this` = obj, if called as obj.regular()
arrow: () => this?.name, // `this` = lexical (outer) scope, NOT obj
};
button.addEventListener('click', function () { console.log(this); }); // `this` = button
button.addEventListener('click', () => console.log(this)); // `this` = enclosing scope
Arrow functions do not have their own this — they inherit it from where they're defined, not how they're called. Regular functions get this from the call site (obj.method(), .call/.apply/.bind, or undefined/ global in strict/non-strict mode when called bare). Picking the wrong one is the single most common source of "why is this undefined" bugs.
Async: Promises and async/await
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error('load failed', err);
throw err;
}
}
// Run independent async work concurrently, not sequentially:
const [a, b] = await Promise.all([loadUser(1), loadUser(2)]);
async functionalways returns a Promise;awaitonly pauses within that function, it does not block the runtime.awaiting calls one after another when they don't depend on each other serializes work that could run concurrently — usePromise.all(fail-fast) orPromise.allSettled(collect all outcomes) instead.- An unhandled rejection (a
.catch-less Promise, or athrowinside anasyncfunction nobodyawaits/catches) crashes a Node process by default in modern versions — always terminate a promise chain with.catchor wrap theawaitintry/catch.
Classes and prototypes
class Animal {
#sound; // private field
constructor(name, sound) { this.name = name; this.#sound = sound; }
speak() { return `${this.name} says ${this.#sound}`; }
}
class Dog extends Animal {
constructor(name) { super(name, 'Woof'); }
}
class is sugar over JavaScript's prototype chain — dog instanceof Animal works because Dog.prototype's prototype is Animal.prototype. #field is a genuinely private instance field (not accessible or even enumerable from outside), distinct from a _field convention which is just a naming hint.
Modules: ESM vs CommonJS
// ESM (import/export) — the standard, used in browsers and modern Node ("type": "module")
export function add(a, b) { return a + b; }
import { add } from './math.js';
// CommonJS (require/module.exports) — Node's original module system
function add(a, b) { return a + b; }
module.exports = { add };
const { add } = require('./math.js');
ESM imports are statically analyzable (enables tree-shaking) and hoisted; CommonJS require is a runtime function call. Mixing them in one project without a bundler's interop layer is a common source of "not a function" or "unexpected token export" errors — check package.json's "type" field and file extensions (.mjs/.cjs) when debugging this.
Common pitfalls
==vs===.==coerces types before comparing ('' == 0istrue); default to===/!==unless the coercion is deliberate and documented.- Mutating an array/object you don't own —
array.sort(),.reverse(),.splice()mutate in place; prefer.toSorted()/.toReversed()/spread copies ([...arr]) when the caller doesn't expect their argument changed. NaN !== NaN— useNumber.isNaN(x), neverx === NaN.- Floating-point arithmetic —
0.1 + 0.2 !== 0.3; compare with an epsilon or use integer-cents-style representations for money. - Forgetting
awaiton a Promise-returning call — the code moves on with a pending Promise object instead of the resolved value, and errors surface far from their real cause.
Learn more
- MDN: JavaScript — language and standard-library reference.
- MDN: Closures, MDN:
this - MDN: Using Promises
- Node.js docs: Modules (CommonJS) and ECMAScript modules
- ECMA-262 (the language spec)