有时感觉挺有趣的是在群里聊天时的自嘲,「xx 项目在经过我一年的不断努力下,终于变得不可维护」。个人认为,维护是一件比开发更富挑战性的事情,前人的代码是否规范优雅会很直接地影响我们的工作效率和心情。
所以,我们更要时刻地去注意我们代码的质量,也许你的代码已经足够规范,但在某种程度上来讲却不够优雅。本文列出了一些让 JS 代码更加优雅的技巧及建议,希望能够对你有所帮助。
我的世界不只有 if else
在逻辑判断的场景中,经常我们的第一反应都是使用 if else / switch 来解决,因为这是最符合我们命令式逻辑思维的语法(难道不是因为书里只教了这两个吗)。
但当在我们的逻辑判断场景中有很多种情况需要判断时,使用 if else / switch 固然能够解决我们的问题,但却让人感觉代码比较冗余而不够优雅。
举个栗子,通过 type 类型来判断相应的学生类型。
// if else
let getStudentType = (type) =>{
if(type == 1){
console.log('学神')
} else if (type == 2){
console.log('学霸')
} else if (type == 3) {
console.log('学渣')
} else {
console.log('学灰')
}
}
// switch
let getStudentType = (type) => {
switch (type) {
case 1:
console.log('学神')
break
case 2:
console.log('学霸')
break
case 3:
console.log('学渣')
break
default:
console.log('学灰')
break
}
}
如上,通过 if else / switch 语法虽然能够直观地表现出代码的逻辑,但却似乎让人感觉有些重复赘余。其实,对于逻辑判断的场景,我们还可以有更多其它的方式来解决;其实,你的 JS 代码本可以更加优雅。
三目运算符
如果通过逻辑判断只是单纯为了进行赋值的操作,那么我们通常可以使用三目运算符来解决。
let getStudentType = (type) => {
let studentType = type == 1 ?'学神'
: type == 2 ? '学霸'
: type == 3 ? '学渣' : '学灰';
console.log(studentType)
}
是否看起来更加舒适了呢。
Object / Map 对象
基本上所有的逻辑判断操作都可以通过 Object / Map 对象的方式来解决。
// Object 对象
let getStudentType = (type) => {
let obj = {
1:'学神',
2:'学霸',
3:'学渣',
0:'学灰'
}
let studentType = obj[type] || obj['0'];
console.log(studentType);
}
// Map 对象
let getStudentType = (type) => {
let map = new Map([
[1, '学神'],
[2, '学霸'],
[3, '学渣'],
[0, '学灰']
])
let studentType = map.get(type) || map.get('0');
console.log(studentType);
}
在逻辑判断的场景中通过 Object / Map 对象我们依旧能够实现优雅的代码,当然,上面所举的栗子仅仅只是逻辑判断中进行赋值操作的场景,在对于需要做更多操作的逻辑判断的场景中,Object / Map 对象更能体现出它们的优势。
让我们扩展上面的栗子,在通过 type 类型判断相应学生类型之后,每个学生还会发动自身相关类型的技能。
通过 if else 实现的方式如下
// if else
let studentAction = (type) =>{
if(type == 1){
console.log('学神')
launchXueshenSkill();
} else if (type == 2){
console.log('学霸')
launchXuebaSkill();
} else if (type == 3) {
console.log('学渣')
launchXuezhaSkill();
} else {
console.log('学灰')
launchXuehuiSkill();
}
}
而通过 Object / Map 对象,可以更优雅地实现
// Object 对象
let getStudentType = (type) => {
let obj = {
1: () => { console.log('学神'); launchXueshenSkill(); },
2: () => { console.log('学霸'); launchXuebaSkill(); },
3: () => { console.log('学渣'); launchXuezhaSkill(); },
0: () => { console.log('学灰'); launchXuehuiSkill(); },
}
let studentSkill = obj[type] || obj['0'];
studentSkill();
}
// Map 对象
let getStudentType = (type) => {
let map = new Map([
[1, () => { console.log('学神'); launchXueshenSkill(); }],
[2, () => { console.log('学霸'); launchXuebaSkill(); }],
[3, () => { console.log('学渣'); launchXuezhaSkill(); }],
[0, () => { console.log('学灰'); launchXuehuiSkill(); }]
])
let studentSkill = map.get(type) || map.get('0');
studentSkill()
}
Object 和 Map 对象都能解决所有的逻辑判断的问题,那么它们两者有什么区别呢?
Map 对象是 ES6 中的语法,它与 Object 对象最大的区别就是 Object 对象的键只能是字符串,而 Map 对象的键可以是任意值。
所以相对而言,Map 对象较 Object 对象更加灵活,在更加复杂的逻辑判断中,当我们的键使用字符串不再满足需求时,使用 Map 对象才能实现我们的目的。
true && xxx
true && xxx 主要是适用于 if else 中一些简单场景的情况。判断一个值是否为 true,如果为 true 时则执行 xxx 的相关操作。
let isGoodStudent = true;
// if else
if (isGoodStudent){
console.log('我是一个好学生')
}
// true && xxx
isGoodStudent && (console.log('我是一个好学生'));
三行的代码最终简写为一行,真是优雅呀!
false || variable
false || xxx 的使用场景是作为某些场景下三目运算符的简洁写法。判断一个变量是否存在,若是不存在(为 false )则赋值另一个变量(variable)。
let studentType1 = '学神'
let studentType2 = '学霸'
// 三目运算符
let goodStudent = studentType1 ? studentType1 : studentType2;
// false || xxx
let goodStudent = studentType1 || studentType2;
我的世界不只有 for
在逻辑循环的场景中,经常我们的第一反应都是使用 for / while 来解决,因为这也是最符合我们命令式逻辑思维的语法(难道不还是因为书里只教了这两个吗)。
但与 if else / switch 一样,for / while 也是比较直观但同时欠缺优雅性的写法。
let studentType = ['学神', '学霸', '学渣', '学灰'];
// for
for (let i = 0, len = studentType.length; i < len; i++) {
console.log(studentType[i]);
}
// while
let i = 0;
while (i < studentType.length){
console.log(studentType[i]);
i++;
}
同样的,对于逻辑循环的场景,我们还可以有更多其它的方式来解决。
Array.prototype.forEach
forEach() 方法的使用场景与 for / while 基本是一致的(forEach 循环不能提前终止),只要是逻辑循环的场景,都可以使用 forEach() 来实现。
studentType.forEach((v,i) => {
console.log(v);
})
Array.prototype.map
map() 方法若是只需要取得数组的元素进行循环的一些操作,则其使用方式与 forEach() 是一致的。
studentType.map((v, i) => {
console.log(v);
})
map() 方法会返回一个新数组,其结果是原始数组中的每个元素都调用一个提供的函数后返回的结果。
举个栗子,在 studentType 类型中的每个元素后面都添加 +10086 的字符串然后返回一个新数组。
llet superStudentType = studentType.map((v, i) => `${v}+10086`)
console.log(superStudentType); // [ '学神+10086', '学霸+10086', '学渣+10086', '学灰+10086' ]
所以,map() 方法除了能代替 for / while 循环外,还提供了对原始数组元素操作并返回新数组的功能(这同样也可以使用 for / while 循环来实现,只是需要书写更多的代码来实现)。
同样的,下述所列举的关于数组的方法,都是可以通过 for / while 循环来实现的,只是使用下述已封装好的方法,会让我们的代码逻辑更清晰并且代码更加简洁优雅。
Array.prototype.filter
filter() 方法返回一个新数组, 其包含通过所提供函数实现的测试的所有元素。
let studentTypeWithScore = [{
type:'学神',
score:100
},{
type: '学霸',
score: 85
},{
type: '学渣',
score: 65
},{
type: '学灰',
score: 50
}]
let goodStudentType = studentTypeWithScore.filter((v, i) => v.score > 80)
console.log(goodStudentType); // [ { type: '学神', score: 100 }, { type: '学霸', score: 85 } ]
Array.prototype.find
find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。
所以,当我们只想获得数组中符合条件的第一个元素时,使用 find() 方法会比使用 filter() 方法更加高效简洁。find() 方法获得符合条件的第一个元素时就停止遍历了,而 filter() 方法需要遍历数组全部元素获得符合条件的所有元素并取出第一个元素。
let oneGoodStudentType = studentTypeWithScore.find((v, i) => v.score > 80)
console.log(oneGoodStudentType); // { type: '学神', score: 100 }
Array.prototype.some
some() 方法用于检测数组中是否有元素满足指定条件。
同样的,当我们只想确定数组中是否有符合条件的元素,使用 some() 方法会比使用 find() 方法更加高效简洁。some() 方法是返回布尔值而 find() 方法是返回符合条件的第一个元素值。
let hasGoodStudentType = studentTypeWithScore.some((v, i) => v.score > 80)
console.log(hasGoodStudentType); // true
Array.prototype.every
every() 方法测试数组的所有元素是否都通过了指定函数的测试。
let isAllGoodStudentType = studentTypeWithScore.every((v, i) => v.score > 80)
console.log(isAllGoodStudentType); // false
let isAllStudentType = studentTypeWithScore.every((v, i) => v.score > 40)
console.log(isAllStudentType); // true
Array.prototype.reduce
reduce() 方法对累计器和数组中的每个元素(从左到右)应用一个函数,将其简化为单个值。
let sum = studentTypeWithScore.reduce((acc, curVal) => acc + curVal.score, 0); // 100 + 85 + 65 + 50
console.log(sum); // 300
其它技巧及建议
- 当仅仅是为了判断字符串中是否存在某子串时,使用 String.prototype.includes 代替 String.prototype.indexOf;
- 当仅仅是为了判断数组中是否存在某元素时,使用 Array.prototype.includes 代替 Array.prototype.indexOf;
- 尽可能地减少代码块的嵌套;
- 尽可能使用 ES6 及更新的语法;
有时,代码优雅是建立在牺牲代码可读性及性能之上的,鱼与熊掌不可兼得,具体的实现方式还是需要根据实际场景来做不同的取舍。