原始内容
name: javascript slug: javascript version: 1.0.6 changelog: "Full coverage pass: deeper guides, situation-named files, and per-user configuration" description: >- Writes, debugs, and reviews JavaScript: async and the event loop, coercion, closures, dates, Unicode, regex, and modern ES2023+ APIs. Use when JS throws TypeError or "undefined is not a function", a promise never settles or a rejection goes unhandled, NaN or [object Object] appears, dates shift by a day, sorting or equality misbehaves, memory grows, a regex hangs, JSON loses precision, a Node process won't exit or ignores signals, or fetch doesn't reject on a 404; also when choosing data structures, handling errors, profiling slow code, or checking whether a feature is safe for a target Node or browser version. Covers Node and browser runtime edges. Not for TypeScript type-system design or framework internals. homepage: https://clawic.com/skills/javascript metadata: clawdbot: emoji: 🟨 displayName: JavaScript configPaths: - ~/Clawic/data/javascript/
User preferences and memory live in ~/Clawic/data/javascript/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/javascript/ or ~/clawic/javascript/), move it to ~/Clawic/data/javascript/.
Configuration
User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/javascript/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| runtime_target | node | browser | both | node | Selects which platform file applies by default (node.md vs browser.md) and which floor gates advice |
| node_floor | number (Node major) | from package.json engines, else 22 |
Gates every recommendation against the feature-floor table in modern.md; flags any API above the floor |
| module_system | esm | cjs | dual | esm | Switches module guidance and import/export examples in modern.md; dual activates the dual-package hazard checks |
| browser_floor | text (e.g. "Safari 16+", "last 2 years") | none | When set, gates syntax and API advice for browser-targeted code the way node_floor gates Node |
Preference areas to record as the user reveals them:
- tooling — package manager, bundler, test runner, lint/format stack — affects which commands and config examples are offered
- conventions — style (semicolons, functional vs imperative iteration), error-handling shape (exceptions vs result values) — affects generated code and review verdicts
- platform — Deno/Bun/edge runtimes, TypeScript presence, monorepo layout — affects which floors and module advice apply
- safety posture — how proactively to flag legacy patterns (
var,==, callback APIs) in review vs only on request
When To Use
- Debugging JavaScript: wrong values, NaN, TypeErrors, ordering surprises, async bugs, timezone shifts
- Writing or reviewing JS (Node or browser) for correctness and modern idioms
- Choosing data structures (Object/Map/Array/Set), copy semantics, iteration strategy, or error-handling shape
- Deciding whether an ES2020+ feature is safe for the target runtime
- Diagnosing memory growth, leaks, slow code, or an unresponsive event loop
- Not for TypeScript type-system design or framework internals — this is the core language
Quick Reference
| Situation | Go to |
|---|---|
| TypeError/ReferenceError, NaN appearing, value undefined after await, works-in-dev-only, heisenbug | debug.md |
| Promise/await bug, rejection handling, cancellation, concurrency limits, races | async.md |
| Throwing, catching, custom errors, cause chains, global error hooks, serializing errors | errors.md |
== surprise, truthiness, implicit conversion, ?? vs || |
coercion.md |
| Array/Object/Map/Set choice, copying, sorting, iteration traps | collections.md |
| ES2020+ syntax semantics, feature floors, modules (ESM/CJS), classes, generators/iterators | modern.md |
| Memory grows, listener/timer leaks, WeakMap/WeakRef, heap snapshots | memory-leaks.md |
| Slow code, jank, benchmarks, GC pressure, workers | performance.md |
Regex wrong matches, stateful lastIndex, catastrophic backtracking, Unicode flags |
regex.md |
| JSON precision loss, Date/Map round-trips, reviver/replacer, canonicalization | json.md |
| Env vars, process exit, signals, streams, Buffer, child processes | node.md |
| DOM events, storage, script loading, fetch response handling | browser.md |
Anything else (numbers, dates, strings, this, timers) |
Sections below |
Core Rules
===always; the only defensible==is the idiomx == null— it matches both null and undefined in one check. Never expand it to two comparisons.- Float equality is a band, not
===:Math.abs(a - b) <= Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b)). Worked:0.1 + 0.2 - 0.3≈ 5.6e-17, inside the band (EPSILON ≈ 2.2e-16). Money never touches floats: integer minor units. - Every promise gets a handler attached synchronously — a rejection with no handler when the microtask queue drains crashes Node (default since Node >=15) and fires
unhandledrejectionin browsers. - Mutation is opt-in: default to
toSorted/toReversed/withand spread;structuredCloneonly when depth is real. Runtime floors for all of these:modern.md. - Durations from
performance.now()(monotonic); wall-clock timestamps fromDate.now(). Never subtract twoDate.now()calls for benchmarks — NTP can step it backwards mid-measurement. - Sequential vs parallel is a decision you write down:
for...of+ await = sequential;Promise.all(arr.map(f))= parallel, fail-fast, and it does NOT cancel the losers. .lengthcounts UTF-16 code units, not characters:"😀".length === 2. Slice user-visible text withIntl.Segmenter, never by index.- Check the feature-floor table in
modern.mdbefore shipping ES2023+ APIs — onetoSortedcall breaks Node 18 at runtime, not at build time.
Numbers & Money
Number.MAX_SAFE_INTEGER= 2^53 − 1 = 9007199254740991 (16 digits). Integer ids with ≥16 digits can silently round inJSON.parse— transport ids as strings; use BigInt only for arithmetic (JSON.stringifyon BigInt throws).(1.005).toFixed(2) === "1.00"— binary representation, not a rounding bug you can fix locally. Compute in integer cents; display throughIntl.NumberFormat.- Pick the parser by intent:
Number("12px")→ NaN (whole-string strict),parseInt("12px")→ 12 (prefix).Number("")andNumber(null)→ 0;Number(undefined)→ NaN. ["10","10","10"].map(parseInt)→[10, NaN, 2]: map passes the index as radix. Always wrap:map(s => parseInt(s, 10)).-0exists:Object.is(0, -0)is false,1 / -0 === -Infinity. It appears when rounding negatives toward zero and survives into keys and comparisons.
Dates & Time
- Months are 0-indexed:
new Date(2025, 0, 31)is Jan 31. - Parsing split: date-only ISO (
"2024-01-01") → UTC midnight; date-time without offset ("2024-01-01T00:00") → local time. Same input day can render one day off in negative-offset zones. Any non-ISO string is implementation-defined — never parse it. - Month math rolls over:
d = new Date(2025, 0, 31); d.setMonth(1)→ March 3 (Feb 31 normalizes). Pin the day to 1 before month arithmetic, then restore a clamped day. getTimezoneOffset()is UTC minus local: UTC+2 reports -120. Sign errors here produce double-offset bugs that cancel out in your own timezone and explode in others.- Store epoch ms (
Date.now()) plus IANA zone name; format only at the edge withIntl.DateTimeFormat.
Strings & Unicode
length,slice,charAtoperate on UTF-16 units:"👨👩👧".length === 8.[...str]yields code points; user-perceived characters needIntl.Segmenter. Index-based slicing can cut a surrogate pair → U+FFFD garbage.- Normalize before comparing user input: composed
"é"!=="e"+ combining accent even when rendered identically —str.normalize("NFC")both sides. - Human sorting:
(a, b) => a.localeCompare(b, undefined, {numeric: true})→["file2", "file10"], accents ordered correctly. Bare<compares code units ("Z" < "a"is true). /gand/yregexes are stateful:lastIndexpersists across calls, sore.test(s)twice on the same string can alternate true/false. Drop/gfor single tests or resetre.lastIndex = 0(depth:regex.md).
Objects, this & Closures
- Key order is spec'd: integer-like keys ascending FIRST, then strings in insertion order.
Object.keys({b:1, 2:2, a:3, 1:4})→["1","2","b","a"]. Never encode order in numeric-string keys — use Map or an array. - User-controlled keys on plain objects are an injection surface:
obj[userKey]with"__proto__"pollutes the prototype. Use Map orObject.create(null)for user-keyed storage. Object.hasOwn(obj, k)overobj.hasOwnProperty(k): works on null-prototype objects and can't be shadowed.{...obj}invokes getters (snapshots values);Object.assign(target, src)fires setters on target. Same shallow result, different side effects.this: arrow = lexical (use in callbacks); regular = call-site (methods, event handlers that want the element).setTimeout(obj.method)detachesthis— wrap in an arrow.- Closure retention is per-scope in practice: one small long-lived closure keeps alive every object referenced by ANY sibling closure of that scope. Null out large locals before returning long-lived callbacks.
Timers & the Event Loop
- Microtasks (promise callbacks) drain completely before the next task: a self-scheduling microtask loop starves rendering and I/O forever; self-scheduling
setTimeoutyields between runs. - Timer clamps: nested
setTimeoutbeyond 5 levels → 4ms minimum; background tabs throttle to ≥1000ms (often far more). Clocks and animations must computeelapsed = Date.now() - starteach tick — accumulating+intervaldrifts. - Node: a
setTimeoutdelay above 2147483647 ms (~24.8 days, 32-bit signed) fires almost immediately. Long schedules: persist the target time and re-arm. - Node ordering:
process.nextTickqueue → promise microtasks → timers/macrotasks.setImmediatevssetTimeout(0): nondeterministic at top level,setImmediatealways first inside I/O callbacks. - Sync work over ~50ms is a long task (the input-jank threshold); a 60Hz frame budget is 16.7ms. Chunk batch work with an awaited yield (
await new Promise(r => setTimeout(r))) between slices.
Traps
| Trap | Why it fails | Do instead |
|---|---|---|
forEach(async x => ...) |
forEach ignores returned promises: everything runs at once, "finishes" instantly | for...of (sequential) or Promise.all(arr.map(f)) |
Array(3).fill({}) |
one shared object, three references | Array.from({length: 3}, () => ({})) |
return fetchThing() inside try |
the rejection settles after try exits — catch never sees it | return await fetchThing() |
Promise.race([op, timeout]) |
the loser keeps running and holding sockets/memory | AbortSignal.timeout(ms) passed into the op |
return/throw inside finally |
overrides the try's result and swallows its exception | finally is for cleanup only |
throw "failed" |
no stack, instanceof Error false, breaks error middleware |
new Error("failed", {cause: err}) |
JSON.parse(JSON.stringify(x)) as deep clone |
drops undefined/functions, Date → string, Map/Set → {}, throws on cycles |
structuredClone(x) |
obj.fn?.() when fn is a number |
?. guards null/undefined only, not "not callable" |
typeof obj.fn === "function" && obj.fn() |
if (x) for "is x set" |
silently drops 0, "", false |
x != null |
setInterval around async work |
next run starts while the previous still awaits — overlapping executions | chained setTimeout re-armed after each completion |
innerHTML with user text |
the text executes as markup — XSS | textContent, or one sanitizer at the render boundary |
Output Gates
Before emitting JS code or a review verdict, verify:
- Every promise created inside try is
return await-ed, notreturn-ed? - No API above the target floor (
modern.mdtable vsnode_floor/browser_floor)? - Money in integer minor units; no ≥16-digit id passing through
JSON.parseas a number? - Every thrown value an Error instance, wrapped with
causewhere context was added? - Each mutating call (
sort,reverse,splice,Object.assign) intentional, not incidental? - No plain object indexed by user-controlled keys?
- Concurrency written down: each await either deliberately sequential or batched with a bounded pool?
Where Experts Disagree
- Classes vs closures/factories. Classes when many instances share methods (prototype = one function object) or brand checks matter (
#x in obj); factories for one-off capability objects and simple DI. Field-initializer arrow functions are the worst of both (→modern.mdClasses). - Exceptions vs result values. Throw for the unexpected: broken invariants, failed I/O you cannot proceed without. Return values for expected outcomes: validation, not-found, cancellation. The test: if the immediate caller always try/catches, it wasn't exceptional — return it.
- Chained array methods vs loops. Chains are the readability default; a loop wins when profiling shows intermediate-array cost or the logic needs early exit beyond
some/every/find. Switching styles without a measurement is churn, not optimization.
Related Skills
More Clawic skills, get them at https://clawic.com/skills/javascript (install if the user confirms):
typescript— the type system layered on top of this languagenodejs— Node platform operations beyond the language: servers, tooling, deploymentreact— framework work where these language rules get appliedregex— pattern crafting beyond JS-specific regex behavior
Feedback
- If useful, star it: https://clawic.com/skills/javascript
- Latest version: https://clawic.com/skills/javascript
Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/javascript.