浏览器

JavaScript

围绕运行语义、异步行为与特性版本下限,为 Node 与浏览器场景调试与编写 JavaScript。

它能做什么

覆盖 Node 与浏览器双端的 JavaScript 实战指南,按可调试场景组织:TypeError、NaN、日期偏移、内存增长、正则卡死、fetch 行为等。涉及异步语义(微任务、调度顺序、AbortSignal)、隐式转换陷阱、闭包持有、排序与相等性怪事、UTF-16 与码点的区别、JSON 精度丢失、ES2023+ 特性版本下限。内置快速索引表将 promise 问题导向 async.md、正则问题导向 regex.md、DOM 问题导向 browser.md,并在给出建议前应用用户保存的偏好(运行时目标、Node 版本下限、模块系统、浏览器版本下限)。输出代码或评审结论前会经过输出门检查。不涉及 TypeScript 类型系统设计与框架内部机制。

什么时候用它

  • 排查 TypeError 或 'undefined is not a function'
  • 判断 toSorted、structuredClone 等 API 是否在目标 Node 版本中可用
  • 定位一直挂起不 resolve 的 Promise,或未处理的 rejection 导致 Node 崩溃
  • 确认日期为何偏移一天,或大整数 id 在 JSON 中为何丢精度

技能文档

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.

VariableTypeDefaultEffect
runtime_targetnode | browser | bothnodeSelects which platform file applies by default (node.md vs browser.md) and which floor gates advice
node_floornumber (Node major)from package.json engines, else 22Gates every recommendation against the feature-floor table in modern.md; flags any API above the floor
module_systemesm | cjs | dualesmSwitches module guidance and import/export examples in modern.md; dual activates the dual-package hazard checks
browser_floortext (e.g. "Safari 16+", "last 2 years")noneWhen 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

SituationGo to
TypeError/ReferenceError, NaN appearing, value undefined after await, works-in-dev-only, heisenbugdebug.md
Promise/await bug, rejection handling, cancellation, concurrency limits, racesasync.md
Throwing, catching, custom errors, cause chains, global error hooks, serializing errorserrors.md
== surprise, truthiness, implicit conversion, ?? vs ||coercion.md
Array/Object/Map/Set choice, copying, sorting, iteration trapscollections.md
ES2020+ syntax semantics, feature floors, modules (ESM/CJS), classes, generators/iteratorsmodern.md
Memory grows, listener/timer leaks, WeakMap/WeakRef, heap snapshotsmemory-leaks.md
Slow code, jank, benchmarks, GC pressure, workersperformance.md
Regex wrong matches, stateful lastIndex, catastrophic backtracking, Unicode flagsregex.md
JSON precision loss, Date/Map round-trips, reviver/replacer, canonicalizationjson.md
Env vars, process exit, signals, streams, Buffer, child processesnode.md
DOM events, storage, script loading, fetch response handlingbrowser.md
Anything else (numbers, dates, strings, this, timers)Sections below

Core Rules

  1. === always; the only defensible == is the idiom x == null — it matches both null and undefined in one check. Never expand it to two comparisons.
  2. 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.
  3. 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 unhandledrejection in browsers.
  4. Mutation is opt-in: default to toSorted/toReversed/with and spread; structuredClone only when depth is real. Runtime floors for all of these: modern.md.
  5. Durations from performance.now() (monotonic); wall-clock timestamps from Date.now(). Never subtract two Date.now() calls for benchmarks — NTP can step it backwards mid-measurement.
  6. 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.
  7. .length counts UTF-16 code units, not characters: "😀".length === 2. Slice user-visible text with Intl.Segmenter, never by index.
  8. Check the feature-floor table in modern.md before shipping ES2023+ APIs — one toSorted call 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 in JSON.parse — transport ids as strings; use BigInt only for arithmetic (JSON.stringify on BigInt throws).
  • (1.005).toFixed(2) === "1.00" — binary representation, not a rounding bug you can fix locally. Compute in integer cents; display through Intl.NumberFormat.
  • Pick the parser by intent: Number("12px") → NaN (whole-string strict), parseInt("12px") → 12 (prefix). Number("") and Number(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)).
  • -0 exists: 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 with Intl.DateTimeFormat.

