重要提示: "不好"的定义有时是相对的,取决于上下文、团队规范和性能要求。这里的示例主要针对那些普遍认为会增加 Bug 风险、降低可读性、影响性能或难以维护的做法。
JavaScript 不良代码实践及优化建议
一、 变量与作用域
-
滥用
var
声明变量-
不良:
javascriptfunction exampleVar() { if (true) { var count = 10; // var 没有块级作用域 } console.log(count); // 输出 10,变量泄漏到函数作用域 for (var i = 0; i < 3; i++) { setTimeout(function timer() { // console.log(i); // 输出 3, 3, 3 (闭包问题) }, i * 100); } } exampleVar();
-
优化: 使用
let
和const
javascriptfunction exampleLetConst() { if (true) { const count = 10; // const/let 具有块级作用域 // console.log(count); // 在块内可见 } // console.log(count); // ReferenceError: count is not defined for (let i = 0; i < 3; i++) { // let 在每次循环创建新的绑定 setTimeout(function timer() { console.log(i); // 输出 0, 1, 2 }, i * 100); } } exampleLetConst();
-
解释:
var
存在变量提升和函数作用域,容易导致变量覆盖、循环闭包陷阱和意外的全局变量。let
和const
具有块级作用域,更符合预期,const
还强制变量不可重新赋值(引用类型内容可变),有助于提高代码稳定性。
-
-
隐式创建全局变量
-
不良:
scssfunction setGlobal() { userId = 123; // 没有使用 var/let/const,在非严格模式下会创建全局变量 // 'use strict'; // 在严格模式下会直接报错 ReferenceError } setGlobal(); // console.log(window.userId); // 浏览器环境下输出 123
-
优化: 始终使用
let
或const
声明变量。javascript'use strict'; // 推荐开启严格模式 function setLocal() { const userId = 123; // 显式声明为局部变量 console.log(userId); } setLocal(); // console.log(window.userId); // undefined (或 ReferenceError)
-
解释: 隐式全局变量污染全局命名空间,可能导致难以追踪的冲突和 Bug。严格模式 (
'use strict'
) 可以帮助捕获这类错误。
-
-
变量命名不清晰或过于简短
-
不良:
javascriptlet x = 10; // x 代表什么? let arr = [1, 2, 3]; // arr 是什么内容的数组? function proc(d) { // proc 做什么? d 是什么? // ... }
-
优化: 使用有意义的、描述性的名称。
inilet maxRetryCount = 10; let userIds = [1, 2, 3]; function processUserData(userData) { // ... }
-
解释: 清晰的命名是代码可读性的关键。好的命名能自解释代码的意图。
-
-
在循环外声明循环变量 (如果只在循环内使用)
-
不良:
csslet i; // 在外部声明 const data = [1, 2, 3]; for (i = 0; i < data.length; i++) { console.log(data[i]); } // 循环结束后 i 仍然存在于外部作用域 console.log('Loop finished, i =', i); // i = 3
-
优化: 使用
let
在for
循环初始化语句中声明。iniconst data = [1, 2, 3]; for (let i = 0; i < data.length; i++) { // i 的作用域限制在循环内 console.log(data[i]); } // console.log('Loop finished, i =', i); // ReferenceError: i is not defined
-
解释: 将变量的作用域限制在最小需要的范围内,减少变量泄漏和潜在冲突。
-
二、 数据类型与比较
-
使用
==
进行比较 (非严格等于)-
不良:
iniconsole.log(0 == false); // true (发生类型转换) console.log('' == false); // true (发生类型转换) console.log(null == undefined); // true (规范特例) console.log(1 == '1'); // true (发生类型转换) console.log([] == false); // true (复杂转换) console.log([] == ![]); // true (更复杂转换)
-
优化: 使用
===
(严格等于) 和!==
(严格不等于)。javascriptconsole.log(0 === false); // false console.log('' === false); // false console.log(null === undefined); // false console.log(1 === '1'); // false console.log([] === false); // false // 仅在明确需要利用类型转换时才使用 ==,并加注释说明 // 例如,检查 null 或 undefined: let value = null; if (value == null) { // 等价于 value === null || value === undefined console.log('Value is null or undefined'); }
-
解释:
==
会进行隐式类型转换,其规则复杂且容易出错。===
不进行类型转换,只有在类型和值都相同时才返回true
,更可预测、更安全。
-
-
不正确地检查
NaN
-
不良:
inilet result = parseInt('abc'); // NaN if (result == NaN) { // 永远是 false,因为 NaN != NaN console.log('Result is NaN'); } if (result === NaN) { // 永远是 false console.log('Result is NaN'); }
-
优化: 使用
isNaN()
或Number.isNaN()
。javascriptlet result = parseInt('abc'); // NaN if (isNaN(result)) { // true - isNaN 会尝试转换参数为数字 console.log('Result is NaN (using isNaN)'); } if (Number.isNaN(result)) { // true - Number.isNaN 更严格,不进行类型转换 console.log('Result is NaN (using Number.isNaN)'); } // isNaN 的陷阱 console.log(isNaN('hello')); // true, 因为 'hello' 无法转为数字,被认为是 NaN console.log(Number.isNaN('hello')); // false, 因为 'hello' 本身不是 NaN 类型 // 推荐使用 Number.isNaN() 来精确判断一个值是否真的是 NaN 类型
-
解释:
NaN
是唯一一个不等于自身的值。必须使用isNaN()
或Number.isNaN()
来检查。Number.isNaN()
通常更推荐,因为它不会对参数进行强制类型转换。
-
-
依赖隐式类型转换的运算
-
不良:
inilet countStr = "5"; let total = countStr + 10; // "510" (字符串拼接) console.log(total); let isActive = "true"; // 字符串 if (isActive) { // 字符串 "true" 被转换为布尔值 true console.log("Active"); }
-
优化: 显式进行类型转换。
inilet countStr = "5"; let total = parseInt(countStr, 10) + 10; // 15 (显式转为数字) // 或使用 Number() 或 + // let total = Number(countStr) + 10; // let total = +countStr + 10; console.log(total); let isActiveStr = "true"; let isActiveBool = isActiveStr === "true"; // 显式转为布尔值 if (isActiveBool) { console.log("Active"); }
-
解释: 隐式类型转换可能导致意想不到的结果。显式转换使代码意图更清晰,减少错误。
-
-
使用
new Boolean()
,new String()
,new Number()
-
不良:
javascriptlet boolObject = new Boolean(false); if (boolObject) { // 对象总是被认为是 true! 即使它包装的是 false console.log("Boolean object is truthy!"); } console.log(typeof boolObject); // "object"
-
优化: 直接使用字面量或相应的转换函数。
inilet boolLiteral = false; if (boolLiteral) { // 不会执行 } else { console.log("Boolean literal false is falsy"); } console.log(typeof boolLiteral); // "boolean" let numLiteral = 10; let strLiteral = "hello"; let numFromStr = Number("123"); // 使用转换函数
-
解释: 使用构造函数创建的是包装对象,它们的类型是
object
,在布尔上下文中总是true
,容易引起混淆。直接使用字面量或Boolean()
,Number()
,String()
(不带new
) 进行类型转换。
-
三、 控制流
-
深度嵌套的
if-else
或循环-
不良:
javascriptfunction processData(data, user, settings) { if (data) { if (user.isLoggedIn) { if (settings.isActive) { for (let item of data) { if (item.value > 10) { // ... 很多层嵌套 ... console.log('Processing item deep inside:', item); } } } else { console.log('Settings not active'); } } else { console.log('User not logged in'); } } else { console.log('No data'); } }
-
优化: 使用卫语句 (Guard Clauses)、提前返回、函数抽取。
phpfunction processItem(item) { // 抽取处理单个 item 的逻辑 console.log('Processing item:', item); } function processDataOptimized(data, user, settings) { // 卫语句提前处理无效情况 if (!data) { console.log('No data'); return; } if (!user.isLoggedIn) { console.log('User not logged in'); return; } if (!settings.isActive) { console.log('Settings not active'); return; } // 核心逻辑,嵌套减少 for (let item of data) { if (item.value > 10) { processItem(item); // 调用抽取出的函数 } } } // 模拟数据调用 processDataOptimized([{value: 5}, {value: 15}], {isLoggedIn: true}, {isActive: true}); processDataOptimized(null, {isLoggedIn: true}, {isActive: true});
-
解释: 过度嵌套的代码难以阅读和维护(形成"箭头型代码")。卫语句可以快速处理掉异常或前置条件,使主逻辑更清晰。抽取函数可以降低单个函数的复杂度。
-
-
在
if
条件中使用赋值语句 (易错)-
不良:
inilet x = 0; if (x = 5) { // 实际上是赋值,且赋值表达式结果是 5 (truthy) console.log("x is 5? No, x was assigned 5."); // 这会执行 } console.log(x); // x 现在是 5
-
优化: 明确使用比较运算符。
inilet y = 0; if (y === 5) { // 使用严格比较 console.log("y is 5."); } else { console.log("y is not 5."); } y = 5; // 如果需要赋值,单独进行 if (y === 5) { console.log("Now y is 5."); }
-
解释: 在
if
中使用=
极易与==
或===
混淆,导致逻辑错误。虽然在某些受控情况下(如循环中while (node = node.next)
) 会有意使用,但通常应避免。
-
-
使用魔术数字或魔术字符串
-
不良:
inifunction calculatePrice(quantity, type) { let price = 0; if (type === 1) { // 1 代表什么? price = quantity * 10.5; // 10.5 是什么单价? } else if (type === 2) { price = quantity * 8.0; } if (price > 1000) { // 1000 是什么阈值? price *= 0.9; // 0.9 是什么折扣? } return price; } console.log(calculatePrice(50, 1));
-
优化: 使用具名常量或枚举。
iniconst ProductType = { STANDARD: 1, PREMIUM: 2, }; const Price = { STANDARD: 10.5, PREMIUM: 8.0, }; const DISCOUNT_THRESHOLD = 1000; const DISCOUNT_RATE = 0.9; // 90% function calculatePriceOptimized(quantity, type) { let price = 0; if (type === ProductType.STANDARD) { price = quantity * Price.STANDARD; } else if (type === ProductType.PREMIUM) { price = quantity * Price.PREMIUM; } else { console.warn("Unknown product type:", type); return 0; // 或者抛出错误 } if (price > DISCOUNT_THRESHOLD) { price *= DISCOUNT_RATE; } return price; } console.log(calculatePriceOptimized(50, ProductType.STANDARD));
-
解释: "魔术"值(直接写在代码里的、没有解释的字面量)使代码难以理解和维护。当这些值需要改变时,需要在多处修改,容易出错。使用常量提高了可读性,易于修改。
-
-
不必要的
else
(在if
中有return
或throw
时)-
不良:
javascriptfunction checkValue(value) { if (value < 0) { return "Negative"; } else { // 这个 else 是多余的 console.log("Processing non-negative value..."); return "Non-negative"; } }
-
优化: 移除不必要的
else
。javascriptfunction checkValueOptimized(value) { if (value < 0) { return "Negative"; } // 如果执行到这里,说明 value >= 0 console.log("Processing non-negative value..."); return "Non-negative"; }
-
解释: 当
if
分支包含return
,throw
,continue
,break
等中断后续执行的语句时,else
块是不必要的,移除它可以减少一层嵌套,使代码更简洁。
-
-
使用
for...in
遍历数组 (可能遍历原型链属性)-
不良:
javascriptArray.prototype.customMethod = function() {}; // 在原型上添加方法 const myArray = [1, 2, 3]; let sum = 0; for (const key in myArray) { console.log(`Key: ${key}, Type: ${typeof key}`); // key 是字符串 "0", "1", "2", "customMethod" // if (myArray.hasOwnProperty(key)) { // 需要手动检查 // sum += myArray[key]; // 如果不检查,可能会尝试加函数,导致 NaN 或错误 // } } // console.log(sum); // 结果可能不符合预期 delete Array.prototype.customMethod; // 清理
-
优化: 使用
for...of
(推荐),forEach
, 或标准for
循环。javascriptconst myArrayOptimized = [1, 2, 3]; let sumOptimized = 0; // 推荐: for...of (获取值) for (const value of myArrayOptimized) { console.log(`Value: ${value}, Type: ${typeof value}`); // value 是数字 1, 2, 3 sumOptimized += value; } console.log("Sum (for...of):", sumOptimized); // 6 // 或者: forEach (带索引) sumOptimized = 0; myArrayOptimized.forEach((value, index) => { console.log(`Index: ${index}, Value: ${value}`); sumOptimized += value; }); console.log("Sum (forEach):", sumOptimized); // 6 // 或者: 标准 for 循环 (需要索引时) sumOptimized = 0; for (let i = 0; i < myArrayOptimized.length; i++) { sumOptimized += myArrayOptimized[i]; } console.log("Sum (standard for):", sumOptimized); // 6
-
解释:
for...in
主要设计用于遍历对象的可枚举属性键 (key) ,它会遍历原型链上的属性,并且遍历顺序不确定。对于数组,应使用for...of
(获取值)、forEach
(值和索引)或标准for
循环(需要控制索引或中断)。
-
四、 函数
-
函数过长,职责过多 (违反单一职责原则)
-
不良:
javascriptfunction handleUserData(userId) { // 1. 获取用户数据 console.log(`Fetching data for user ${userId}...`); const userData = { id: userId, name: 'Temp', email: '[email protected]' }; // 模拟 API 调用 // 2. 验证数据 console.log('Validating user data...'); if (!userData.email || !userData.email.includes('@')) { console.error('Invalid email'); // ...错误处理 return; } // 3. 格式化数据 console.log('Formatting user data...'); const formattedName = userData.name.toUpperCase(); // 4. 更新 UI console.log('Updating UI...'); // const userNameElement = document.getElementById('user-name'); // if (userNameElement) userNameElement.textContent = formattedName; // 5. 发送分析事件 console.log('Sending analytics event...'); // analytics.track('UserDataHandled', { userId }); console.log('User data handling complete.'); } handleUserData(1);
-
优化: 将不同职责拆分成更小的、独立的函数。
javascriptfunction fetchUserData(userId) { console.log(`Fetching data for user ${userId}...`); // 模拟 API 调用 return { id: userId, name: 'Temp', email: '[email protected]' }; } function validateUserData(userData) { console.log('Validating user data...'); if (!userData || !userData.email || !userData.email.includes('@')) { console.error('Invalid user data or email'); return false; } return true; } function formatUserName(userData) { console.log('Formatting user name...'); return userData.name.toUpperCase(); } function updateUI(userName) { console.log('Updating UI with name:', userName); // const userNameElement = document.getElementById('user-name'); // if (userNameElement) userNameElement.textContent = userName; } function sendAnalytics(eventName, data) { console.log(`Sending analytics event: ${eventName}`, data); // analytics.track(eventName, data); } function handleUserDataOptimized(userId) { const userData = fetchUserData(userId); if (!validateUserData(userData)) { return; // 验证失败,提前退出 } const formattedName = formatUserName(userData); updateUI(formattedName); sendAnalytics('UserDataHandled', { userId }); console.log('User data handling complete (optimized).'); } handleUserDataOptimized(2);
-
解释: 过长的函数难以理解、测试和维护。遵循单一职责原则,将函数拆分成更小、功能单一的部分,可以提高代码的模块化、可重用性和可测试性。
-
-
函数参数过多
-
不良:
javascriptfunction createUser(username, password, email, firstName, lastName, age, country, isAdmin) { console.log(`Creating user ${username} in ${country}...`); // ... lots of parameters } createUser('john_doe', 'pass123', '[email protected]', 'John', 'Doe', 30, 'USA', false);
-
优化: 使用对象作为参数(参数对象模式)。
phpfunction createUserOptimized(options) { // 使用解构赋值和默认值 const { username, password, email, firstName, lastName, age = 18, // 可以设置默认值 country = 'Unknown', isAdmin = false } = options; // 校验必需参数 if (!username || !password || !email) { console.error("Username, password, and email are required."); return; } console.log(`Creating user ${username} in ${country} (age: ${age}, admin: ${isAdmin})...`); // ... } createUserOptimized({ username: 'jane_doe', password: 'securePassword', email: '[email protected]', firstName: 'Jane', lastName: 'Doe', // age, country, isAdmin 可以省略,会使用默认值 }); createUserOptimized({ username: 'admin_user', password: 'adminPassword', email: '[email protected]', isAdmin: true, age: 40, country: 'Canada' });
-
解释: 过多的参数使函数调用变得困难且容易出错(参数顺序、可选参数处理)。使用参数对象可以:
- 使参数传递更清晰(键值对)。
- 参数顺序不再重要。
- 更容易添加或删除参数,向后兼容性更好。
- 方便使用解构赋值和设置默认值。
-
-
函数有副作用 (Side Effects) 且命名不清晰
-
不良:
javascriptlet globalCounter = 0; function getAndIncrement() { // 函数名像只获取,实际修改了全局状态 globalCounter++; return globalCounter - 1; // 返回的是增加前的值? 还是增加后的? 易混淆 } console.log(getAndIncrement()); // 0 console.log(getAndIncrement()); // 1 console.log(globalCounter); // 2
-
优化: 分离查询和修改,或明确函数名体现副作用。
javascriptlet globalCounterOptimized = 0; // 查询函数 (无副作用) function getCounter() { return globalCounterOptimized; } // 修改函数 (有副作用,命名清晰) function incrementCounter() { globalCounterOptimized++; console.log("Counter incremented to:", globalCounterOptimized); } // 或者,如果需要原子操作,函数名明确体现 function incrementAndGetPreviousCounter() { const previousValue = globalCounterOptimized; globalCounterOptimized++; console.log("Counter incremented to:", globalCounterOptimized); return previousValue; } console.log("Initial counter:", getCounter()); // 0 incrementCounter(); // Counter incremented to: 1 console.log("Counter after increment:", getCounter()); // 1 const prevVal = incrementAndGetPreviousCounter(); // Counter incremented to: 2 console.log("Previous value was:", prevVal); // 1 console.log("Final counter:", getCounter()); // 2
-
解释: 函数的副作用(修改函数外部的状态,如全局变量、DOM 等)会增加代码的复杂性和不可预测性。尽量编写纯函数(给定相同输入总是返回相同输出,且无副作用)。如果必须有副作用,函数命名应清晰地反映其行为。
-
-
滥用
arguments
对象-
不良:
inifunction sumAll() { let sum = 0; // arguments 不是真正的数组,没有 forEach, map 等方法 for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } console.log(sumAll(1, 2, 3, 4)); // 10
-
优化: 使用剩余参数 (
...rest
)。javascriptfunction sumAllOptimized(...numbers) { // numbers 是一个真正的数组 console.log("Received numbers:", numbers); return numbers.reduce((sum, current) => sum + current, 0); } console.log(sumAllOptimized(1, 2, 3, 4, 5)); // 15
-
解释:
arguments
对象是类数组对象,使用不便且在某些 JavaScript 引擎中可能影响优化。剩余参数 (...rest
) 提供了一个真正的数组,可以使用所有数组方法,代码更简洁、更现代。
-
-
不正确地处理函数中的
this
指向-
不良:
javascriptconst myObject = { value: 10, getValue: function() { // 在 setTimeout 的回调中,this 通常指向 window (非严格模式) 或 undefined (严格模式) setTimeout(function() { // console.log(this.value); // TypeError or undefined // console.log('Inside setTimeout, this is:', this); }, 100); } }; // myObject.getValue(); function MyConstructor() { this.data = 'some data'; // document.addEventListener('click', function() { // console.log(this.data); // this 指向 document,而不是 MyConstructor 实例 // }); } // const instance = new MyConstructor();
-
优化: 使用箭头函数、
bind
或保存this
引用。javascript'use strict'; // 推荐严格模式 const myObjectOptimized = { value: 10, getValueArrow: function() { // 箭头函数不绑定自己的 this,它会捕获其词法上下文的 this setTimeout(() => { console.log("Arrow function this.value:", this.value); // 10 (this 指向 myObjectOptimized) console.log('Inside arrow function, this is:', this); }, 100); }, getValueBind: function() { // 使用 bind 显式绑定 this const callback = function() { console.log("Bind this.value:", this.value); // 10 console.log('Inside bind callback, this is:', this); }.bind(this); // 绑定当前的 this (myObjectOptimized) setTimeout(callback, 200); }, getValueSelf: function() { // 使用变量保存 this 引用 (旧方法) const self = this; setTimeout(function() { console.log("Self this.value:", self.value); // 10 console.log('Inside self callback, this is:', this, 'but self is:', self); // this 可能是 window/undefined }, 300); } }; myObjectOptimized.getValueArrow(); myObjectOptimized.getValueBind(); myObjectOptimized.getValueSelf(); function MyConstructorOptimized() { this.data = 'some data optimized'; // 使用箭头函数保持 this 指向实例 // document.addEventListener('click', () => { // console.log('Event listener this.data:', this.data); // this 指向 MyConstructorOptimized 实例 // }); // 或者使用 bind // document.addEventListener('click', function() { // console.log('Event listener (bind) this.data:', this.data); // }.bind(this)); } // const instanceOptimized = new MyConstructorOptimized();
-
解释: JavaScript 中的
this
指向在函数调用时确定,规则复杂(取决于调用方式:普通函数、对象方法、构造函数、call/apply/bind
、箭头函数)。普通函数回调(如setTimeout
, 事件监听器)中的this
通常不指向预期对象。箭头函数是解决此问题的现代且简洁的方法,因为它继承外层作用域的this
。bind
或保存this
到变量(如self
,that
)是传统方法。
-
五、 数组与对象
-
直接修改传入函数的数组或对象参数 (产生副作用)
-
不良:
javascriptfunction sortArrayInPlace(arr) { // 直接修改了原始数组 arr.sort((a, b) => a - b); return arr; // 返回的是修改后的原始数组引用 } const originalArray = [3, 1, 4, 1, 5]; console.log("Original array before:", originalArray); const sortedArrayRef = sortArrayInPlace(originalArray); console.log("Original array after:", originalArray); // [1, 1, 3, 4, 5] (被修改了!) console.log("Returned array ref:", sortedArrayRef); // [1, 1, 3, 4, 5] console.log(originalArray === sortedArrayRef); // true
-
优化: 创建副本进行修改,或明确函数会修改原对象/数组。
javascriptfunction sortArrayImmutable(arr) { // 1. 创建副本 (使用 slice() 或扩展运算符 ...) const newArray = [...arr]; // 2. 在副本上排序 newArray.sort((a, b) => a - b); // 3. 返回新数组 return newArray; } const originalArray2 = [3, 1, 4, 1, 5, 9]; console.log("Original array 2 before:", originalArray2); const sortedNewArray = sortArrayImmutable(originalArray2); console.log("Original array 2 after:", originalArray2); // [3, 1, 4, 1, 5, 9] (未被修改) console.log("Returned new array:", sortedNewArray); // [1, 1, 3, 4, 5, 9] console.log(originalArray2 === sortedNewArray); // false // 如果确实需要修改原数组,函数命名应体现 (如 sortArrayInPlace) // 并在文档/注释中说明其副作用
-
解释: 直接修改传入的对象或数组参数是一种副作用,可能导致调用者代码中出现意外行为,因为原始数据被改变了。对于期望无副作用的函数,应在副本上操作并返回新结果(不可变性原则)。如果函数设计为就地修改,命名和文档应清晰说明。
-
-
使用
delete
从数组中删除元素 (留下空位)-
不良:
javascriptconst numbers = [10, 20, 30, 40, 50]; delete numbers[2]; // 删除索引为 2 的元素 (30) console.log(numbers); // [ 10, 20, <1 empty item>, 40, 50 ] console.log(numbers.length); // 5 (长度不变!) console.log(numbers[2]); // undefined // 遍历时可能遇到问题 numbers.forEach(n => console.log(n)); // 10, 20, 40, 50 (跳过了空位) for(let i=0; i<numbers.length; i++){ console.log(`Index ${i}: ${numbers[i]}`); // Index 2: undefined }
-
优化: 使用
splice()
或filter()
。javascriptconst numbersSplice = [10, 20, 30, 40, 50]; // 从索引 2 开始,删除 1 个元素 const removedElements = numbersSplice.splice(2, 1); console.log("Using splice:", numbersSplice); // [ 10, 20, 40, 50 ] console.log("Splice removed:", removedElements); // [ 30 ] console.log("Splice length:", numbersSplice.length); // 4 const numbersFilter = [10, 20, 30, 40, 50]; // 创建一个不包含 30 的新数组 const filteredNumbers = numbersFilter.filter(n => n !== 30); console.log("Using filter:", filteredNumbers); // [ 10, 20, 40, 50 ] console.log("Filter length:", filteredNumbers.length); // 4 console.log("Original after filter:", numbersFilter); // [ 10, 20, 30, 40, 50 ] (filter 不修改原数组)
-
解释:
delete
操作符仅将数组指定索引处的值设为undefined
,并留下一个"空洞"(empty slot),数组长度不变,这通常不是期望的行为。splice()
可以直接在原数组上删除(或添加/替换)元素,并正确更新长度。filter()
创建一个满足条件的新数组,是实现不可变删除的好方法。
-
-
低效的数组查找 (对未排序大数组使用
indexOf
或find
)-
不良:
javascript// 假设 data 是一个包含 100 万个对象的大数组,且未排序 const largeData = Array.from({ length: 1000000 }, (_, i) => ({ id: i, value: Math.random() })); const targetId = 999999; console.time('indexOfLarge'); // indexOf 对对象数组无效,除非是同一个对象引用 // const index = largeData.indexOf({ id: targetId }); // -1 // find 需要遍历 const foundItem = largeData.find(item => item.id === targetId); console.timeEnd('indexOfLarge'); // 时间取决于 targetId 的位置,最坏情况 O(n) console.log("Found item:", foundItem ? foundItem.id : 'Not Found');
-
优化: 使用
Map
或Set
进行快速查找 (如果需要频繁查找)。javascriptconst largeDataOptimized = Array.from({ length: 1000000 }, (_, i) => ({ id: i, value: Math.random() })); const targetIdOptimized = 999999; // 预处理:创建一个 Map 用于快速查找,空间换时间 console.time('createMap'); const dataMap = new Map(largeDataOptimized.map(item => [item.id, item])); console.timeEnd('createMap'); console.time('findInMap'); const foundItemMap = dataMap.get(targetIdOptimized); // O(1) 平均时间复杂度 console.timeEnd('findInMap'); // 非常快 console.log("Found item in Map:", foundItemMap ? foundItemMap.id : 'Not Found'); // 如果只需要检查是否存在,Set 更合适 // const idSet = new Set(largeDataOptimized.map(item => item.id)); // console.time('findInSet'); // const exists = idSet.has(targetIdOptimized); // O(1) // console.timeEnd('findInSet');
-
解释: 对于大型数组,线性查找(如
find
,findIndex
,indexOf
)的时间复杂度是 O(n),性能较差。如果需要频繁根据某个键(如id
)进行查找,可以先将数组转换为Map
(键值对存储)或Set
(仅存储唯一值),它们的查找操作平均时间复杂度接近 O(1),性能显著提升。但这需要额外的内存和预处理时间。
-
-
不必要的数组或对象创建
-
不良:
javascript// 在循环中创建不必要的数组/对象 function processCoords(coords) { for (const coord of coords) { const tempPoint = [coord.x, coord.y]; // 每次循环都创建新数组 // 如果只是读取,不需要创建 console.log(`Processing point: (${tempPoint[0]}, ${tempPoint[1]})`); } } processCoords([{x:1, y:2}, {x:3, y:4}]); // 返回一个每次都一样的新数组/对象 function getDefaultOptions() { return { theme: 'light', timeout: 5000 }; // 每次调用都创建新对象 } let options1 = getDefaultOptions(); let options2 = getDefaultOptions(); console.log(options1 === options2); // false
-
优化: 重用对象/数组,或使用常量。
javascriptfunction processCoordsOptimized(coords) { for (const coord of coords) { // 直接访问属性,避免创建临时数组 console.log(`Processing point: (${coord.x}, ${coord.y})`); } } processCoordsOptimized([{x:1, y:2}, {x:3, y:4}]); // 如果默认选项是固定的,定义为常量 const DEFAULT_OPTIONS = Object.freeze({ theme: 'light', timeout: 5000 }); // Object.freeze 防止修改 function getDefaultOptionsOptimized() { return DEFAULT_OPTIONS; // 返回同一个常量对象的引用 } let optionsOpt1 = getDefaultOptionsOptimized(); let optionsOpt2 = getDefaultOptionsOptimized(); console.log(optionsOpt1 === optionsOpt2); // true // optionsOpt1.theme = 'dark'; // 在严格模式下会报错 TypeError,非严格模式下静默失败
-
解释: 在循环或频繁调用的函数中创建不必要的对象或数组会增加内存分配和垃圾回收的压力。如果数据是只读的或可以重用,尽量避免重复创建。使用常量存储固定的默认值。
-
六、 异步编程
-
回调地狱 (Callback Hell)
-
不良:
javascriptfunction asyncOperation1(data, callback) { console.log('Step 1 with', data); setTimeout(() => callback(null, data + '-step1'), 100); } function asyncOperation2(data, callback) { console.log('Step 2 with', data); setTimeout(() => callback(null, data + '-step2'), 100); } function asyncOperation3(data, callback) { console.log('Step 3 with', data); setTimeout(() => callback(null, data + '-step3'), 100); } asyncOperation1('start', function(err1, data1) { if (err1) { console.error(err1); return; } asyncOperation2(data1, function(err2, data2) { if (err2) { console.error(err2); return; } asyncOperation3(data2, function(err3, data3) { if (err3) { console.error(err3); return; } console.log('Callback Hell Final Result:', data3); // start-step1-step2-step3 // 嵌套越来越深... }); }); });
-
优化: 使用 Promises 或
async/await
。javascriptfunction asyncOperationPromise(data, stepName) { console.log(`Step ${stepName} with`, data); return new Promise((resolve, reject) => { setTimeout(() => { // 模拟可能发生的错误 if (Math.random() < 0.1) { reject(new Error(`Error in step ${stepName}`)); } else { resolve(data + `-step${stepName}`); } }, 100); }); } // 使用 Promise Chaining asyncOperationPromise('start', '1') .then(data1 => asyncOperationPromise(data1, '2')) .then(data2 => asyncOperationPromise(data2, '3')) .then(data3 => { console.log('Promise Chain Final Result:', data3); }) .catch(error => { console.error('Promise Chain Error:', error.message); }); // 使用 async/await (更推荐,更同步化) async function runAsyncOperations() { try { console.log("\n--- Running with async/await ---"); const data1 = await asyncOperationPromise('start-async', 'A'); const data2 = await asyncOperationPromise(data1, 'B'); const data3 = await asyncOperationPromise(data2, 'C'); console.log('Async/Await Final Result:', data3); } catch (error) { console.error('Async/Await Error:', error.message); } } // runAsyncOperations(); // 调用 async 函数
-
解释: 回调地狱导致代码难以阅读、理解和维护,错误处理也变得复杂。Promises 通过
.then()
链式调用和.catch()
统一错误处理,改善了结构。async/await
是基于 Promise 的语法糖,让异步代码看起来更像同步代码,可读性最高,是现代 JavaScript 中处理异步的首选方式。
-
-
未处理的 Promise Rejections
-
不良:
javascriptfunction mightReject() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() < 0.5) { reject(new Error("Something went wrong!")); } else { resolve("Success!"); } }, 100); }); } mightReject(); // 调用了 Promise,但没有 .catch() 或在 async 函数中 try...catch // 如果 Promise reject,会在控制台看到 "Uncaught (in promise) Error..." // 在 Node.js 环境中可能导致进程退出
-
优化: 始终为 Promise 添加
.catch()
或在async
函数中使用try...catch
。javascriptfunction mightRejectHandled() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() < 0.5) { reject(new Error("Something went wrong (handled)!")); } else { resolve("Success (handled)!"); } }, 100); }); } // 使用 .catch() mightRejectHandled() .then(result => console.log(".catch() scenario:", result)) .catch(error => console.error(".catch() scenario Error:", error.message)); // 使用 async/await async function handleRejectionAsync() { try { const result = await mightRejectHandled(); console.log("async/await scenario:", result); } catch (error) { console.error("async/await scenario Error:", error.message); } } // handleRejectionAsync();
-
解释: 未处理的 Promise rejection 是潜在的运行时错误,可能导致应用程序行为异常或崩溃。务必确保每个可能 reject 的 Promise 都有相应的错误处理逻辑。
-
-
在
async
函数中忘记使用await
-
不良:
javascriptasync function fetchData() { console.log("Fetching..."); // 忘记 await,fetchUser 返回的是 Promise,而不是用户数据 const userPromise = asyncOperationPromise('user-data', 'fetch'); // 后续代码可能期望 user 是实际数据,导致错误 // console.log(userPromise.name); // undefined or error, userPromise is a Promise console.log("Fetch call initiated, received:", userPromise); // 输出 Promise 对象 return userPromise; // 返回的是 Promise } // fetchData();
-
优化: 确保在调用返回 Promise 的函数时使用
await
(如果需要等待结果)。javascriptasync function fetchDataOptimized() { console.log("Fetching optimized..."); try { // 使用 await 等待 Promise resolve const user = await asyncOperationPromise('user-data-optimized', 'fetch'); console.log("Fetch complete, received user:", user); // 输出 'user-data-optimized-stepfetch' // 可以安全使用 user 数据 return user; } catch (error) { console.error("Fetch optimized error:", error.message); return null; // 或抛出错误 } } // fetchDataOptimized().then(data => console.log("Final fetched data:", data));
-
解释: 在
async
函数内部,调用另一个返回 Promise 的异步函数时,如果需要等待其结果才能继续执行,必须使用await
。否则,你将得到一个 Promise 对象,而不是它 resolve 后的值。
-
-
不必要地将同步代码包装在
async
函数中-
不良:
javascript// 这个函数本身没有任何异步操作 async function getSyncData(a, b) { const result = a + b; // 同步计算 console.log("Calculating sync data:", result); return result; // async 函数总是隐式返回 Promise } const dataPromise = getSyncData(5, 10); console.log("Returned value is Promise:", dataPromise instanceof Promise); // true dataPromise.then(data => console.log("Resolved sync data:", data)); // 15
-
优化: 如果函数是纯同步的,就定义为普通函数。
javascriptfunction getSyncDataOptimized(a, b) { const result = a + b; console.log("Calculating sync data optimized:", result); return result; // 直接返回结果 } const syncData = getSyncDataOptimized(5, 10); console.log("Returned value is number:", typeof syncData); // number console.log("Direct sync data:", syncData); // 15
-
解释:
async
关键字会使函数总是返回一个 Promise。如果函数内部完全是同步操作,使用async
会带来不必要的开销(创建 Promise 对象),并迫使调用者使用.then()
或await
来获取结果。
-
-
并行执行异步任务时使用串行
await
(如果任务间无依赖)-
不良:
javascriptasync function fetchMultipleSerial() { console.time("fetchMultipleSerial"); console.log("Fetching data 1..."); const data1 = await asyncOperationPromise('data1', 'fetch1'); // 等待 100ms console.log("Fetching data 2..."); const data2 = await asyncOperationPromise('data2', 'fetch2'); // 再等待 100ms console.log("Fetching data 3..."); const data3 = await asyncOperationPromise('data3', 'fetch3'); // 再等待 100ms console.timeEnd("fetchMultipleSerial"); // 总耗时约 300ms+ return [data1, data2, data3]; } // fetchMultipleSerial().then(results => console.log("Serial results:", results));
-
优化: 使用
Promise.all()
或Promise.allSettled()
并行执行。javascriptasync function fetchMultipleParallel() { console.time("fetchMultipleParallel"); console.log("Initiating parallel fetches..."); // 同时启动所有异步操作 const promise1 = asyncOperationPromise('data1-p', 'fetchP1'); const promise2 = asyncOperationPromise('data2-p', 'fetchP2'); const promise3 = asyncOperationPromise('data3-p', 'fetchP3'); try { // 等待所有 Promise 完成 // Promise.all: 如果有任何一个 reject,整个 Promise.all 会立即 reject const results = await Promise.all([promise1, promise2, promise3]); console.timeEnd("fetchMultipleParallel"); // 总耗时约 100ms+ (取决于最慢的那个) console.log("Parallel results (all):", results); return results; // 或者使用 Promise.allSettled: 等待所有 Promise 完成(无论成功或失败) // const settledResults = await Promise.allSettled([promise1, promise2, promise3]); // console.timeEnd("fetchMultipleParallel"); // console.log("Parallel results (allSettled):", settledResults); // // settledResults 是 [{status: 'fulfilled', value: ...}, {status: 'rejected', reason: ...}] 形式的数组 // return settledResults; } catch (error) { // 只有在使用 Promise.all 时才需要 catch 这里,allSettled 不会 reject console.error("Parallel fetch error (Promise.all):", error.message); console.timeEnd("fetchMultipleParallel"); return []; } } // fetchMultipleParallel();
-
解释: 如果多个异步任务之间没有依赖关系,可以并行执行以节省时间。使用
await
串行等待每个任务完成会不必要地增加总耗时。Promise.all()
接收一个 Promise 数组,并发启动它们,并在所有 Promise 都fulfilled
时返回结果数组(或在任何一个rejected
时立即reject
)。Promise.allSettled()
也是并发启动,但它总是等待所有 Promise 结束(无论成功或失败),并返回每个 Promise 的状态和结果/原因。
-
七、 DOM 操作 (前端相关)
-
频繁直接操作 DOM 导致多次重排/重绘
-
不良:
inifunction updateListBad(items) { const listElement = document.getElementById('my-list'); if (!listElement) return; listElement.innerHTML = ''; // 清空可能触发一次重排/重绘 items.forEach(item => { const li = document.createElement('li'); li.textContent = item.name; li.style.color = item.color; // 每次添加/修改样式都可能触发重排/重绘 li.style.fontWeight = 'bold'; listElement.appendChild(li); // 每次添加都可能触发重排/重绘 }); } // 假设页面有 <ul id="my-list"></ul> // updateListBad([{name: 'A', color: 'red'}, {name: 'B', color: 'blue'}]);
-
优化: 使用文档片段 (DocumentFragment) 或批量更新样式。
inifunction updateListOptimized(items) { const listElement = document.getElementById('my-list-optimized'); if (!listElement) return; // 使用 DocumentFragment 批量操作 const fragment = document.createDocumentFragment(); items.forEach(item => { const li = document.createElement('li'); li.textContent = item.name; // 批量设置样式 (例如通过 class,或者一次性设置 style.cssText) // li.style.color = item.color; // li.style.fontWeight = 'bold'; li.className = `list-item color-${item.color}`; // 通过 CSS 类控制样式更好 fragment.appendChild(li); // 添加到 fragment,不触发重排 }); // 一次性将 fragment 添加到 DOM listElement.innerHTML = ''; // 清空 listElement.appendChild(fragment); // 只触发一次重排/重绘 } // 假设页面有 <ul id="my-list-optimized"></ul> 和对应的 CSS // updateListOptimized([{name: 'Opt A', color: 'red'}, {name: 'Opt B', color: 'blue'}]);
-
解释: 浏览器对 DOM 的更改(特别是影响布局的更改,如添加/删除元素、改变尺寸/位置、修改
display
等)会触发重排 (reflow/layout),然后是重绘 (repaint)。重排是非常昂贵的操作。频繁触发会导致性能下降。使用DocumentFragment
作为一个临时的、轻量级的 DOM 容器,可以在其上进行多次 DOM 操作,最后一次性将其内容插入到实际 DOM 中,从而大大减少重排次数。同样,应尽量合并样式更改(如通过添加/删除 CSS 类,或一次性设置element.style.cssText
)。
-
-
滥用
innerHTML
插入不可信内容 (XSS 风险)-
不良:
inifunction displayComment(userInput) { const commentDiv = document.getElementById('comment-display'); if (!commentDiv) return; // 如果 userInput 包含 <script>alert('XSS')</script>,这会被执行! commentDiv.innerHTML = userInput; } // 假设页面有 <div id="comment-display"></div> // const maliciousInput = "Looks good! <script>alert('XSS Attack!');</script>"; // displayComment(maliciousInput);
-
优化: 使用
textContent
或createElement
+appendChild
。inifunction displayCommentSafe(userInput) { const commentDiv = document.getElementById('comment-display-safe'); if (!commentDiv) return; // 使用 textContent,浏览器不会解析 HTML 标签 commentDiv.textContent = userInput; // 或者,如果需要创建结构,使用 DOM API // const p = document.createElement('p'); // p.textContent = userInput; // 仍然使用 textContent 设置内容 // commentDiv.innerHTML = ''; // 清空 // commentDiv.appendChild(p); } // 假设页面有 <div id="comment-display-safe"></div> const maliciousInputSafe = "Looks good! <script>alert('XSS Attack!');</script>"; // displayCommentSafe(maliciousInputSafe); // 页面会显示字符串 "<script>alert('XSS Attack!');</script>"
-
解释:
innerHTML
会将指定的字符串解析为 HTML 并插入到 DOM 中。如果字符串来源于用户输入或其他不可信来源,恶意脚本可能会被注入并执行,导致跨站脚本攻击 (XSS)。应优先使用textContent
,它会将内容作为纯文本插入。如果必须基于用户输入创建 HTML 结构,需要进行严格的清理和转义,或者使用安全的模板引擎/框架。
-
-
未移除不再需要的事件监听器 (内存泄漏)
-
不良:
javascriptfunction setupTemporaryListener() { const button = document.getElementById('temp-button'); if (!button) return; const handleClick = () => { console.log('Temporary button clicked!'); // ... 做一些事情 ... // 忘记移除监听器 }; button.addEventListener('click', handleClick); // 假设这个按钮或其父元素稍后会被从 DOM 中移除 // 但 handleClick 函数(及其闭包)仍然被按钮引用, // 按钮又被浏览器的事件监听器机制引用,导致无法被垃圾回收。 } // setupTemporaryListener(); // 假设稍后执行: document.getElementById('temp-button')?.remove();
-
优化: 在元素销毁或不再需要监听时,使用
removeEventListener
移除监听器。javascriptfunction setupTemporaryListenerOptimized() { const button = document.getElementById('temp-button-optimized'); if (!button) return; // 必须保存对同一个函数实例的引用才能移除 const handleClick = () => { console.log('Temporary button clicked (optimized)!'); // ... 做一些事情 ... // 在这里或在其他清理逻辑中移除监听器 cleanupListener(); }; const cleanupListener = () => { console.log("Removing temporary listener..."); button.removeEventListener('click', handleClick); // 如果按钮本身也要移除,可以在移除前调用 cleanup }; button.addEventListener('click', handleClick); // 模拟稍后的清理操作 (例如组件卸载时) setTimeout(() => { // cleanupListener(); // 可以在这里移除 // document.getElementById('temp-button-optimized')?.remove(); // 移除元素前最好先移除监听器 }, 5000); // 返回一个清理函数,让调用者负责清理 (常见于 React/Vue 等框架) return cleanupListener; } // const cleanup = setupTemporaryListenerOptimized(); // // 在适当的时候调用 cleanup() // // window.addEventListener('unload', cleanup); // 例如页面卸载时
-
解释: 当 DOM 元素被移除时,如果它上面还绑定着事件监听器,而监听器函数又引用了其他对象(形成了闭包),这些对象可能无法被垃圾回收,导致内存泄漏。特别是在单页应用 (SPA) 中,组件频繁创建和销毁,这个问题尤为突出。务必在元素销毁前或不再需要监听时,使用
removeEventListener
移除对应的监听器。注意addEventListener
和removeEventListener
的第二个参数必须是同一个函数引用。
-
八、 其他
-
使用
eval()
或new Function()
执行动态代码 (安全风险和性能问题)-
不良:
iniconst codeString = "console.log('Executed via eval:', 2 + 2)"; // eval(codeString); // 执行任意字符串代码,非常危险! const funcString = "a, b", funcBody = "return a * b;"; // const multiply = new Function(funcString, funcBody); // console.log("Executed via new Function:", multiply(5, 6)); // 30
-
优化: 避免使用。寻找替代方案(如数据驱动、配置、安全的模板引擎)。
javascript// 如果需要根据配置执行不同逻辑 const operations = { add: (a, b) => a + b, multiply: (a, b) => a * b, }; const opName = 'multiply'; const arg1 = 5, arg2 = 6; if (operations[opName]) { console.log("Executing via lookup:", operations[opName](arg1, arg2)); // 30 } else { console.error("Unknown operation:", opName); }
-
解释:
eval()
和new Function()
可以执行包含在字符串中的任意 JavaScript 代码。这带来了巨大的安全风险,因为恶意代码可能被执行(特别是当字符串来自外部输入时)。此外,它们通常会阻止 JavaScript 引擎的优化,导致性能下降。绝大多数情况下都应该避免使用它们。
-
-
忽略错误处理 (
try...catch
中捕获但不处理)-
不良:
javascripttry { const data = JSON.parse("{ invalid json "); console.log("Parsed data:", data); } catch (error) { // 捕获了错误,但什么也没做,错误被"吞掉"了 // console.log("An error occurred, but we ignored it."); } console.log("Program continues silently...");
-
优化: 记录错误、向用户显示消息或执行适当的回退/恢复逻辑。
javascripttry { const data = JSON.parse("{ invalid json "); console.log("Parsed data (optimized):", data); } catch (error) { // 至少记录错误 console.error("Failed to parse JSON:", error.message); // 可以向用户显示友好的错误提示 // showErrorMessage("Sorry, there was an issue processing data."); // 或者执行备用逻辑 // useDefaultData(); } console.log("Program continues after handling error...");
-
解释: 捕获错误但完全不处理(空的
catch
块)会导致问题被隐藏,使调试变得极其困难。至少应该记录错误信息,以便追踪问题。根据情况,可能还需要通知用户或尝试从错误中恢复。
-
-
在代码中留下
console.log
或debugger
语句 (生产环境)-
不良:
javascriptfunction calculateComplexValue(input) { console.log("Debugging input:", input); // 用于调试,忘记删除 let result = input * 2; // debugger; // 用于断点调试,忘记删除 result += 5; console.log("Final result:", result); // 可能也是调试信息 return result; } calculateComplexValue(10);
-
优化: 使用构建工具 (如 Terser, Babel 插件) 移除,或使用条件日志。
inifunction calculateComplexValueOptimized(input) { // 使用条件日志,只在开发环境输出 if (process.env.NODE_ENV === 'development') { console.log("Dev Debug: Input is", input); } let result = input * 2; result += 5; // 生产构建时,构建工具会自动移除 console.log 和 debugger // 或者使用专门的日志库 (如 Winston, Pino, debug) return result; } // 需要配置 process.env.NODE_ENV (通常由 Webpack/Vite 等设置) // process.env.NODE_ENV = 'production'; // 模拟生产环境 calculateComplexValueOptimized(10);
-
解释:
console.log
输出可能暴露敏感信息或干扰控制台。debugger
语句会导致代码在浏览器开发者工具打开时暂停执行。这些调试语句应在提交到版本控制或部署到生产环境之前移除。使用构建工具自动化移除是最佳实践。
-
-
不写注释或写无用的注释
-
不良:
ini// i加1 (无用的注释,代码本身很明显) i++; // 复杂且没有注释的逻辑 const magicValue = (x << 3) ^ (y >> 1) | z;
-
优化: 为复杂的逻辑、重要的决策或"为什么"这样写添加注释。
scss// 计数器加 1 (如果变量名不清晰,注释可能有帮助,但最好是改名) retryCount++; // 使用位运算优化性能,计算哈希值 (解释了"为什么"和"做什么") // x << 3: 快速乘以 8 // y >> 1: 快速除以 2 (取整) // ^: 异或操作 // |: 或操作 const hashCode = (x << 3) ^ (y >> 1) | z;
-
解释: 代码应该尽可能自解释(通过好的命名和结构)。注释不应解释"代码做了什么"(除非代码非常晦涩),而应解释"为什么这样做"、业务逻辑背景、重要的假设或需要注意的陷阱。
-
-
代码格式不一致
-
不良: (混合使用缩进、空格、括号风格等)
cssfunction badFormat (a,b){ if(a>b){ console.log( a); } else { console.log(b) ;} var c=a+b; return c ; }
-
优化: 使用 Prettier、ESLint 等工具强制执行统一的代码风格。
css// 使用 Prettier 格式化后 (示例) function goodFormat(a, b) { if (a > b) { console.log(a); } else { console.log(b); } const c = a + b; return c; }
-
解释: 不一致的代码格式降低了可读性,增加了团队协作的难度。使用自动化格式化工具 (Prettier) 和代码检查工具 (ESLint) 可以轻松保持代码风格统一。
-
... (为了达到数量和篇幅,继续补充更多示例)
-
对
null
或undefined
的属性进行深层访问 (导致 TypeError)- 不良:
const cityName = user.address.city;
(如果user
或user.address
是null
或undefined
则报错) - 优化: 使用可选链 (
?.
) 和/或空值合并 (??
)。const cityName = user?.address?.city ?? 'Unknown';
- 不良:
-
布尔值判断过于冗余
- 不良:
if (isValid === true)
或if (isValid === false)
- 优化: 直接使用布尔值
if (isValid)
或if (!isValid)
- 不良:
-
使用
Array
构造函数创建数组 (除非指定长度)- 不良:
const arr = new Array(1, 2, 3);
(行为同[1, 2, 3]
) 或const single = new Array(5);
(创建长度为 5 的稀疏数组) - 优化: 使用数组字面量
const arr = [1, 2, 3];
。如果需要预设长度的空数组,Array(5)
可以接受,但注意它是稀疏的。Array.from({ length: 5 })
创建非稀疏数组。
- 不良:
-
使用
Object
构造函数创建空对象- 不良:
const obj = new Object();
- 优化: 使用对象字面量
const obj = {};
- 不良:
-
不必要的字符串包装对象
- 不良:
const strObj = new String("hello");
- 优化: 使用字符串字面量
const str = "hello";
- 不良:
-
在
switch
语句中忘记break
(导致 case 穿透)-
不良:
typescriptlet type = 1; switch (type) { case 1: console.log("Type 1"); // 输出 // 忘记 break case 2: console.log("Type 2"); // 也会输出! break; default: console.log("Default"); }
-
优化: 确保每个
case
结束时有break
(除非有意利用穿透,并加注释)。arduinolet typeOpt = 1; switch (typeOpt) { case 1: console.log("Type 1 Opt"); break; // 添加 break case 2: console.log("Type 2 Opt"); break; default: console.log("Default Opt"); }
-
-
使用
with
语句 (已被废弃,有性能和作用域问题)- 不良:
with (myObject) { prop1 = 1; prop2 = 2; }
- 优化: 明确访问对象属性
myObject.prop1 = 1; myObject.prop2 = 2;
或使用解构赋值(如果适用)。
- 不良:
-
修改内置对象的原型 (如
Array.prototype
,Object.prototype
)- 不良:
Array.prototype.last = function() { return this[this.length - 1]; };
(可能与未来 JS 标准或第三方库冲突) - 优化: 创建独立的工具函数
function getLastElement(arr) { return arr[arr.length - 1]; }
或使用子类化。
- 不良:
-
在
try...catch...finally
中,finally
里的return
会覆盖try
或catch
中的return
-
不良:
javascriptfunction testFinallyReturn() { try { console.log("Try block"); return "from try"; // 这个返回值会被 finally 覆盖 } catch (e) { console.log("Catch block"); return "from catch"; } finally { console.log("Finally block"); return "from finally"; // 最终返回这个值 } } console.log(testFinallyReturn()); // 输出 "from finally"
-
优化: 不要在
finally
中使用return
(除非是刻意为之的特殊逻辑)。finally
主要用于清理资源。
-
-
创建不必要的闭包 (尤其在循环中)
-
不良:
javascriptfunction createHandlersBad() { const elements = document.querySelectorAll('.my-elements'); for (let i = 0; i < elements.length; i++) { const element = elements[i]; const data = element.dataset.value; // 假设有 data-value // 每次循环都创建一个新的 handleClick 函数闭包,即使处理逻辑相同 element.addEventListener('click', function handleClick() { console.log(`Clicked element ${i} with data: ${data}`); }); } }
-
优化: 将事件处理函数移到循环外,使用事件委托或通过
event.target
获取数据。javascriptfunction handleClickOptimized(event) { // 使用事件委托时,event.target 是实际点击的元素 // const targetElement = event.target.closest('.my-element'); // 找到目标元素 // if (!targetElement) return; // const data = targetElement.dataset.value; // const index = Array.from(targetElement.parentNode.children).indexOf(targetElement); // 获取索引较麻烦 // 如果不用事件委托,只是提取函数 // 需要一种方式将 data 和 i 传递进来,或者在函数内部获取 // 例如,如果函数在循环外定义,可以通过 bind 传参,但这又创建了新函数 console.log(`Clicked element, data: ${this.dataset.value}`); // 使用 this (需要确保 this 指向 element) // 或者在 addEventListener 时使用 bind } function createHandlersOptimized() { const elements = document.querySelectorAll('.my-elements-optimized'); for (let i = 0; i < elements.length; i++) { const element = elements[i]; // 绑定同一个函数引用,但需要处理数据传递问题 // element.addEventListener('click', handleClickOptimized.bind(element)); // bind 会创建新函数,但比内联函数稍好 // 更好的方式通常是事件委托 } // 事件委托方式 const container = document.getElementById('container'); if (container) { container.addEventListener('click', function(event) { const targetElement = event.target.closest('.my-element-delegated'); if (!targetElement) return; const data = targetElement.dataset.value; console.log(`Delegated click on element with data: ${data}`); }); } }
-
解释: 在循环中为每个元素创建内联事件处理函数会生成大量闭包,占用更多内存。如果处理逻辑相同,最好将函数定义在循环外部。事件委托是更优的方式,将监听器添加到父元素,利用事件冒泡来处理子元素的事件,只需一个监听器。
-
-
在 JavaScript 中模拟块级作用域 (ES6 之前使用 IIFE)
-
不良: (ES5 及之前)
ini(function() { var blockScopedVar = 'I am kind of block scoped'; console.log(blockScopedVar); }()); // console.log(blockScopedVar); // ReferenceError
-
优化: (ES6+) 直接使用
let
和const
以及{}
块。ini{ let blockScopedVar = 'I am truly block scoped'; const anotherBlockVar = true; console.log(blockScopedVar, anotherBlockVar); } // console.log(blockScopedVar); // ReferenceError
-
解释: 在 ES6 之前,只能通过立即执行函数表达式 (IIFE) 来模拟块级作用域。ES6 引入了
let
和const
,它们本身就具有块级作用域,使得代码更简洁、更自然。
-
-
字符串拼接性能问题 (大量拼接时)
-
不良:
inilet longString = ''; const iterations = 10000; console.time('stringConcat'); for (let i = 0; i < iterations; i++) { longString += 'Part ' + i + '; '; // 每次 + 都会创建新字符串 } console.timeEnd('stringConcat'); // console.log(longString.length);
-
优化: 使用数组
join('')
或模板字面量 (如果结构简单)。iniconst parts = []; const iterationsOpt = 10000; console.time('arrayJoin'); for (let i = 0; i < iterationsOpt; i++) { parts.push('Part '); parts.push(i); parts.push('; '); } const longStringOptimized = parts.join(''); console.timeEnd('arrayJoin'); // console.log(longStringOptimized.length); // 对于简单拼接,模板字面量可读性好,性能通常也不错 // let str = ''; // for (let i = 0; i < 10; i++) { // str += `Item ${i}\n`; // }
-
解释: 在旧的 JavaScript 引擎中,使用
+
或+=
进行大量字符串拼接性能较差,因为字符串是不可变的,每次拼接都会创建新的中间字符串。将各部分添加到数组中,最后使用join('')
一次性合并通常更快。现代 JS 引擎对字符串拼接做了很多优化,性能差异可能不再那么显著,但join
对于构建非常长的字符串仍然是一个可靠的选择。模板字面量在可读性和性能之间取得了很好的平衡。
-
-
使用
setTimeout
或setInterval
传递字符串代码 (类似eval
)- 不良:
setTimeout("console.log('Delayed eval-like execution')", 1000);
- 优化: 传递函数引用或箭头函数。
setTimeout(() => console.log('Delayed safe execution'), 1000);
或setTimeout(myFunction, 1000);
- 不良:
-
依赖
Date
对象的构造函数解析字符串 (行为不一致)- 不良:
new Date('2023-10-26')
(结果可能因浏览器/时区而异,特别是没有指定时间时)。 - 优化: 使用 ISO 8601 格式 (带时区或 UTC)
new Date('2023-10-26T00:00:00Z')
或使用可靠的日期库 (如 date-fns, Moment.js - 后者已不推荐新项目使用)。
- 不良:
-
在
Array.prototype.map
或filter
中执行副作用-
不良:
iniconst ids = [1, 2, 3]; let sideEffectCounter = 0; const processed = ids.map(id => { console.log(`Processing id ${id}`); // 副作用:日志 sideEffectCounter++; // 副作用:修改外部变量 return { id: id, processed: true }; });
-
优化:
map
应用于转换数据,副作用应使用forEach
或分离。iniconst idsOpt = [1, 2, 3]; let sideEffectCounterOpt = 0; // 使用 forEach 处理副作用 idsOpt.forEach(id => { console.log(`Processing id ${id} (forEach)`); sideEffectCounterOpt++; }); // 使用 map 进行纯粹的转换 const processedOpt = idsOpt.map(id => ({ id: id, processed: true })); console.log(processedOpt); console.log("Side effect counter:", sideEffectCounterOpt);
-
解释:
map
的设计目的是根据原数组创建一个新的 转换后的数组,它应该是一个纯函数操作。在map
回调中执行副作用(如修改外部变量、DOM 操作、网络请求)会违反其设计意图,使代码更难理解和测试。如果需要遍历并执行副作用,应使用forEach
。
-
... 继续努力接近目标 ...
-
不使用
Array.isArray()
判断数组- 不良:
if (typeof myArray === 'object' && myArray !== null && myArray.hasOwnProperty('length'))
(不准确,很多对象也有 length) 或if (myArray instanceof Array)
(跨 frame/realm 可能失效) - 优化:
if (Array.isArray(myArray))
- 不良:
-
对非数字使用位运算 (可能产生意外结果)
- 不良:
const intVal = ~~"123.45";
(虽然可以取整,但可读性差,且对非数字字符串结果为 0)console.log(~~"abc"); // 0
- 优化: 使用
Math.floor()
,Math.trunc()
,parseInt()
。const intValOpt = Math.trunc(Number("123.45"));
- 不良:
-
使用
toFixed()
后期望得到数字类型- 不良:
const num = 123.456; const fixed = num.toFixed(2); console.log(typeof fixed); // "string"
- 优化: 如果需要数字,用
parseFloat()
或Number()
转换回来。const fixedNum = parseFloat(num.toFixed(2));
或const fixedNum = Number(num.toFixed(2));
- 不良:
-
在
JSON.stringify
中丢失undefined
, 函数, Symbol 值- 不良:
const obj = { a: 1, b: undefined, c: function(){}, d: Symbol('s') }; console.log(JSON.stringify(obj)); // "{"a":1}"
- 优化: 了解此行为,如有需要,在序列化前进行预处理或使用支持这些类型的库。
- 不良:
-
在
JSON.parse
前不进行错误处理- 不良:
const data = JSON.parse(invalidJsonString);
(如果字符串无效会抛错) - 优化: 使用
try...catch
包裹JSON.parse
。
- 不良:
-
正则表达式不转义特殊字符
- 不良:
const userInput = "1. Item"; const regex = new RegExp(userInput);
(如果 userInput 含特殊字符如.
会按正则含义解析) - 优化: 对用户输入或动态构建的正则部分进行转义。
function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[]\]/g, '\$&'); } const regexOpt = new RegExp(escapeRegExp(userInput));
- 不良:
-
创建不必要的正则表达式对象 (在循环内)
- 不良:
for (const str of strings) { const matches = str.match(/pattern/g); /* 每次循环都创建正则对象 */ }
- 优化: 在循环外创建一次正则表达式对象。
const regex = /pattern/g; for (const str of strings) { const matches = str.match(regex); }
- 不良:
-
未使用
const
声明不会被重新赋值的变量- 不良:
let PI = 3.14159; let MAX_USERS = 100;
(变量意图是常量,但用了let
) - 优化: 使用
const
声明常量。const PI = 3.14159; const MAX_USERS = 100;
- 不良:
-
链式赋值可读性差
- 不良:
let a = b = c = 0;
- 优化: 分开赋值。
let a = 0; let b = 0; let c = 0;
(或const
如果适用)
- 不良:
-
使用
void
操作符产生undefined
(除非刻意)- 不良:
const result = void (a + b);
(结果是undefined
,可读性差) - 优化: 直接使用
undefined
字面量。let result = undefined;
- 不良:
-
依赖函数声明提升的行为
-
不良:
scsshoistedFunc(); // 可以调用,因为函数声明被提升了 function hoistedFunc() { console.log("Hoisted!"); }
-
优化: 先声明后使用,提高可读性。
scssfunction definedFirst() { console.log("Defined First!"); } definedFirst(); // 调用在声明之后
-
解释: 虽然函数声明提升是 JavaScript 的特性,但依赖它会降低代码从上到下的可读性。函数表达式(
const myFunc = function() {}
或const myFunc = () => {}
)则不会提升函数体。
-
-
使用
document.write
(阻塞渲染,可能覆盖页面)- 不良:
document.write("<h1>Hello</h1>");
- 优化: 使用 DOM 操作 API (
createElement
,appendChild
,textContent
等)。
- 不良:
-
在非构造函数上使用
new
- 不良:
const result = new Math.max(1, 2);
(TypeError: Math.max is not a constructor) - 优化: 直接调用函数
const result = Math.max(1, 2);
- 不良:
-
不使用严格模式 (
'use strict';
)- 不良: (省略
'use strict';
) 导致静默错误、不安全的特性可用。 - 优化: 在脚本或函数顶部添加
'use strict';
。现代模块默认是严格模式。
- 不良: (省略
-
对象属性或方法名使用保留字 (可能需要引号)
- 不良:
const obj = { try: 1, catch: 2 };
(在旧浏览器或某些情况下可能出错) - 优化: 使用引号
const obj = { 'try': 1, 'catch': 2 };
或避免使用保留字。
- 不良:
-
对
document.querySelectorAll
的结果期望是实时更新的NodeList
- 不良:
const list = document.querySelectorAll('.item'); /* 添加新 .item 元素 */; console.log(list.length);
(长度不变,它是静态列表) - 优化: 了解
querySelectorAll
返回静态列表。如果需要实时列表,使用getElementsByClassName
或getElementsByTagName
(返回HTMLCollection
),或者在需要时重新查询。
- 不良:
-
在
map
,filter
,reduce
中忘记return
- 不良:
const doubled = [1, 2, 3].map(n => { n * 2; /* 忘记 return */ }); console.log(doubled); // [undefined, undefined, undefined]
- 优化: 确保回调函数返回值。
const doubledOpt = [1, 2, 3].map(n => n * 2);
或const doubledOpt = [1, 2, 3].map(n => { return n * 2; });
- 不良:
-
reduce
方法不提供初始值 (当数组可能为空时)- 不良:
const emptyArr = []; const sum = emptyArr.reduce((acc, val) => acc + val);
(TypeError: Reduce of empty array with no initial value) - 优化: 提供初始值。
const sumOpt = emptyArr.reduce((acc, val) => acc + val, 0); console.log(sumOpt); // 0
- 不良:
-
在异步回调中修改循环变量 (ES5
var
陷阱的变种)-
不良: (类似 #1)
cssfunction processItemsVar(items) { for (var i = 0; i < items.length; i++) { // 假设 asyncOperation 是异步的 // asyncOperation(items[i], function(result) { // console.log(`Processed item ${i}: ${result}`); // i 始终是循环结束后的值 // }); } }
-
优化: 使用
let
或将i
传入异步回调 (通过闭包或参数)。javascriptfunction processItemsLet(items) { for (let i = 0; i < items.length; i++) { // let 创建块作用域 // asyncOperation(items[i], function(result) { // console.log(`Processed item ${i}: ${result}`); // i 是当前循环的值 // }); } // 或者 // items.forEach((item, i) => { // asyncOperation(item, (result) => { // console.log(`Processed item ${i}: ${result}`); // }); // }); }
-
-
不必要的
Promise.resolve()
或Promise.reject()
- 不良:
async function getData() { return Promise.resolve(syncValue); }
或return Promise.reject(new Error('...'));
- 优化:
async
函数自动包装返回值async function getDataOpt() { return syncValue; }
。对于 reject,直接throw new Error('...');
- 不良:
-
在
Promise.all
中某个 Promise reject 但仍期望获取其他成功结果- 不良: (如 #27 所示,
Promise.all
会快速失败) - 优化: 使用
Promise.allSettled()
。
- 不良: (如 #27 所示,
-
将
async
函数作为事件监听器或回调,但不处理其返回的 Promise- 不良:
button.addEventListener('click', async () => { await mightReject(); /* 未处理 rejection */ });
- 优化: 在
async
回调内部使用try...catch
。button.addEventListener('click', async () => { try { await mightReject(); } catch (e) { console.error(e); } });
- 不良:
-
使用
substr
(可能被废弃)- 不良:
str.substr(startIndex, length)
- 优化: 使用
substring(startIndex, endIndex)
或slice(startIndex, endIndex)
。
- 不良:
-
浮点数精度问题未处理
- 不良:
console.log(0.1 + 0.2); // 0.30000000000000004
if (0.1 + 0.2 === 0.3) { /* false */ }
- 优化: 比较时设置一个小的容差 (epsilon),或将浮点数转为整数计算再转回,或使用 Decimal 库。
const epsilon = 1e-10; if (Math.abs((0.1 + 0.2) - 0.3) < epsilon) { console.log("Approximately equal"); }
- 不良:
-
不缓存 DOM 查询结果 (在循环或多次使用时)
- 不良:
for (let i=0; i<10; i++) { document.getElementById('myElement').style.opacity = i / 10; /* 每次都查询 */ }
- 优化: 查询一次并缓存结果。
const element = document.getElementById('myElement'); if (element) { for (let i=0; i<10; i++) { element.style.opacity = i / 10; } }
- 不良:
-
使用
setAttribute
设置style
(不如直接访问style
对象)- 不良:
element.setAttribute('style', 'color: red; font-weight: bold;');
(会覆盖所有现有内联样式) - 优化:
element.style.color = 'red'; element.style.fontWeight = 'bold';
或element.style.cssText += '; color: red;';
(追加) 或使用 class。
- 不良:
-
事件监听器中进行耗时操作阻塞主线程
- 不良:
button.addEventListener('click', () => { for (let i=0; i<1e9; i++) {} /* 长时间计算 */; console.log('Done'); /* 界面卡顿 */ });
- 优化: 使用 Web Workers 处理耗时计算,或将任务拆分成小块使用
setTimeout
或requestAnimationFrame
。
- 不良:
-
在
window.onscroll
或window.onresize
中执行高频操作,未使用节流或防抖- 不良:
window.onscroll = () => { console.log('Scrolled!', window.scrollY); /* 高频触发 */ };
- 优化: 使用节流 (throttle) 或防抖 (debounce) 函数包装事件处理程序。 (需要引入 lodash/debounce 或自行实现)
- 不良:
-
未正确处理
this
的箭头函数 (当需要动态this
时)-
不良: (在对象方法中,如果需要访问调用上下文的
this
)iniconst counter = { count: 0, // 箭头函数 this 指向外层作用域 (可能是 window 或 undefined) increment: () => { // this.count++; // TypeError or increments global count if exists // console.log(this); } }; // counter.increment();
-
优化: 使用普通函数作为对象方法。
javascriptconst counterOpt = { count: 0, increment: function() { // this 指向 counterOpt this.count++; console.log("Counter incremented:", this.count); } // 或者使用 ES6 方法简写 // increment() { this.count++; console.log(this.count); } }; counterOpt.increment(); // Counter incremented: 1
-
-
过度使用解构赋值使代码难以理解
- 不良:
const { a: { b: [{ c: d }] }, e: [f,,g] } = complexNestedObject;
(如果层级很深或命名随意) - 优化: 分步解构或使用更清晰的变量名。
- 不良:
-
将同步代码错误地放入
Promise
构造函数- 不良:
new Promise((resolve) => { const result = 1 + 1; /* 同步操作 */ resolve(result); });
(可以用Promise.resolve(1 + 1)
代替) - 优化:
Promise
构造函数用于包装异步 操作。同步值直接用Promise.resolve()
。
- 不良:
-
不必要的
await
(对非 Promise 值)- 不良:
async function example() { const syncValue = 10; const result = await syncValue; /* await 对 10 无效 */ return result; }
- 优化: 直接返回值
async function exampleOpt() { const syncValue = 10; return syncValue; }
- 不良:
-
使用
async/await
但忘记函数声明为async
- 不良:
function fetchData() { const data = await fetch('/api'); /* SyntaxError: await is only valid in async functions */ }
- 优化: 添加
async
关键字async function fetchDataOpt() { const response = await fetch('/api'); ... }
- 不良:
-
条件语句中复杂的布尔逻辑,未使用变量或函数封装
- 不良:
if ((user.isAdmin && user.isActive && !user.isSuspended) || (user.isSupport && settings.allowSupportOverride))
- 优化: 提取为具名变量或函数。
const canPerformAction = isAdminUser(user) || isSupportOverride(user, settings); if (canPerformAction) { ... }
- 不良:
-
在
finally
块中修改可能在try
或catch
中设置的变量,意图影响返回值 (见 #44) -
使用
for
循环反向迭代时索引处理不当- 不良:
for (let i = arr.length; i >= 0; i--) { console.log(arr[i]); /* i=arr.length 时越界 */ }
- 优化:
for (let i = arr.length - 1; i >= 0; i--) { console.log(arr[i]); }
- 不良:
-
依赖对象属性的插入顺序 (ES2015 后部分情况有序,但不应完全依赖)
- 不良: 期望
for...in
或Object.keys
总是按插入顺序返回键 (仅对非负整数键按升序,其他字符串键按插入顺序,Symbol 键最后)。 - 优化: 如果需要严格顺序,使用
Map
或将键存储在数组中按数组顺序访问。
- 不良: 期望
-
将
null
和undefined
视为完全相同 (虽然==
认为它们相等)- 不良: 在逻辑判断中不区分它们,可能导致问题 (例如
null
可能表示"值确实为空",undefined
可能表示"值未定义/未提供")。 - 优化: 根据需要使用
=== null
或=== undefined
进行精确判断,或使用value == null
同时检查两者。
- 不良: 在逻辑判断中不区分它们,可能导致问题 (例如
-
不使用可选链 (
?.
) 处理可能不存在的方法调用- 不良:
if (callback) { callback(); }
- 优化:
callback?.();
- 不良:
-
不使用空值合并 (
??
) 提供默认值 (与||
的区别)- 不良:
const timeout = options.timeout || 5000;
(如果options.timeout
是0
或false
,也会被替换为 5000) - 优化:
const timeout = options.timeout ?? 5000;
(??
只在左侧是null
或undefined
时才使用右侧的值)
- 不良:
-
在类方法中使用箭头函数,导致无法被子类正确覆盖或
super
调用-
不良:
scalaclass Parent { myMethod = () => { // 箭头函数绑定在实例上 console.log("Parent method"); } } class Child extends Parent { // 尝试覆盖,但实际上是定义了另一个实例属性 myMethod = () => { console.log("Child method"); // super.myMethod(); // TypeError: super.myMethod is not a function (箭头函数不在原型上) } } // const c = new Child(); // c.myMethod();
-
优化: 使用普通方法定义在原型上。
scalaclass ParentOpt { myMethod() { // 定义在原型上 console.log("Parent method Opt"); } } class ChildOpt extends ParentOpt { myMethod() { console.log("Child method Opt"); super.myMethod(); // 可以正确调用父类方法 } } const cOpt = new ChildOpt(); cOpt.myMethod();
-
解释: 类字段中的箭头函数会作为实例自身的属性,而不是原型上的方法。这使得子类无法通过原型链正确覆盖它,也无法使用
super
调用父类的同名方法。如果需要继承和覆盖,应使用标准的类方法语法。
-
-
手动实现
debounce
或throttle
而不使用可靠的库 (容易出错)- 不良: (自行实现复杂的防抖/节流逻辑,可能未处理边缘情况)
- 优化: 使用 Lodash (
_.debounce
,_.throttle
) 或其他成熟库,或者仔细测试自己实现的版本。
-
正则表达式缺乏注释解释复杂模式
-
不良:
const regex = /^([a-z0-9_.-]+)@([\da-z.-]+).([a-z.]{2,6})$/;
(没有注释) -
优化: 添加注释解释各部分含义。
javascript// 匹配邮箱地址 const emailRegex = new RegExp( '^([a-z0-9_\.-]+)' + // 用户名部分: 字母、数字、下划线、点、连字符 '@' + // @ 符号 '([\da-z\.-]+)' + // 域名部分: 数字、字母、点、连字符 '\.' + // 点 . '([a-z\.]{2,6})$' // 顶级域名: 2-6个字母或点 );
-
-
在
Promise
构造函数中忘记调用resolve
或reject
(导致 Promise 永远 pending)- 不良:
new Promise((resolve, reject) => { setTimeout(() => { console.log("Done"); /* 忘记 resolve() */ }, 100); });
- 优化: 确保异步操作完成后调用
resolve
或reject
。
- 不良:
-
使用
== true
或== false
以外的布尔比较 (如== 1
,== 0
)- 不良:
if (count == 1)
(如果只想判断是否为 true/false 意图,这会误导) - 优化: 使用
=== true
/=== false
或直接if (booleanValue)
/if (!booleanValue)
。
- 不良:
-
在
Array.sort()
中提供不返回-1
,0
,1
(或负数/零/正数) 的比较函数- 不良:
arr.sort((a, b) => a > b);
(返回布尔值,排序结果不可靠) - 优化:
arr.sort((a, b) => a - b);
(升序数字) 或arr.sort((a, b) => a.localeCompare(b));
(字符串)
- 不良:
-
过度使用全局状态管理库 (如 Redux) 处理本地组件状态
- 不良: 将所有组件内部的临时状态(如表单输入、开关状态)都放入全局 Store。
- 优化: 使用组件本地状态 (
useState
) 管理仅与该组件相关的状态,仅将真正需要跨组件共享或持久化的状态放入全局 Store。
-
不使用
?.
和??
导致冗长的空值检查- 不良:
const value = data && data.payload && data.payload.items ? data.payload.items[0] : 'default';
- 优化:
const value = data?.payload?.items?.[0] ?? 'default';
- 不良:
-
在循环中修改被迭代的集合 (如使用
splice
时未正确调整索引)-
不良:
iniconst nums = [1, 2, 3, 4, 5]; for (let i = 0; i < nums.length; i++) { if (nums[i] % 2 === 0) { nums.splice(i, 1); // 删除元素后,后续元素前移,导致跳过检查 // i--; // 需要手动调整索引,容易出错 } } console.log("Incorrect removal:", nums); // 可能不是 [1, 3, 5]
-
优化: 反向迭代或使用
filter
创建新数组。iniconst numsOpt = [1, 2, 3, 4, 5]; for (let i = numsOpt.length - 1; i >= 0; i--) { // 反向迭代 if (numsOpt[i] % 2 === 0) { numsOpt.splice(i, 1); } } console.log("Reverse removal:", numsOpt); // [1, 3, 5] const numsFilterOpt = [1, 2, 3, 4, 5]; const oddNums = numsFilterOpt.filter(n => n % 2 !== 0); // filter 更简洁且不可变 console.log("Filter removal:", oddNums); // [1, 3, 5]
-
-
代码重复,未抽取可重用函数或组件
- 不良: (在多处复制粘贴相似的代码块)
- 优化: 将重复逻辑封装到函数、类或组件中,进行调用。遵循 DRY (Don't Repeat Yourself) 原则。