前端代码优化实用篇

18,562 阅读12分钟

相信很多小伙伴,在接手别人的二手代码时,是否都有一个感受,一无注释,二无格式,代码冗杂,一个简单的功能用了N个方法实现,顿时感觉,一个脑袋两个大,俗话说好的代码令人赏心悦目,冗杂的代码让人心生不快。个人也是接手了其他同事或是外包或是经过多人手的代码的一点感悟,于是个人总结了以下几个代码优化的方法

索引优化代码

先来看一个简单的例子:

需求是星期回显,传 1 回显 星期一,传 7 回显 星期日,其他类推,无效回显空字符串;类似的需求还有回显月份

第一种实现方法: switch 实现

function previewWeek(i){
    switch(i){
        case 1:
            return '星期一'
            break;
        case 2:
            return '星期二'
            break;
        case 3:
            return '星期三'
            break;  
        case 4:
            return '星期四'
            break;  
        case 5:
            return '星期五'
            break;  
        case 6:
            return '星期六'
            break;  
        case 7:
            return '星期日'
            break;
        default:
            return ''
    }
}

第二种实现方法: if else 实现

function previewWeek(i){
    if(i==1){
        return '星期一'
    }else if(i==2){
        return '星期二'
    }else if(i==3){
        return '星期三'
    }else if(i==4){
        return '星期四'
    }else if(i==5){
        return '星期五'
    }else if(i==6){
        return '星期六'
    }else if(i==7){
        return '星期日'
    }else{
        return ''
    }
}

第三种实现方法: 三元实现

function previewWeek(i){
    return  i==1?'星期一':
            i==2?'星期二':
            i==3?'星期三':
            i==4?'星期四':
            i==5?'星期五':
            i==6?'星期六':
            i==7?'星期日':''  
}

感觉代码量少了很多,是否我们还可以优化呢?不难发现代码中有很多重复的代码(包括中文)

第四种实现方法: 数组+索引优化代码

function previewWeek(i){
    return  i>0 && i<8 ?'星期'+['一','二','三','四','五','六','日'][i-1]:''
}

总结:有些时候,重复的代码比较多,我们可以把重复的代码提出来,观察剩下的动态的值,如果可以与索引建立关系更好,可以进一步简化我们的代码

感谢 CherishXY web前端 补充了可以用map,集思广益,特地补充 map 优化方法

第五种实现方法: map优化代码

function previewWeek(i){
    let weeksMap = new Map([
        [1, '一'],
        [2, '二'],
        [3, '三'],
        [4, '四'],
        [5, '五'],
        [6, '六'],
        [7, '日']
    ]);
    return weeksMap.get(i)?'星期'+weeksMap.get(i):''
}

includes 优化代码

includes 是 ES7 新增的API,与 indexOf 不同的是 includes 直接返回的是 Boolean 值,indexOf 则 返回的索引值, 数组和字符串都有 includes 方法,具体可以查看 Array.prototype.includesString.prototype.includes

先来看一个简单的例子:以 数组的 includes 为例子,字符串的includes类似

我们来实现一个身份认证方法,通过传入身份Id返回对应的验证结果

一般实现:||

function verifyIdentity(identityId){
    if(identityId==1 || identityId==2 || identityId==3 || identityId==4){
        return '你的身份合法,请通行!'
    }else{
        return '你的身份未知,警告!'
    }
}

此种写法的缺点是在需要验证的身份Id 变的很多的时候,重复代码量跟着变多

初级优化实现:includes

function verifyIdentity(identityId){
    if([1,2,3,4].includes(identityId)){
        return '你的身份合法,请通行!'
    }else{
        return '你的身份未知,警告!'
    }
}

此种写法的在需要验证的身份Id 变多的时候,只需要在includes前的数组里后面继续添加就行,缺点是仍然占用4行

最终优化实现:includes + 三元

function verifyIdentity(identityId){
    return [1,2,3,4].includes(identityId)?'你的身份合法,请通行!':'你的身份未知,警告!'
}

此种写法个人比较推荐,从维护和扩展方面比较友好

定点优化代码

从一个例子说起:

写个查询元素的方法

一般写法

 /**
 * @param {String } selector : 元素选择器
 * @param {Boolean } isAll  :是否获取所有
 */
function getElement(selector,isAll = false){
    if(isAll){
        return document.querySelectorAll(selector)
    }else{
        return document.querySelector(selector)
    }
}

三元写法

function getElement(selector,isAll = false){
    return isAll?document.querySelectorAll(selector):document.querySelector(selector)
}

三元定点

动态的数据发生的地点,针对性的优化

