每个入门级代码库中都能看到的 10 个 JavaScript 错误

我有一支技术全面、经验丰富的小型团队,专注高效交付中等规模外包项目,有需要外包项目的可以联系我
每次克隆一个“看起来干净”的 starter 仓库,我都要浪费半天在同一批小陷阱上:版本漂移、脆弱的 fetch、混乱的模块、形同虚设的配置。 一台机器能跑,换台机器就炸。熟悉吗?
下面把我每次首日就会修的 10 个典型错误摊开讲透, 目的只有一个——让第一次运行,安静得像没事发生。

  1. 不固定 Node 版本,团队环境必漂移
    今天能过、明天黄;你用的包管家不同、Node 小版本换个警告、原生扩展重编译,谁先崩谁先赢。 我现在一律钉死运行时,所有人跑同一套位。

.nvmrc 或 .node-version

18.20.3
// package.json 片段
{
“engines”: { “node”: “>=18.20 <19” }
}
在 CI 里强制版本,版本不对就打印清晰提示并 fail。 从此“works on my machine”回复显著减少,安装可复现。

  1. 别再随手混用 ESM 和 CommonJS
    今天 require,明天 import,模块系统不是心情切换键。模块类型是包级或文件级,请选一个阵营站稳。
    // 选 ESM
    { “type”: “module” }
    // src/app.js
    import express from “express”; // ESM
    或者:
    // 选 CJS
    { “type”: “commonjs” }
    const express = require(“express”);
    新代码优先 ESM,别用无扩展导入;测试与源码保持一致。 这样路径解析不迷路,构建错误也少很多。
  2. 没有超时的 fetch,就是隐形内存漏
    默认 fetch 永不超时:重试排队、Tab 卡死、Lambda 拖爆。每次请求都带 AbortController 与预算毫秒。
    // http.js
    export async function getJson(url, ms = 3000, tries = 2) {
    for (let i = 0; i <= tries; i++) { const c = new AbortController(); const t = setTimeout(() => c.abort(), ms);
    try {
    const r = await fetch(url, { signal: c.signal });
    if (!r.ok) throw new Error(HTTP ${r.status});
    return await r.json();
    } catch (e) {
    if (i === tries) throw e;
    } finally {
    clearTimeout(t);
    }
    }
    }
    冷启动稳了,丢包时 p95 延迟不再飙。按服务设默认超时,日志里记预算而不只是 URL。
    图片
  3. 没有 env 模式/schema,配置全靠猜

process.env 散落各处,手抖一个 key,应用带着空值就起飞。把校验集中在启动做掉。
// config.js
const required = [“PORT”, “DB_URL”];
for (const k of required) {
if (!process.env[k]) throw new Error(Missing ${k});
}
export const cfg = {
port: Number(process.env.PORT),
dbUrl: process.env.DB_URL
};
从此误配回滚少。快速失败、一次性打印汇总配置,但记得不打敏感值。

  1. 没有 pre-commit 统一 lint,垃圾进主干
    样式口水仗、微 bug 混进 main。一个钩子把格式 + 修复拦在本地。
    // package.json
    {
    “scripts”: { “lint”: “eslint .”, “fmt”: “prettier -w .” },
    “lint-staged”: { “*.{js,ts,jsx,tsx,json}”: [“prettier -w”, “eslint –fix”] }
    }
    PR 审核时间缩短,CI 队列不卡;同样命令在 CI 再跑一遍,避免“本地钩子能过、线上翻车”。
  2. 开发与生产构建不一致,“只在生产复现”的鬼
    本地走 A 工具,线上走 B 工具;source map、env 标志、side effects 全不一样,追 bug 像捉迷藏。
    +———————-+ +———————-+
    | dev: vite + esbuild | -> | prod: webpack + Ters |
    | ESM/HMR | | CJS/Minify |
    +———————-+ +———————-+
    选同一套链路打 dev/prod,或镜像一致的 flags,保证产物等价。 “只在 prod 崩”这类工单会明显减少。
  3. Promise 批处理不设防,错误直接掉地上
    到处 await foo() 没边界;Promise.all 一炸整批失败且日志寂静。非全或无的场景请用 allSettled。
    const tasks = urls.map(u => getJson(u));
    const results = await Promise.allSettled(tasks);
    const ok = results.filter(r => r.status === “fulfilled”).map(r => r.value);
    const bad = results.filter(r => r.status === “rejected”);
    if (bad.length) console.warn(“partial failure”, bad.length);

可降级任务继续出结果,错误率也有数可看。Promise.all 仅用于真·原子流程。

  1. 路由/目录无拓扑,后来一定烂
    一开始“看着挺清爽”,再过两周循环依赖、死文件、加载时副作用全来了。
    /src
    /routes
    users.js -> controllers/users.js
    orders.js -> controllers/orders.js
    /controllers -> services -> lib
    /lib (routes 禁止反向引用)
    单向依赖:routes → controllers → services → lib;lib 保持叶子、不进口上层。 冷路径延迟降低,因为加载期不再干多余活。
  2. JSON 解析不设限,CPU 被大包拖死
    默认把巨型 payload 全丢给 JSON.parse,一个坏包就把 worker 卡住。限制体积 + 安全解析是标配。
    // 限制请求体
    app.use(express.json({ limit: “200kb” }));

export function safeJson(s) {
try { return JSON.parse(s); }
catch { return null; }
}
吞吐在流量峰值仍稳,事件循环不被拖慢。 按真实场景测量,在均值 +20%设上限,丢包只打一次警。

  1. 把“从未使用”的巨型依赖打进包
    一个日期库 + 一个全量 UI 套件,首屏直接增肥。
    pkg size

dayjs 7kb
date-fns 24kb
moment 67kb
优先小而可 tree-shake的包,按路径导入而非整包抱走。 移动端首屏时间明显改善,低端机不再嗡嗡叫。
上线前,别忘了这几件“无聊但要命”的事
钉死运行时:Node 版本明确、CI 强校验
给网络设预算:每个 fetch 都有超时和重试策略
配置集中校验:fail fast,打印可见、掩盖敏感
模块规则要“无聊”:不混风格,构建链 dev/prod 一致
Promise 用意图驱动:all = 原子,全局流;allSettled = 可降级,多源流
边缘字节要抠:小包、按需、tree-shake 友好
这些默认值到位,下一次 git clone 就会平平无奇(这是夸奖); 而你的生产监控,会更像一条直线,而不是过山车。

全栈AI·探索:涵盖动效、React Hooks、Vue 技巧、LLM 应用、Python 脚本等专栏,案例驱动实战学习,点击二维码了解更多详情。

声明:来自JavaScript 每日一练,仅代表创作者观点。链接:https://eyangzhen.com/3928.html

JavaScript 每日一练的头像JavaScript 每日一练

相关推荐

关注我们
关注我们
购买服务
购买服务
返回顶部