初级 JavaScript 开发者的常见错误:进阶篇
当你已经掌握了 JavaScript 的基本语法,并为浏览器或 Node.js 编写了几个脚本后,似乎最困难的部分已经过去了。但正是在过渡到实际项目(通常在 Node.js 服务器端)的阶段,开发者会遇到一些棘手的错误。这些错误不会立即破坏代码,但会导致难以察觉的 bug、内存泄漏和意外行为。在本文中,我们将分析初级 JavaScript 开发者常犯的 4 个进阶错误,并展示如何避免它们。
1. 对循环中闭包 (Closures in Loops) 的理解不足
这是一个从浏览器 JavaScript 延续到 Node.js 的经典问题。许多人都知道 var 具有函数作用域而非块级作用域,但即使使用 let,如果错误地使用异步回调,也会出现错误。
问题: 你在循环中创建了一个函数数组,并期望每个函数记住它自己的计数器值。但实际上,所有函数都引用了最后一个值。
// ❌ 错误:所有调用都会返回 3const functions = [];for (var i = 0; i < 3; i++) { functions.push(() => console.log(i));}functions.forEach(fn => fn()); // 3, 3, 3为什么会这样? 闭包捕获的不是变量 i 的值,而是对变量本身的引用。当函数被调用时,循环已经结束,i 等于 3。
解决方案(针对 Node.js): 使用 let 获得块级作用域,或者通过 IIFE(立即调用函数表达式)创建新的闭包。
// ✅ 使用 let 修复const functions = [];for (let i = 0; i < 3; i++) { functions.push(() => console.log(i));}functions.forEach(fn => fn()); // 0, 1, 2
// ✅ 使用闭包修复(立即调用函数)for (var i = 0; i < 3; i++) { (function(j) { functions.push(() => console.log(j)); })(i);}2. 忽视异步:Promise 的“竞态条件”与 this 上下文丢失
Node.js 是一个异步环境。最常见的错误之一是在异步操作完成之前尝试使用其结果,或者在方法内部丢失 this 上下文。
示例 1:Promise 竞态条件 (Race condition)
// ❌ 错误:结果可能是 undefinedlet userData;fetchUserData().then(data => { userData = data;});console.log(userData); // undefined — 同步代码先执行了示例 2:回调中丢失 this
class Logger { constructor(prefix) { this.prefix = prefix; } logLater() { setTimeout(function() { console.log(this.prefix); // undefined — this 指向全局对象 }, 100); }}const logger = new Logger('INFO:');logger.logLater(); // undefined解决方案: 对于顺序操作始终使用 async/await,并使用箭头函数来保留上下文。
// ✅ 使用 async/await 修复async function getUserData() { const userData = await fetchUserData(); console.log(userData); // 现在数据肯定存在}
// ✅ 使用箭头函数修复class Logger { constructor(prefix) { this.prefix = prefix; } logLater() { setTimeout(() => { console.log(this.prefix); // INFO: }, 100); }}3. Promise 链和流 (Streams) 中的错误处理不当
在 Node.js 中,许多操作返回 Promise 或流。初级开发者常常忘记在链的中间或流中处理错误,这会导致“静默”崩溃