本文最后更新于:2021年6月21日 中午
场景
圣人走过的道路,荆棘遍布,火焰片片焚烧……
日常 review
代码时看到一些奇怪的代码,这里记录一下重构方案以及原因。
命名规范
不要使用拼音命名
如果不熟悉英语,可以使用 Codelf 或者 Google 翻译,避免使用拼音命名。
错误示例
1 2
| const yongHuZhuangTai = 1
|
正确示例
函数中的变量
js 中普通变量使用 小写开头驼峰命名法,而非不区分大小写,或使用下划线命名等等。
错误示例
1 2
| const useroperatinglogremark = '新增用户'
|
正确示例
1
| const userOperatingLogRemark = '新增用户'
|
内部变量
如果需要不想让使用者使用的属性(能够看到),需要使用下划线开头。例如 _value
,代表内部的值,外部不应该直接访问(实际上可以做到)。
1 2 3 4 5 6 7 8 9 10 11 12
| class DateFormat { constructor(fmt) { this._fmt = fmt } format(date) { } parse(str) { } }
|
不要使用无意义的前缀命名
如果一个对象的变量名已经很好的标识了该对象,那么内部的属性就不能使用对象名作为前缀!
错误示例
1 2 3 4 5
| const activeLog = { activeUserId: 'rx', activeTime: new Date(), }
|
正确示例
1 2 3 4
| const activeLog = { userId: 'rx', time: new Date(), }
|
ES6
优先使用 const/let
一般情况下,使用 const/let
声明变量,而不是使用 var
。因为使用 var
声明的变量会存在变量提升。
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| ;(function () { console.log(i) i = 1 console.log(i) var i = 0
k = 1 const k = 0 })()
|
关于可以参考 let 与 var 在 for 循环中的区别
使用新的函数声明方式
ES6 推出了一种更简洁的函数声明方式,不需要在写 function
,只要 名字 + () 即可在 class
或 Object
中声明函数。
错误示例
1 2 3 4 5 6
| const user = { name: 'rx', hello: function () { console.log('hello' + this.name) }, }
|
正确示例
1 2 3 4 5 6
| const user = { name: 'rx', hello() { console.log('hello' + this.name) }, }
|
优先使用箭头函数而非 function
优先使用 箭头函数 而不是使用传统的函数,尤其是使用 匿名函数 时,更应如此。
错误示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const sum = [1, 2, 3, 4] .filter(function (i) { return i % 2 === 0 }) .map(function (i) { return i * 2 }) .reduce(function (res, i) { return (res += i) })
console.log(sum)
|
正确示例
1 2 3 4 5 6 7 8 9
| const sum = [1, 2, 3, 4] .filter((i) => i % 2 === 0) .map((i) => i * 2) .reduce((res, i) => (res += i))
console.log(sum)
|
不要使用 if 判断再赋予默认值
如果函数需要对参数做默认值处理,请不要使用 if
判空之后再修改参数,而是使用 ES6 的 默认参数 和 解构赋值。
主要优点
- 减少代码,JavaScript 是动态语言,维护起来较为麻烦,代码越少,错误越少
- 清晰明了,可以让人一眼就能看出这个参数的默认值,而不需要关心函数内部的逻辑
- IDE 大多对此进行了支持,代码提示时便会告诉我们参数是可选的并且有默认值
错误示例
1 2 3 4 5 6 7 8 9 10 11
|
function formatDate(date) { if (date === undefined) { date = new Date() } return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}` }
|
正确示例
1 2 3
| function formatDate(date = new Date()) { return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}` }
|
这里如果展开来讲实在太多,请参考 JavaScript 善用解构赋值
优先使用 Map 做键值对映射而非传统的对象
如果需要 键值映射,不要使用一般的对象,而是用 ES6 的 Map。它不仅可以使用 任意类型的键,另外 Map 本身也是 有序 的哦
错误示例
1 2 3 4 5 6 7 8 9
| const obj = { 2: '琉璃', 1: 'rx', 1: 'liuli', }
console.log(obj[1] === obj['1'])
console.log(Object.keys(obj))
|
正确示例
1 2 3 4 5
| const map = new Map().set(2, '琉璃').set(1, 'rx').set('1', 'liuli')
console.log(map.get(1) === map.get('1'))
console.log(Array.from(map.keys()))
|
优先使用模板字符串拼接多个字符串变量
如果需要拼接多个对象以及字符串时,不要使用 +
进行拼接,使用 es6 的 模板字符串 会更好一点。一般而言,如果需要拼接的变量超过 3 个,那么就应该使用模板字符串了。
错误示例
1 2 3
| function hello(name, age, sex) { return 'name: ' + name + ', age: ' + age + ', sex: ' + sex }
|
正确示例
1 2 3
| function hello(name, age, sex) { return `name: ${name}, age: ${age}, sex: ${sex}` }
|
当独立参数超过 3 个时使用对象参数并解构
错误示例
1 2 3
| function hello(name, age, sex) { return `name: ${name}, age: ${age}, sex: ${sex}` }
|
正确示例
1 2 3
| function hello({ name, age, sex }) { return `name: ${name}, age: ${age}, sex: ${sex}` }
|
不要写多余的 await
如果 await
是不必要的(在返回语句时,那么就不要用 async
标识函数),这是没有必要的 – 除非,你需要在这个函数内异步操作完成后有其他操作。
错误示例
1 2 3 4 5 6 7 8 9 10 11 12
| const login = async ({ username, password }) => { if (!useranme) { console.log('用户名不能为空') return } if (!password) { console.log('密码不能为空') return } return await userApi.login(user) }
|
正确示例
1 2 3 4 5 6 7 8 9 10 11 12
| const login = ({ username, password }) => { if (!useranme) { console.log('用户名不能为空') return } if (!password) { console.log('密码不能为空') return } return userApi.login(user) }
|
不要使用 == 进行比较
在 js 中使用 ==
比较相当危险,你永远不知道 js 到底是按照什么类型比较的,因为 js 会做各种隐式转换。而如果使用 ===
比较,则会同时比较 值 和 类型 是否都相同,避免了各种不确定的问题。
错误示例
1 2 3 4 5
| console.log(1 == true) console.log(1 == '1') console.log('1' == true) console.log('0' == true) console.log([] == [])
|
扪心自问,你真的知道上面为什么会出现这种结果么?即便知道,对于其他人而言仍然是难以预测的,所以抛弃掉 ==
吧,学会使用更好的 ===
最好
1 2 3 4 5
| console.log(1 == true) console.log(1 == '1') console.log('1' == true) console.log('0' == true) console.log([] == [])
|
使用计算属性名替代使用方括号表示法赋值
目前而言已经有了 计算属性名 用以在初始化时计算属性名,所以不需要再先声明对象再使用 方括号表示法 进行赋值了。
ES5 写法
1 2 3 4 5 6 7
| const state = { 'user.username': function () {}, }
state[Date.now()] = new Date()
console.log(state)
|
ES6 写法
1 2 3 4 5 6
| const state = { 'user.username'() {}, [Date.now()]: new Date(), }
console.log(state)
|
简单的选项列表优先使用 Map 而非数组
对于复选框,想必很多人相当熟悉。下面使用 js 模拟一个复选框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const item = { id: 1, role: ['1', '2'], name: '', } const options = [ { roleid: '1', label: '黄金糕', }, { roleid: '2', label: '双皮奶', }, { roleid: '3', label: '蚵仔煎', }, ]
|
现在的需求是根据 role
计算显示值 name
的值
1 2 3 4
| item.name = item.role .map((role) => options.find((op) => op.roleid === role)) .filter((s) => s) .join(',')
|
但实际上这里应该使用 Map
替代数组,因为数组的 find
其实非常低效,也需要进行遍历,使用 Map
的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const item = { id: 1, role: [1, 2], name: '', } const options = new Map().set(1, '黄金糕').set(2, '双皮奶').set(3, '蚵仔煎')
function calcName(role) { return role .map((k) => options.get(k)) .filter((s) => s) .join(',') }
item.name = calcName(item.role)
|
可以看到,获取时使用了 Map#get
,在效率上应该是极好的。
附: 该问题来自 https://segmentfault.com/q/1010000019426996
存放 id 标识列表使用 Set 而非数组
还是上面的例子,当你需要存取当前选中复选框的值 role
使用数组时,有可能遇到 id 重复的问题,实际上导致每次添加前需要使用 Array#includes
判断是否已存在。这里可以使用 Set
从数据结构层面避免掉可能重复的问题。
修改后的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const item = { id: 1, role: new Set([1, 2]), name: '', } const options = new Map().set(1, '黄金糕').set(2, '双皮奶').set(3, '蚵仔煎')
function calcName(role) { return Array.from(role) .map((k) => options.get(k)) .filter((s) => s) .join(',') }
item.name = calcName(item.role)
|
先判断是否存在执行某些操作也非常方便,可以使用 Set#has
进行判断,当然时间复杂度时 O1。
逻辑代码
不要判断一个 Boolean 值并以此返回 Boolean 值
不要在得到一个 Boolean
的值后使用 if-else
进行判断,然后根据结果返回 true
或 false
,这真的显得非常非常蠢!
错误示例
1 2 3 4 5 6 7 8 9
| const login = async ({ username, password }) => { const res = username === 'rx' && password === 'rx' if (res) { return true } else { return false } }
|
正确示例
1 2 3
| const login = async ({ username, password }) => username === 'rx' && password === 'rx'
|
不要使用多余的变量
如果一个表达式立刻被使用并且只会被使用一次,那就不要使用变量声明,直接在需要的地方使用好了。
错误示例
1 2 3 4 5
| const login = async ({ username, password }) => { const res = username === 'rx' && password === 'rx' return res }
|
正确示例
1 2 3 4
| const login = async ({ username, password }) => { return username === 'rx' && password === 'rx' }
|
不要使用嵌套 if
不要使用多级的 if
嵌套,这会让代码变得丑陋且难以调试,应当优先使用 提前 return 的策略。
错误示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const login = async ({ username, password }) => username === 'rx' && password === 'rx'
async function submit(user) { const { username, password } = user if (username) { if (password) { const res = await login(user) if (res) { console.log('登录成功,即将跳转到首页') } else { console.log('登录失败,请检查用户名和密码') } } else { console.log('用户密码不能为空') } } else { console.log('用户名不能为空') } }
|
正确示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const login = async ({ username, password }) => username === 'rx' && password === 'rx'
async function submit(user) { const { username, password } = user if (!username) { console.log('用户名不能为空') return } if (!password) { console.log('用户密码不能为空') return } const res = await login(user) if (!res) { console.log('登录失败,请检查用户名和密码') return } console.log('登录成功,即将跳转到首页') }
|
不要先声明空对象然后一个个追加属性
有时候会碰到这种情况,先声明一个空对象,然后在下面一个个追加属性,为什么创建对象与初始化不放到一起做呢?
错误示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const login = async ({ username, password }) => username === 'rx' && password === 'rx'
async function submit(username, password) {
const user = {} user.username = username.trim() user.password = password.trim()
const res = await login(user)
}
|
正确示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const login = async ({ username, password }) => username === 'rx' && password === 'rx'
async function submit(username, password) {
const user = { username: username.trim(), password: password.trim(), }
const res = await login(user)
}
|
不要使用无意义的函数包裹
使用函数时,如果你想包裹的函数和原来函数的参数/返回值相同,那就直接应该使用函数作为参数,而非在包裹一层。给人的感觉就像是大夏天穿着棉袄吃雪糕 – 多此一举!
错误示例
1 2 3 4
| const isEven = (i) => i % 2 === 0
const res = [1, 2, 3, 4].filter((i) => isEven(i))
|
正确示例
1 2 3 4
| const isEven = (i) => i % 2 === 0
const res = [1, 2, 3, 4].filter(isEven)
|
不要使用三元运算符进行复杂的计算
三元运算符适合于替代简单的 if-else
的情况,如果碰到较为复杂的情况,请使用 if + return
或者解构/默认参数的方式解决。
错误示例
1 2 3 4 5 6 7 8
| function formatUser(user) { return user === undefined ? 'username: noname, password: blank' : 'username: ' + (user.username === undefined ? 'noname' : user.username) + ', password: ' + (user.password === undefined ? 'blank' : user.password) }
|
看到上面的代码就感觉到一股烂代码的味道扑面而来,这实在是太糟糕了!实际上只需要两行代码就好了!
正确示例
1 2 3
| function formatUser({ username = 'noname', password = 'blank' } = {}) { return `username: ${username}, password: ${password}` }
|
如果变量有所关联则使用对象而非多个单独的变量
如果变量有所关联,例如一个表单,存储的时候不要使用单独的变量,将之存储到一个表单变量中更好。
错误示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| function login(){ const username = document.querySelector('#username') const password = document.querySelector('#password') const remeberMe = document.querySelector('#remeberMe')
if (!validate(username, password, remeberMe)) { return } const res = await userLogin.login(username, password, remeberMe) }
|
正确示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function login(){ const user = { username : document.querySelector('#username'), password : document.querySelector('#password'), remeberMe : document.querySelector('#remeberMe'), } if (!validate(user)) { return } const res = await userLogin.login(user) }
|
应该尽量解决编辑器警告
如果编辑器对我们的代码发出警告,那么一般都是我们代码出现了问题(一般开发人员的能力并不足以比肩编辑器 #MS 那些 dalao 的能力)。所以,如果出现了警告,应该先去解决它 – 如果你确认发生了错误,则通过注释/配置禁用它!
使用类型定义参数对象
如果一个函数需要一个对象参数,最好专门定义一个类型,并在注释上说明,便于在使用时 IDE 进行提示,而不需要去查找文档手册。
错误示例
1 2 3 4 5 6 7 8 9 10 11 12
|
function formatUser(user) { const { username, password } = user || {} return `user, username: ${username}, password: ${password}` }
const str = formatUser({ username: 'rx', password: '123456' }) console.log(str)
|
正确示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class User { constructor(username, password) { this.username = username this.password = password } }
function formatUser(user) { const { username, password } = user || {} return `user, username: ${username}, password: ${password}` }
const str = formatUser(new User('rx', '123456')) console.log(str)
|
尽量扁平化代码
尽量将 a
调用 b
, b
调用 c
,然后 b
调用 d
,优化为依次调用 a, b, c, d
。
注意: 这里使用的是尽量而非不要,深层嵌套不可避免,但在局部上,应该采取扁平化的策略,提前 return 避免嵌套 if-else 是个很好的例子。
自执行函数前面必须加分号
如果我们需要使用自执行函数,则开头必须加上 ;
以避免可能出现的歧义。
错误示例
1 2 3 4 5 6 7
| function returnItself(o) { return o } returnItself(() => console.log(1)) (() => { console.log(2) })()
|
上面这段代码是有问题的,因为后面的自执行函数会被认为是上一句的 returnItself
返回函数的参数,最后的括号会被认为又是一次调用,将会抛出错误
1
| returnItself(...)(...) is not a function
|
正确示例
1 2 3 4 5 6 7
| function returnItself(o) { return o } returnItself(() => console.log(1)) ;(() => { console.log(2) })()
|
使用分号可以明确告诉 JavaScript 这是一行新的代码,上面的代码已经到此为止了。