function getElement(selector,isAll = false){
    return document[ 'querySelector'+(isAll?'All':'') ](selector)
}

不难发现,动态的数据发生在[]里面的小括号里面,通过定点优化进一步减少了我们的代码量,此种衍生的版本比较多

例如:为 container 类元素根据isShow来显示隐藏

// jQuery 中是否经常这么干?
$('.container')[ isShow?'show' : 'hide' ]()

switch语句优化

示例:

let color = "red"
function printBlackBackground(){
    console.log('black')
}
function printRedBackground(){
    console.log('red')
}

function printBlueBackground(){
    console.log('blue')
}

function printGreenBackground(){
    console.log('green')
}

function printYellowBackground(){
    console.log('yellow')
}
switch(color) {
    case 'black':
        printBlackBackground();
        break;
    case 'red':
        printRedBackground();
        break;
    case 'blue':
        printBlueBackground();
        break;
    case 'green':
        printGreenBackground();
        break;
    default:
        printYellowBackground();
}

待优化部分为switch

我们用对象形式建立key-value映射关系


let  colorMap = {
    black: printBlackBackground,
    red: printRedBackground,
    blue: printBlueBackground,
    green: printGreenBackground,
    yellow: printYellowBackground
}

colorMap[color]? colorMap[color]() : printYellowBackground()

备注:此种方法借鉴于其他网友

默认值优化

优化前

function request(options){
    let method = options.method?options.method:'GET'
    let data = options.data?options.data:{}
    //...
}

优化后

function request(options){
    let method = options.method || 'GET'
    let data = options.data || {}
    //...
}

基于 ES6 优化后

function request(method='GET',data={}){
    
    //...
}

有些时候我们封装请求,需要准备自己的默认参数,然后与传入的参数进行合并得到最终的请求参数

function request(options){
    let opt = Object.assign({
        method:'POST',
        data:{}
    },options)
    //opt.data.fxiedProps = 1 ; //有时,请求里需求固定存在一个key值
    return opt
}

单个 if 语句优化策略

优化前

function log(){
    console.log('前面的flag为真,就会看到我')
}
let flag = true

if(flag){
    log()
}

优化后

function log(){
    console.log('前面的flag为真,就会看到我')
}

let flag = true

flag && log()

示例2:

if(a && b){
    c()
}

//=> 优化后 a && b && c()
//其他类推

此种写法项目中使用较多

多个 if不同条件 执行相同逻辑 优化策略

优化前

if(a==1){
    return "结果"
}
if(b==2){
    return "结果"
}
if(c==3){
    return "结果"
}

优化后

if(a==1 || b==2 || c==3){
    return "结果"
}

//也可以简写成
if(a==1 || b==2 || c==3)return "结果"

此种策略采用 归并达到相同结果的 不同条件,从而进行了优化

单个普通if else优化策略

优化前

let flag = true
if(flag){
    //执行业务逻辑
}else{
    return;
}

优化后

let flag = true

if(!flag)return;

//执行业务逻辑

排非策略,此种策略优先排除false情形,通过则后面执行 true 情形的 业务逻辑

单个 if else 带返回值 优化策略

优化前

function demo(flag){
    if(flag){
        return "真"
    }else{
        return "假"
    }
}

优化后

function demo(flag){
    return flag? "真" : "假"
}

单个 if else 执行不同方法 优化策略

从一个例子说起: demo 方法传true执行 success 方法,传 false 执行 error 方法

function success(){
    console.log("success")
}

function fail(){
    console.log("fail")
}

function demo(flag){
    if(flag){
        success()
    }else{
        fail()
    }
}

看了以上多个例子后,你也许会这么优化:

function demo(flag){
    flag?success():fail()
}

这个应该是最常用的,true 就执行 successfalse 就执行 fail

补充一个不常见的

// 如果你不能保证 你所传的参数一定是布尔值的话 用这种
function demo(flag){
    [false,true].includes(flag) && [fail,success][Number(flag)]()
}

// false 转成 0 ,对应执行success ,true 转成 1,对应执行 fail

// 如果你能保证 你所传的参数一定是布尔值的话 用这种
function demo(flag){
    [fail,success][Number(flag)]()
}

此种优化策略,结合了 布尔值的false和true是可以转成 0 和1,因此可以拿来当索引使用

多个 else-if 带返回值 优化策略

封装一个获取位置的方法:getPosition

优化前

function getPosition(direction){
    if(direction == "left"){
        return "左"
    }else if(direction == "right"){
        return "右"
    }else if(direction == "top"){
        return "上"
    }else if(direction == "bottom"){
        return "下"
    }else{
        return "未知"
    }
}

优化后

