SyntaxError
原因:代码不符合 JavaScript 语法,整段脚本都没能开始执行。报错里的 Unexpected token 后面跟着它读不懂的那个字符。
// 下面这些每一条都会让整段代码跑不起来,所以逐个用 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
原因:用了一个从没声明过的名字。
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();
本站沙箱里特别容易遇到的:
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
原因:把不是函数的东西当函数调用,或者方法名拼错、用在了错误的类型上。
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 里最常见的运行时错误,没有之一。
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 也当成假,取数字或字符串默认值时应该用 ??。
const count = 0;
console.log(count || 10, count ?? 10); // 10 0 —— 用 || 就把合法的 0 覆盖掉了
RangeError
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
无限循环与超时
// 这段代码永远不会结束
let i = 0;
while (true) {
i++;
if (i > 100000) break; // 少了这一行就会一直转
}
console.log("i =", i);
本站的行为:代码在独立的 Web Worker 里运行,超过 15 秒会自动终止并提示,也可以随时点「停止」。终止后下次运行会自动重建运行环境,页面本身不会卡死。
常见成因:while 的条件永远为真(忘了改循环变量)、for 的第三步写成了 i--、递归没有 base case。
结果为 undefined 或 NaN
这类不报错,但输出不对,最难查。
// 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
相等比较的坑
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 与作用域
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 用错
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); }
报错信息怎么读
浏览器控制台(以及本站的输出区)里的堆栈长这样:
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之类),只留下你代码的位置。
调试的三个习惯
// 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));