讲解
学完语法,最后沉淀一批跨越具体知识点的工程习惯。这些约定来自社区长期踩坑的共识,本身没有强制性,但遵守它们的代码 bug 更少、协作更顺。第一部分是「默认安全」:默认 const,需要重新赋值才 let,永不 var;相等永远 === 和 !==;函数参数做边界校验,异常尽早抛出;不写隐式类型转换的「聪明代码」,显式调用 Number/String/Boolean。
第二部分是「结构清晰」:函数保持短小,一个函数只做一件事,超过一屏就该拆分;用提前返回代替深层嵌套,让主逻辑保持在最浅缩进;变量和函数名见名知意——布尔用 is/has/can 开头,函数用动词开头(fetchUser、formatPrice),常量全大写;纯函数优先(相同输入永远相同输出、无副作用),它们可预测、可测试;魔法数字提取成有名字的常量。
第三部分是「健壮与协作」:外部数据(接口返回、用户输入、localStorage)一律不可信,解析加 try/catch、使用前先校验;不静默吞错,catch 里至少记日志;代码格式化交给 Prettier、静态检查交给 ESLint,把风格争议交给工具;写注释解释「为什么」而不是复述「做什么」。这些习惯单条看收益有限,组合起来就是专业与业余的分水岭。
示例
// 反例 vs 正例:同一个函数两种写法
// 反例:嵌套深、魔法数字、隐式转换、无校验
function getPriceBad(user, item) {
if (user) {
if (item) {
if (user.level == 2) {
return item.price * 0.8;
} else {
return item.price;
}
}
}
}
// 正例:提前返回、命名常量、显式校验、纯函数
const VIP_LEVEL = 2;
const VIP_DISCOUNT = 0.8;
function getPrice(user, item) {
if (!user || !item) throw new TypeError('user 和 item 必填');
if (typeof item.price !== 'number' || item.price < 0) {
throw new RangeError('价格必须是非负数字');
}
const isVip = user.level >= VIP_LEVEL;
return isVip ? item.price * VIP_DISCOUNT : item.price;
}
console.log(getPrice({ level: 3 }, { price: 100 }));
console.log(getPrice({ level: 1 }, { price: 100 }));
// 外部数据防御性处理
function parseConfig(raw) {
try {
const config = JSON.parse(raw);
return {
theme: config.theme === 'dark' ? 'dark' : 'light', // 白名单收敛
fontSize: Number.isFinite(config.fontSize) ? config.fontSize : 14,
};
} catch {
return { theme: 'light', fontSize: 14 }; // 兜底默认值
}
}
console.log(parseConfig('{"theme":"dark","fontSize":18}'));
console.log(parseConfig('坏数据'));
// 纯函数:好测试、可预测
const addTag = (tags, tag) => (tags.includes(tag) ? tags : [...tags, tag]);
console.log(addTag(['js'], 'web'), addTag(['js'], 'js'));
常见坑
- 过早抽象:三行重复代码就急着抽函数、上设计模式。规则是「第三次重复再抽象」,错误的抽象比重复更难维护。
- 注释复述代码:i++ // i 加一 是噪音。注释写意图、写坑、写为什么不那样做。
- 一行流炫技:把五步链式调用塞成一行的「聪明」代码,三个月后没人敢动。可读性优先于行数。
- 依赖口头约定:风格靠人盯总会失守,Prettier/ESLint 进 CI 才能强制执行,把争论留给工具。
小结
默认 const 与 ===、小函数早返回、见名知意、纯函数优先、外部数据不可信、不吞错、风格交给工具。下一章看看 ES2022 之后标准又添了哪些好用的新特性。