Skills on AI 459 skills

Active theme: Light

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/const are block-scoped; var is function-scoped and hoists with an undefined initial value — prefer let/const always, var essentially 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 function always returns a Promise; await only 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 — use Promise.all (fail-fast) or Promise.allSettled (collect all outcomes) instead.
  • An unhandled rejection (a .catch-less Promise, or a throw inside an async function nobody awaits/catches) crashes a Node process by default in modern versions — always terminate a promise chain with .catch or wrap the await in try/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 ('' == 0 is true); default to ===/!== unless the coercion is deliberate and documented.
  • Mutating an array/object you don't ownarray.sort(), .reverse(), .splice() mutate in place; prefer .toSorted()/.toReversed()/spread copies ([...arr]) when the caller doesn't expect their argument changed.
  • NaN !== NaN — use Number.isNaN(x), never x === NaN.
  • Floating-point arithmetic0.1 + 0.2 !== 0.3; compare with an epsilon or use integer-cents-style representations for money.
  • Forgetting await on 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

View javascript-programming/SKILL.md on GitHub