在线代码运行BanShow Run

JavaScript 常见报错与原因

JavaScript 常见报错对照表,每一节标题就是你会在屏幕上看到的报错原文,下面给出原因和改法。可以复制到浏览器里亲手触发一遍,比背教程记得牢。

打开 JavaScript 运行器 →

SyntaxError

原因:代码不符合 JavaScript 语法,整段脚本都没能开始执行。报错里的 Unexpected token 后面跟着它读不懂的那个字符。

javascript
// 下面这些每一条都会让整段代码跑不起来,所以逐个用 try 演示是不可能的——
// 语法错误在"编译"阶段就发生,try/catch 抓不到。请把注释去掉一条一条试。

console.log("少了一个括号"      // SyntaxError: Unexpected end of input
if (true) { console.log(1) }    // SyntaxError: missing condition parentheses → if (true)
const a = 1;
const a = 2;                    // SyntaxError: Identifier 'a' has already been declared
let 1x = 5;                     // SyntaxError: Invalid or unexpected token
function f() { return 1 }       // 正确
const obj = { a: 1, };          // 尾随逗号在现代 JS 里是允许的
console.log('引号不匹配");

高频原因:

  • 括号、引号、方括号没配对。编辑器里点一下括号,CodeMirror 会高亮配对的另一半。
  • 用了中文标点:()""'':;, 全部要换成英文半角。
  • const 声明的变量又用 const 声明一次,或者给它赋值。
  • return 写在函数外面。
  • 对象字面量里键值之间用了 = 而不是 :。

ReferenceError: xxx is not defined

原因:用了一个从没声明过的名字。

javascript
console.log(score);         // ReferenceError: score is not defined
console.log(Score);         // 大小写敏感
consol.log("hi");           // ReferenceError: consol is not defined(拼错了 console)

function f() {
  console.log(inner);       // ReferenceError
  let inner = 1;            // let / const 有"暂时性死区",声明前不能用
}
f();

本站沙箱里特别容易遇到的:

javascript
try {
  console.log(document.title);      // ReferenceError: document is not defined
} catch (e) { console.log(e.message); }
try {
  console.log(window.innerWidth);   // ReferenceError: window is not defined
} catch (e) { console.log(e.message); }

代码运行在 Web Worker 沙箱里,**没有 window、document、localStorage、alert**。这是刻意设计:让你专心练语法,也让死循环不会卡住页面。想练网页交互请在本地环境或用浏览器的 DevTools Console。

TypeError: xxx is not a function

原因:把不是函数的东西当函数调用,或者方法名拼错、用在了错误的类型上。

javascript
const nums = [3, 1, 2];

try { nums.foreach((x) => x); } catch (e) { console.log(e.message); }     // 大小写:forEach
try { nums.push2(1); } catch (e) { console.log(e.message); }
try { "abc".length(); } catch (e) { console.log(e.message); }             // length 是属性不是方法

const user = { name: "小明" };
try { user.name.toUpperCase2(); } catch (e) { console.log(e.message); }
try { Object.entris(user); } catch (e) { console.log(e.message); }

// 最经典的一种:上一步返回了 undefined,接着调用它的方法
function findUser(id) {
  return id === 1 ? { name: "小明" } : undefined;
}
try {
  console.log(findUser(2).name);
} catch (e) {
  console.log(e.message);      // Cannot read properties of undefined (reading 'name')
}
console.log(findUser(2)?.name);   // 用可选链 ?. 就不会报错,得到 undefined

TypeError: Cannot read properties of undefined / null

原因:从 undefined 或 null 上取属性。这是 JavaScript 里最常见的运行时错误,没有之一。

javascript
const data = { users: [{ name: "小明" }] };

try { console.log(data.user[0]); } catch (e) { console.log(e.message); }        // user 不存在
try { console.log(data.users[5].name); } catch (e) { console.log(e.message); }  // 下标越界得到 undefined
try { console.log(null.name); } catch (e) { console.log(e.message); }

// 三种安全写法
console.log(data.users?.[5]?.name);                  // 可选链,得到 undefined
console.log(data.users[5]?.name ?? "没有这个人");     // 配合 ?? 给默认值
if (Array.isArray(data.users) && data.users.length > 5) console.log(data.users[5].name);

**要注意 ?. 和 ?? 的区别**:a?.b 是「a 是 null/undefined 就不往下取」;a ?? b 是「a 是 null/undefined 就用 b」。a || b 会把 0、""、false 也当成假,取数字或字符串默认值时应该用 ??。

javascript
const count = 0;
console.log(count || 10, count ?? 10);   // 10 0 —— 用 || 就把合法的 0 覆盖掉了

RangeError

javascript
try { new Array(-1); } catch (e) { console.log(e.message); }        // Invalid array length
try { (1).toFixed(200); } catch (e) { console.log(e.message); }     // toFixed() digits argument must be between 0 and 100
try { [].at(0).toString(); } catch (e) { console.log(e.constructor.name); }   // 这个其实是 TypeError

// 栈溢出:递归没有出口
function f(n) { return f(n + 1); }
try { f(0); } catch (e) { console.log(e.constructor.name + ": " + e.message); }   // RangeError: Maximum call stack size exceeded

无限循环与超时

javascript
// 这段代码永远不会结束
let i = 0;
while (true) {
  i++;
  if (i > 100000) break;     // 少了这一行就会一直转
}
console.log("i =", i);

本站的行为:代码在独立的 Web Worker 里运行,超过 15 秒会自动终止并提示,也可以随时点「停止」。终止后下次运行会自动重建运行环境,页面本身不会卡死。

常见成因:while 的条件永远为真(忘了改循环变量)、for 的第三步写成了 i--、递归没有 base case。

结果为 undefined 或 NaN

这类不报错,但输出不对,最难查。

javascript
// 1. 函数忘了 return
function add(a, b) { a + b; }
console.log(add(1, 2));            // undefined

// 2. 修改类方法返回 undefined
const nums = [3, 1, 2];
const sorted = nums.sort();        // sort 原地排序并返回同一个数组(这个 OK)
const pushed = nums.push(4);       // push 返回的是新长度,不是数组!
console.log(sorted, pushed);       // [3,1,2,4] 4

// 3. map / forEach 搞混
const doubled = nums.forEach((x) => x * 2);   // forEach 返回 undefined
console.log(doubled);
console.log(nums.map((x) => x * 2));          // map 才返回新数组

// 4. 箭头函数用了花括号就必须写 return
const square = (x) => { x * x; };
console.log(square(3));                       // undefined
const square2 = (x) => x * x;
console.log(square2(3));                      // 9

// 5. NaN 会传染,任何涉及 NaN 的运算都是 NaN
console.log(parseInt("abc") + 1);             // NaN
console.log(Number(undefined));               // NaN
console.log(Number(null));                    // 0(注意和 undefined 不同)
console.log(Number.isNaN(NaN), NaN === NaN);  // true false

相等比较的坑

javascript
console.log(0 == "", 0 == false, "" == false, null == undefined);   // 全是 true
console.log(0 === "", 0 === false, null === undefined);             // 全是 false
console.log([] == false, [] == 0, [null] == false);                 // true true true(隐式转换)
console.log([] === []);                     // false:两个不同的数组对象
console.log({ a: 1 } === { a: 1 });         // false:对象比较的是引用
console.log([1, 2].join() === [1, 2].join());   // true:字符串按内容比较

// 深比较要自己写
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
console.log(same({ a: 1 }, { a: 1 }));      // true(但键顺序不同就会失效)

结论:一律使用 === 和 !==。

var、let 与作用域

javascript
for (var i = 0; i < 3; i++) {}
console.log("var 泄漏到外面:", i);      // 3

for (let j = 0; j < 3; j++) {}
try { console.log(j); } catch (e) { console.log("let 只活在循环里:", e.message); }

// 经典面试题:var + 异步
const tasks = [];
for (var k = 0; k < 3; k++) tasks.push(() => k);
console.log(tasks.map((f) => f()));       // [3,3,3] 三个函数共享同一个 k

const tasks2 = [];
for (let m = 0; m < 3; m++) tasks2.push(() => m);
console.log(tasks2.map((f) => f()));      // [0,1,2] 每轮循环都是新的 m

async / await 用错

javascript
const delay = (ms, v) => new Promise((r) => setTimeout(r, ms, v));

// 1. 忘了 await,拿到的是 Promise 对象
console.log(delay(10, "结果"));            // Promise { <pending> }
console.log(await delay(10, "结果"));      // 结果

// 2. 循环里串行 await,白白变慢
const t0 = Date.now();
for (const x of ["a", "b", "c"]) await delay(100, x);
console.log("串行耗时约", Date.now() - t0, "ms");

const t1 = Date.now();
await Promise.all(["a", "b", "c"].map((x) => delay(100, x)));
console.log("并发耗时约", Date.now() - t1, "ms");

// 3. 忘了 catch,Promise 失败时会变成未处理的 rejection
try {
  await Promise.reject(new Error("故意失败"));
} catch (e) {
  console.log("捕获到:", e.message);
}

// 4. async 函数里 throw 会变成 rejected Promise,不是同步异常
async function boom() { throw new Error("boom"); }
try { await boom(); } catch (e) { console.log("async 里的 throw:", e.message); }

报错信息怎么读

浏览器控制台(以及本站的输出区)里的堆栈长这样:

text
TypeError: Cannot read properties of undefined (reading 'name')
    at check (user code:12:18)
    at user code:20:1
  • 第一行最重要:错误类型 + 说明。reading 'name' 告诉你它想读的哪个属性,undefined 告诉你那个对象本身是空的。
  • **后面的 at ... 是调用栈,从上往下是「谁调用了谁」,第一行就是出错的位置**。
  • user code:12:18 表示你写的代码第 12 行第 18 列。本站会把这个行列号对应的代码行用波浪线标出来。
  • 本站会自动过滤掉运行环境自身的内部帧(blob:、worker.js 之类),只留下你代码的位置。

调试的三个习惯

javascript
// 1. 用 console.log 把中间值打出来,尤其是循环和递归
const nums = [5, 2, 9, 1];
let total = 0;
for (const n of nums) {
  total += n;
  console.log("累加到:", n, "当前总和:", total);
}

// 2. 打印整个对象而不是拼字符串,能看到结构
const user = { name: "小明", scores: { 语文: 88, 数学: 92 } };
console.log(user);
console.log(JSON.stringify(user, null, 2));

// 3. 用断言式的输出快速定位
console.log("数组长度对不对:", nums.length === 4, nums.length);
console.log("类型对不对:", typeof total, Array.isArray(nums));

换一门语言