Strings & Unicode

  • length, slice, charAt operate on UTF-16 units: "👨‍👩‍👧".length === 8. [...str] yields code points; user-perceived characters need Intl.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).
  • /g and /y regexes are stateful: lastIndex persists across calls, so re.test(s) twice on the same string can alternate true/false. Drop /g for single tests or reset re.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 or Object.create(null) for user-keyed storage.
  • Object.hasOwn(obj, k) over obj.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) detaches this — 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 setTimeout yields between runs.
  • Timer clamps: nested setTimeout beyond 5 levels → 4ms minimum; background tabs throttle to ≥1000ms (often far more). Clocks and animations must compute elapsed = Date.now() - start each tick — accumulating +interval drifts.
  • Node: a setTimeout delay above 2147483647 ms (~24.8 days, 32-bit signed) fires almost immediately. Long schedules: persist the target time and re-arm.
  • Node ordering: process.nextTick queue → promise microtasks → timers/macrotasks. setImmediate vs setTimeout(0): nondeterministic at top level, setImmediate always 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

TrapWhy it failsDo instead
forEach(async x => ...)forEach ignores returned promises: everything runs at once, "finishes" instantlyfor...of (sequential) or Promise.all(arr.map(f))
Array(3).fill({})one shared object, three referencesArray.from({length: 3}, () => ({}))
return fetchThing() inside trythe rejection settles after try exits — catch never sees itreturn await fetchThing()
Promise.race([op, timeout])the loser keeps running and holding sockets/memoryAbortSignal.timeout(ms) passed into the op
return/throw inside finallyoverrides the try's result and swallows its exceptionfinally is for cleanup only
throw "failed"no stack, instanceof Error false, breaks error middlewarenew Error("failed", {cause: err})
JSON.parse(JSON.stringify(x)) as deep clonedrops undefined/functions, Date → string, Map/Set → {}, throws on cyclesstructuredClone(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, "", falsex != null
setInterval around async worknext run starts while the previous still awaits — overlapping executionschained setTimeout re-armed after each completion
innerHTML with user textthe text executes as markup — XSStextContent, 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, not return-ed?
  • No API above the target floor (modern.md table vs node_floor / browser_floor)?
  • Money in integer minor units; no ≥16-digit id passing through JSON.parse as a number?
  • Every thrown value an Error instance, wrapped with cause where 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.md Classes).
  • 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.

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 language
  • nodejs — Node platform operations beyond the language: servers, tooling, deployment
  • react — framework work where these language rules get applied
  • regex — pattern crafting beyond JS-specific regex behavior

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/javascript.

相关技能

针对 Node.js 运行时、打包与线上问题,给出可落地的诊断依据与修复规则。

142 次安装5 星标

解读 TypeScript 类型错误,设计 API、tsconfig 与 .d.ts 的类型方案。

185 次安装6 星标

按语言层规则编写、调试、审查 PHP:严格类型、正确转义、合理配置 FPM 与 OPcache。

85 次安装4 星标

调试、编写和审查 Go 代码,覆盖 goroutine、错误处理、模块与标准库的实践指导。

82 次安装3 星标

按文档化的 Python 规则手册和固定检查清单排查、审阅与编写代码。

156 次安装6 星标

按规则构建、调试和审查 React 应用,覆盖组件、状态、表单、性能与 React 19 特性。

245 次安装3 星标

Iván 的更多技能

浏览全部技能

执行 Git 操作(提交、分支、合并、变基、冲突解决与恢复)时强制套用安全规则。

作者 Iván527 次安装31 星标

用可量化的层级、间距、字号、配色与版式规则,绘制并诊断视觉作品。

作者 Iván137 次安装5 星标

围绕 CSS 机制排查问题并编写组件样式表,而不是凭感觉试错。

作者 Iván97 次安装5 星标

以系统方式规划并执行自学:从出口测试倒推课程,加入间隔复习与刻意练习,产出可验证的迁移证据。

作者 Iván93 次安装3 星标

针对你的 Azure 订阅,做架构设计、故障排查、安全加固与成本优化

作者 Iván86 次安装2 星标

按配置的 JDK 版本诊断 Java 与 JVM 问题(从 NPE 到容器 OOM),给出可直接套用的代码与配置。

作者 Iván130 次安装9 星标