好的代码不仅是在性能、体积、内存,更要 code for humans
。
我们知道代码被人阅读的难度远胜于引擎,要写出好的代码需要脱离自己的视角,以他人的眼光审视,重新理解上下文含义,此时代码的架构、拆分、组合、技巧则给予人阅读的幸福感,我们致力于构建优秀的代码意味着不仅以代码为工具,更是将其视作传达智慧、思想、理念的桥梁。这是一种智慧上的锤炼与分享。
1. 优先使用 const
。
const
在 JavaScript 中不仅可以用于命名常量,因为其用于保证内存地址不可变,所以也常用于声明对象与数组。在编程中多使用 const
代替 let
,可以在风格上向 immutable
靠拢,在编程思维上开始摈弃副作用。更多的使用 const
虽然可能使声明项增多,但对于开发者来说,更少的心智负担和语义化命名会使代码质量大大上升。
在 JS 中如果过多的使用 let
声明变量,阅读者往往需要贯穿上下文反复阅读才能理解当前变量的值,且变量可能被其他函数引用更改,显而易见,使用变量越多理解的成本也就越高,而且你很难追踪变量具体的值。如下方代码统计数组每个值的总和。使用 const
命名一个常量后,你将无法使用 forEach
在每一次循环时改动它,转而使用 reduce
,我们减少了变量 count
,增加了常量 count
,在随后代码的引用中就无需担忧变量的状态,因为我们知道,count
只能是一个数值,而不会变化。
// bad
let count = 0
[...].forEach(item => {
count += item
})
// good
const count = [...].reduce((pre, current) => pre + current, 0)
2. 使用函数表达式优于函数声明
配合上文所提到的 const
,我们能够使用函数表达式来创建一个函数,更多的时候我们会与箭头函数配合食用 const f = () => {}
。它们优于传统函数声明的地方在于:
- 语义化的指明函数是不可变的。
- 函数表达式可以被看做赋值语句,更加简单易懂,且无法被覆盖。(常量不可以被重复声明)
- 函数声明会在解析时被提升,存在先使用后声明。高可读的代码应当先声明再调用,使用表达式范式可以约束这一点。
- 搭配箭头函数使用可减轻对
this
的思维依赖。
// bad
function addOne(value) {
return value + 1
}
// good
const addOne = value => value + 1
3. 使用 return 减少缩进
缩进问题在 JS 代码中更普遍,推荐在可能的代码块中使用 return
优先返回,如 function
if else
等语句。这样可以有效的减少缩进,同时也能使代码更加的清晰可读,因为在同一时间内总是只做一件事。
我们还可以在必要时优先 return
较短的语句,使代码更美观。
// bad
const render = () => {
if (isServer) {
// server code
} else {
// client code
}
}
// good
const render = () => {
if (isServer) return // server code
// client code
}
4. 不要过度优化
如果你不是编写类库、底层代码等对性能要求极为苛刻时,请勿过度优化性能。绝大多数不必要的性能优化会使代码可读性急剧下降。这非常重要。
例 1,不必要的减少内存空间
// bad
let fullname
users.forEach(user => {
fullname = user.firstname + user.lastname
// ...
register(fullname)
})
// good
users.forEach(user => {
const fullname = user.firstname + user.lastname
// ...
register(fullname)
})
例 2,不必要的运算优化
// bad
let len = users.length
for (i = 0; i < len; i++) {}
// good
users.forEach(user => {})
5. 减少魔术字符
魔术字符 (魔术数字) 指的是代码中出现意义不明的字符,从上下文无法推论其来源与作用,这会使代码难以扩展。
通常,我们还会把所有的字符或数字统一声明在一个常量文件内,如 host
defaultSettings
port
等等,这会有益于维护。
// bad
const url = `${host}/2/users`
// good
const apiVersion = 2
const apis = {
users: 'users',
projects: 'projects',
// ...
}
const url = `${host}/${apiVersion}/${apis.users}`
6. 函数不要有过多参数
在不断延展的需求中,我们的函数会有越来越多的参数,但要注意,当一个函数的参数较多时会使调用方困扰。我们并非需要所有的函数都实现 curry
,但减少函数参数、合并参数、分离函数都会显著提升代码的可读性与扩展性。
在调用较多参数的函数时,我们不仅要紧记每个参数的顺序,还要对空缺的参数进行补位 (如传入 null
undefined
等),这会导致声明与调用的代码中都被迫存在非常多的变量与判断。在函数个数增长时可以考虑将其中的一部分合成为一个对象参数,或是将一部分功能拆离,作为一个新的函数。
// bad
const createUser = (id, name, createdAt, telephone, email, address) => {}
// good
const createUser = (id, userOptions) => {}
7. 保持函数的专注
在一个函数中组好只做一件事,同时也最好保证这件事是与函数的名称是相关的。在单个函数中累积逻辑会给阅读者带来非常大的心智负担,如果我们予以函数拆分、合理化的命名、组合,就能使代码整体获得极大的美感,看起来井井有条,泾渭分明。
// bad
const getUser = id => {
const headers = // ...
const options = // ...
options.headers = headers
const host = // ...
const url = // ...
if (id.length > 1) // ...
return fetch(url, options)
}
// good
const makeOptions = () => {}
const makeUrl = id => {}
const getuser = id => {
const options = makeOptions()
const url = makeUrl(id)
return fetch(url, options)
}
8. 使用语义化命名代替长条件
过长的条件判断常常会在一段时间后变的匪夷所思,很难理解其中的逻辑,将其使用语义化的常量代替则可向阅读者提示意义,更省略了不必要的注释。
// bad
// the code for teen-ager
if (user.age < 19 && user.age > 13) {
// ...
}
// good
const isTeenAgerMember = user.age < 19 && user.age > 13
if (isTeenAgerMember) // ...
9. 减少函数的副作用
减少函数的副作用并非总是需要以纯函数来解决所有问题,不必慌张,我们知道副作用会使状态的变化难以琢磨,在编程中应当以较少的副作用函数为目标,使函数的预期与实际保持一致的同时不至于造成过多的影响,这或许会使在构思和声明时花费一些时间,但对上下文的代码块来说,是一件好事。
// bad
let user = getUser()
const upload = () => {
user.name = `${user.name}-upload`
// fetch user ...
}
// good
const user = getUser()
const upload = user => {
const body = Object.assign({}, user, { name: `${user.name}-upload` })
// fetch body ...
}
upload(user)
下文,JS 优雅指南 - 2。