function getPosition(direction){
    return ({
        left:"左",
        right:"右",
        top:"上",
        bottom:"下"
    })[direction] || "未知"
}

多个 else-if 执行不同方法 优化策略

我们做个权限按钮,不同角色登录一个系统,点击同个按钮执行不同的业务逻辑

优化前

let role = 'admin' //模拟登录接口返回的角色
document.querySelector('#btn').addEventListener( 'click' , function(){
    if(role == 'admin'){
        console.log('管理员点击此按钮执行的业务逻辑')
    }else if(role == 'subAdmin'){
        console.log('子管理员点击此按钮执行的业务逻辑')
    }else if(role == 'mall'){
        console.log('商场角色点击此按钮执行的业务逻辑')
    }else if(role == 'parkingLot'){
        console.log('停车场角色点击此按钮执行的业务逻辑')
    }
})

以上代码看上去没有什么问题,从便于维护和管理角度考虑,是需要优化的

优化后

let role = 'admin' //模拟登录接口返回的角色

let btnPermissionsControl = {
    admin:function(){
        console.log('管理员点击此按钮执行的业务逻辑')
    },
    subAdmin:function(){
        console.log('子管理员点击此按钮执行的业务逻辑')
    },
    mall:function(){
        console.log('商场角色点击此按钮执行的业务逻辑')
    },
    parkingLot:function(){
        console.log('停车场角色点击此按钮执行的业务逻辑')
    }
}
document.querySelector('#btn').addEventListener( 'click' , btnPermissionsControl[role] )

优化后,你只需要维护一个对象即可

多个 if 嵌套优化策略

有些时候,后端返回的数据里动态存在某个值,也就意味着,有时候有这个数据,有时候没有,然后甩你一句话,“有就显示,没有就不显示”,作为前端的我们自然很严谨

场景:后端大哥说了,给你返回的数据里面的 如果有 userInfo字段,并且userInfo下面有hobby字段并且有值就显示 hobby里面的内容,否则页面 hobby这一块不显示

模拟后端返回的数据


let result = {
    status:200,
    codeMsg:'success',
    data:{
        userInfo:{
            age:18,
            hobby:['敲代码','打篮球']
        }
    }
}

前端的严谨写法

if(result.data){
    if(result.data.userInfo){
        if(result.data.userInfo.hobby){
            if(Array.isArray(result.data.userInfo.hobby)){
                if(result.data.userInfo.hobby.length){
                    //遍历 result.data.userInfo.hobby 进行渲染显示
                }
            }
        }
    }
}

&& 进行优化

第一种优化写法

//成功拿到数据了 result
if ( result.data && result.data.userInfo && 
    result.data.userInfo.hobby && 
    Array.isArray(result.data.userInfo.hobby) && 
    result.data.userInfo.hobby.length ) 
{
    //遍历 result.data.userInfo.hobby 进行渲染显示
}

第二种优化写法

//成功拿到数据了 result
result.data && 
result.data.userInfo && 
result.data.userInfo.hobby && 
Array.isArray(result.data.userInfo.hobby) && 
result.data.userInfo.hobby.length) &&
(()=>{
    //遍历 result.data.userInfo.hobby 进行渲染显示
})()

第三种优化写法

此种适合,严谨但又懒的前端

//成功拿到数据了 result
try {
   if(result.data.userInfo.hobby.length){
       //遍历 result.data.userInfo.hobby 进行渲染显示
   }
} catch (error) {
   
}

此种采用 try catch 策略

第四种优化写法

感谢 liubiantaolv-1 爱乐奇小伙伴的建议,特此新增此例,感兴趣的小伙伴可以去看看这个特性

optional chaining优化,新特性,目前最新版的谷歌浏览器已经可以体验,后期应该会加入到 ECMAScript标准中去

if(result?.data?.userInfo?.hobby?.length){
    //遍历 result.data.userInfo.hobby 进行渲染显示
}

其他


let flag = a==1? true:false //优化 => let flag = a==1
//如果 变量 flag 用的地方多的话可以用这个 flag 变量保存,
//如果就一两个地方判断使用,建议直接 if (a==1){}

let FLAG = b==1? false:true //优化 => let FLAG = !(b==1)
//此种策略采用取反

//声明多个变量时
var firstName = '王'
var secondName = '儿'
var thirdName = '麻'
var lastName = '子'

// => 

// var firstName = '王',secondName = '儿',thirdName = '麻',lastName = '子'

//字符串拼接 使用 + 号频繁时,用数组的join方法完成

//let result = firstName+sencondtName+threeName+lastName

// =>

//join
let result = [firstName,secondName,thirdName,lastName].join('')
  • 尽量使用箭头函数
  • 尽量使用模板字符串
  • 尽量使用解构赋值
  • 尽量使用ES6语法,会发现代码会少很多

补充

dom 绑定事件优化,以下示例效果上点击那个dom 就弹出对应的数字(type)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>优化</title>
</head>
<body>
    <div>
        <div class="wrap"><button class="btn" onclick="fn(1)">type=1</button></div>
        <div class="wrap"><button class="btn" onclick="fn(2)">type=2</button></div>
        <div class="wrap"><button class="btn" onclick="fn(3)">type=3</button></div>
    </div>
</body>
<script>
    let btns = document.querySelectorAll('.btn');
    let fn = function(type){
        alert(type)
    }
</script>
</html>

如果我们不想 dom 身上 直接绑定事件呢

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>优化</title>
</head>
<body>
    <div>
        <div class="wrap"><button class="btn">type=1</button></div>
        <div class="wrap"><button class="btn">type=2</button></div>
        <div class="wrap"><button class="btn">type=3</button></div>
    </div>
</body>
<script>
    let btns = document.querySelectorAll('.btn');
    let fn = function(type){
        alert(type)
    }
    btns[0].onclick = fn.bind(null,1)
    btns[1].onclick = fn.bind(null,2)
    btns[2].onclick = fn.bind(null,3)
</script>
</html>

利用 bind 改变this指向(第一个参数不改变传null即可,后面穿参数即可),并且不会立即执行

2021/12/25 补充

场景: 利用ES6剩余参数...取你所想取的数据

// 包含后端不需要字段的参数对象 b和d
const params = {
    a:1,
    b:2,
    c:3,
    d:4
}
// 后端大哥说了,我只要a字段和c字段,其他多余字段给我后端,会报错,于是你就重新组装
// 没有级别的组装
delete params.b
delete params.d

// 青铜级别组装
const param = {
    a:params.a,
    c:params.c
}

// 钻石级别组装
const {a,c} = params
const param = {a,c}

// 王者级别组装
const {b,d,...param} = params // b和d字段是你需求排除的字段
// params:{a:1,c:3}

数组剩余参数...的巧用

const arr = [1,2,3,4,5]
const [a,b,...param] = arr // 排除 a和b对应的索引的值(即1和2) ,但是不能随心所欲的排除,比如排除 1和5 取[2,3,4]
param //[3,4,5]
// 如果想排除 1和5 取[2,3,4] 也是有方法的
arr.filter(i=>![1,5].includes(i)) // [2,3,4]

2022/11/21 补充

场景: 对一个数组需要先过滤再单独处理每一项

示例场景:对 [1,2,3,4,5,6] 数组进行过滤出奇数后再对过滤后的数组的每一项乘以3 后返回该数组

正常操作

const arr = [1,2,3,4,5,6]
const result = arr.filter(item=>item%2===0).map(item=>item*3) // [6, 12, 18]

那么有没有那么一个api 直接可以把两件事都做了呢,答案是有的,它就是人见人爱的flatMap

const arr = [1,2,3,4,5,6]
const result = arr.flatMap(item=>item%2===0?[item*3]:[]) // [6, 12, 18]

2022/11/28 补充

平时我们会封装很多方法,在参数这块是否有所技巧来处理呢

场景: 参数是一个配置对象,里面的属性都是可有可无,没有传的使用默认值,传了的则使用传的值

// 正常操作
function test(config){
    // 默认配置
    const defaultConfig = {
        a:1,
        b:2,
        c:3
    }
    // 传入的 和 默认的 进行合并
    const {a,b,c} =Object.assign(defaultConfig, config || {}) 
    console.log(a,b,c)
}
test() // 1 2 3
test({a:99}) // 99 2 3
// 优化操作
function test({a=1,b=2,c=3}={}){
    console.log(a,b,c)
}
test() // 1 2 3
test({a:99}) // 99 2 3

有没有办法让 findIndexindexOf的出现的更优雅呢,由于他们的返回值 找到就是大于等于0,找不到就是-1,我们可以利用~

// ~位非运算实际上就是对数字进行取负运算,再减 1
~3 // -4
// -3-1 = -4
const arr = [1,2,3,4,5,6]

if(~arr.indexOf(1)){
    console.log('找到了')
}else{
    console.log('没有找到')
}

if(~arr.findIndex(i=>i===1)){
    console.log('找到了')
}else{
    console.log('没有找到')
}

// 更好的就是使用 includes

if(arr.includes(1)){
    console.log('找到了')
}else{
    console.log('没有找到')
}

结语

如果你有更好的点子,欢迎留言

文中若有不准确或错误的地方,欢迎指出

往期文章 :前端开发中实用的工具方法