JS条件判断小技巧

经常code review,我发现很容易写出一堆冗长的代码。今天就列几个比较常见的“解决之道”,看看如何减少JS里的条件判断。

提前返回,少用if...else

但是过多的嵌套,还是挺令人抓狂的。这里有一个很典型的条件嵌套:

        function func() {
            var result;
            if (conditionA) {
                if (condintionB) {
                    result = ‘Success‘;
                } else {
                    result = ‘Error1‘;
                }
            } else {
                result = ‘Error2‘;
            }
            return result;
        }

这种嵌套的特点就是else里的代码块很小,但是由于不得不做的分支语句导致多层嵌套。动动脑筋,怎样精简一下呢?在if里做非判断——条件反转,并通过卫语句提前return else分支。

        function func() {
            if (!conditionA) {
                return ‘Error2‘;
            }

            if (!condintionB) {
                return ‘Error1‘;
            }
            return ‘Success‘;
        }

forEach优化

遍历的时候也经常产生大量的嵌套,如下代码所示,我们先对数组元素做一次合法性校验,通过后再做一次新的操作,最后把操作结果追加到新数组里。

        const func = (arr) => {
            const res = [];
            arr.forEach((e) => {
                if (e !== ‘Onion‘) {
                    res.push(`${e} Run!`);
                }
            })
            return res;
        }

仔细观察这就是一个filter加map的过程。我们不妨试试函数式编程:

        const func = (arr) => {
            return arr.filer((e) => e !== ‘Onion‘)
                .map((e) => `${e} Run!`);
        }

多条件,用Array.includes

再举个例子,某个页面需要检验输入type是否合法。我收到过一个MR曾经是这么写的。  

        const init(type) {
            if (type === ‘Seminar‘ || type === ‘Interview‘) {
                console.log(‘valid‘);
            }
            //...
            console.error(‘invalide‘);
        }

如果合法的类型只有两种,代码其实也没啥问题。只是一般的业务很容易有后续的延展。今后将合法类型增加到10种的话,上述代码里将是一大长串的if判断。这种代码可读性极差,我们不如转换一下思想,把非法类型储到数组里,用Array.includes来帮你完成冗长的判断。之后每增加一种新的类型,只需在数组后追加新字符串就行了。  

        const init(type) {
            const invalidArray = [‘Seminar‘, ‘Interview‘];
            if (invalidArray.includes(type)) {
                console.log(‘valid‘);
            }
            //...
            console.error(‘invalide‘);
        }

使用object索引

类似的情况也出现在三元表达式里:  

        const dateFormat = (dateTime) => {
            const format = this.$i18n.locale === ‘en‘ ? ‘mmm d, yyyy‘ : ‘yyyy年m月d日‘;
            return DateFormat(dateTime, format);
        }

我们现阶段多语言只有en和zh,上述代码自然不是问题,但是也难保哪天会支持日语——ja。这时候再写成下面这类代码就很搞笑了

        const format = this.$i18n.locale === ‘en‘ ? ‘mmm d, yyyy‘ : (
            this.$i18n.locale === ‘zh‘ ? ‘yyyy年m月d日‘ : ‘yyyy/m/d‘
        );

比较合适的写法就是使用object键索引,这样当语言业务扩展后,只需要在localeFormats后追加新格式就行了。

        const localeFormats = {
            en: ‘mmm d, yyyy‘,
            zh: ‘yyyy年m月d日‘,
            ja: ‘yyyy/m/d‘,
        };
        const format = localeFormats[this.$i18n.locale];

尽量少用swith

长Switch也及其难看。

        export function(type) {
            switch (type) {
                case ‘Onion‘:
                    return func1();
                case ‘Garlic‘:
                    return func2();
                case ‘Ginger‘:
                    return func3();
                default:
                    return () => {
                        console.error(‘ERROR‘)
                    };
            }
        }

我记得OOP设计模式里提到过:尽量使用多态和继承代替Switch结构。JS里倒不必非得往这方面想,用Object或是Map索引来代替Switch也是极好滴!

        const arr = [[‘Onion‘, func1], [‘Garlic‘, func2], [‘Ginger‘, func3],];
        const def = () => { console.error(‘ERROR‘) };
        const vegetable = new Map(arr);
        export function(type) {
            return (vegetable.get(type) || def).call(null);
        }

Optional Chaining

Optional Chaining(以下简称OC)是我极力推荐的一个语法糖。我写过一期《Javascript Optional Chaining》具体介绍它的用法,有兴趣的小伙伴可以看一看,这里稍微点一下。比如我们想获取地址里的街道信息,但是并不确定地址本身是否存在,因此只能在获取街道前,事先判断一下地址合法性,一般我们会这么写:  

        if (address) {
            var street = address.street;
        }

但假如再多一层呢,从basicInfo.address.street这样找下来呢?

        if (basicInfo) {
            var address = basicInfo.address;
            if (address) {
                var street = address.street;
            }
        }

上面的代码我已经觉得很丑陋了,再多个几层真是没法看了。不过不用担心,有了OC一切迎刃而解。(虽然OC还在ECMAScript stage2,但是大家可以用babel尝鲜;babel会自动把如下源码转义成上面的形式)

var street = basicInfo?.address?.street;

OOP 多态

某些业务逻辑本身就十分复杂,嵌套的条件语句在逻辑层面就不可能有所优化了;碰到这类场景,我们又该如何作为呢?  

        function greeting(role, access) {
            if (‘owner‘ === role) {
                if (‘public‘ === access) {
                    //...
                }
                if (‘private‘ === access) {
                    //...
                }
                //...
            } else if (‘admin‘ === role) {
                if (‘public‘ === access) {
                    //...
                }
                if (‘private‘ === access) {
                    //...
                }
                //...
            } else if (‘hr‘ === role) {
                //...
            }
        }

看一下代码,第一层的if-else判定的是各种角色(role)类别,第二层判定的是角色访问权限设置(access)。这类代码其实并没有特别优雅的处理手段,只能回到《clean code》里最本源的解决手段——把函数写小。本质问题还是函数体过大,而所谓的把大函数拆成多个小函数,事实上就是以抽象换取可读性。

最常规的手段就是 OOP 多态了。上述代码块,第一层的 role 抽象为 User 实例,嵌套层内的各种 access 进一步抽象为 User 的实例方法。  

        class User {
            public() {
                throw new Error(‘Denied!‘);
            }
            private() {
                throw new Error(‘Denied!‘);
            }
        }

Javascript 并没有 interface 这类语法,好在有 class 了,我仿造 interface 写了一个基类如上。接着就是将各种角色抽象为新的子类型:  

        class Owner extends User {
            public() {
                console.log(‘Owner in public‘);
            }
            private() {
                console.log(‘Owner inside‘);
            }
        }
        class Admin extends User {
            public() {
                console.log(‘Admin in public‘);
            }
            private() {
                console.log(‘Admin inside‘);
            }
        }
        //...

OOP 推荐使用工厂方法初始化实例,我顺手也写个工厂,这样便可以利用工厂方法消除掉了第一层if-else

        class UserFactory {
            static create(role) {
                if (‘owner‘ === role)
                    return new Owner();
                else if (‘admin‘ === role)
                    return new Admin();
                //...
            }
        }

调用的时候我们先通过 role 创建抽象实例,再根据 access 调用具体方法:  

        function greeting(role, access) {
            const user = UserFactory.create(role);
            user[access]();
        }

上面一长串的if-else,一下子被压缩到了两行。这就实现了以抽象(很多可描述的类)换取了可读性(较少的判断嵌套)  

调用链

OOP 效果确实很明显,不过上述代码还是过于特例,假如access并不是字符串(如1,2,3),像user[1]这种就很难映射到具体方法了;所以我们往往还要写更细碎的 access 抽象,也便意味着更多的抽象子类,以及新的工厂方法。很多时候,我们也不并需要抽象得尽善尽美。这个场景里写个调用链,也是勉强可用的:

        const rules = [
            {
                match(role, access) {
                    return ‘owner‘ === role;
                },
                action(role, access) {
                    if (1 === access)
                        console.log(‘Owner in public‘);
                    else if (2 === access)
                        console.log(‘Owner in private‘);
                }
            },
            {
                match(role, access) {
                    return ‘admin‘ === role;
                },
                action(role, access) {
                    //...
                }
            }
            //...
        ];

上面 rules 数组里,每一个元素(rule)里的match被设计用来判定用户权限:遍历数组,若是match为 true,则运行正下方的action——access 相关业务;反之,继续match下一个 rule:

        function greeting(role, access) {
            rules.find(e => e.match(role))
                .action(role, access);
        }

最后 greeting 被重构为上述代码。当然,效果没有多态好,只消掉了一层if-else,第二层判定还是留在了 action 里。  

AOP

AOP,没看错,Javascript 也是有 AOP 的,只是它的实现要修改 Function 的原型链,不是很推荐;但是Function.prototype.before,Function.prototype.after还是挺常见的,开发组里能协商好,还是可以尝试一下的:

        Function.prototype.after = function (next) {
            let fn = this;
            return function $after(...args) {
                let code = fn.apply(this, args);
                next.apply(this, args);
                return code;
            }
        }

传统的 aop after 如上所示。不难看出,用到了高阶函数:具体执行时,先运行函数本体,再运行 after 传进来的 next 方法。为了让 after 应用到我们的话题中,我稍微改一下函数实现:  

        const nextSmb = Symbol(‘next‘);
        Function.prototype.after = function (next) {
            let fn = this;
            return function $after(...args) {
                let code = fn.apply(this, args);
                if (nextSmb === code)
                    return next.apply(this, args);
                return code;
            }
        }

这个 after 实现变成了先运行函数本体,若返回是nextSmb则继续执行后续的 next 方法,反之则停止。有什么用呢?我们看看如何使用:  

        function owner(role, access) {
            function public(access) {
                return 1 === access ? console.log(‘owner in public‘) : nextSmb;
            }
            function private(access) {
                return 2 === access ? console.log(‘owner in private‘) : nextSmb;
            }
            const ownerChain = public.after(private);
            return ‘owner‘ === role ? ownerChain(access) : nextSmb;
        }

代码还是有点难度的,先看一部分——owner 的定义。这个函数被设计处理role === ‘owner‘时的逻辑,内部的public和private方法是处理access为 1 和 2 时的逻辑。我们把public和private方法串联成ownerChain(终于用到after方法了),它的作用就是把之前的if-else逻辑抽象成一个上节讲到的函数调用链,在遍历调用链时检查 access 条件:若符合条件,则执行本节点代码,并结束调用链;反之,继续往调用链的后续节点传送。

我把重构后的 greeting 也列一下——单个role的access可以用after串联;不同role之间也可以进一步利用after串起来。

        function admin(role, access) {
            // familiar with owner
            function public(access) {
                //
            }
            function private(access) {
                //
            }
            const ownerChain = public.after(private);
            return ‘admin‘ === role ? ownerChain(access) : nextSmb;
        }
        let greeting = owner.after(admin);
        greeting(‘owner‘, 1);

嗯,这样,我们最原始的greeting方法就被彻底重构了。可以预见,如果调用链很长greeting会是这样:  

let greeting = owner.after(admin).after(hr).after(staff)...

当然这个方法缺点也很明确,比起之前冗长的代码,可读性增强了,但是理解成本有点高,若团队内没有事先约定,这个维护起来还是挺难的。  

作者:anOnion
链接:https://www.jianshu.com/p/6024b1796af5  

  

  

  

  

  

  

  

  

  

  

  

原文地址:https://www.cnblogs.com/liontone/p/12309697.html

时间: 2024-10-04 08:27:04

JS条件判断小技巧的相关文章

js 的一些小技巧2

js 的一些小技巧 (1)传入一个表单控件(如input输入框,按钮)获取所在的form var getForm= function (formElement) { var $that=$(formElement).parent(); var max=6;//limit the depth var fieldsetElement=null;//form element var tagName=null;//html tag name while((fieldsetElement=$that.ge

记录js的一些小技巧

1.取数组最大值,最小值 Math.max.apply(null,[1,2,3,32,3]); Math.min.apply(null,[1,2,3,32,3]); 2.旧版IE setTimeout(fn,0),延迟太大,可利用image 死链 var img = new Image(); img.onload = img.onerror = function(){ / * 回调方法 */ } img.src = "data:image/png," + Math.random();

winform之权限判断小技巧

每个页面都要判断用户是否登陆并且判断用户是否拥有相应的权限,,以至于每个页面都要判断Session["user"]是否为空,后期不好维护 小技巧: 因为每个页面都继承与Page类,又因为继承的单根性,所以 再新建一个基类,让这个基类继承与Page类, 让页面继承与这个基类. 同时,这个基类还要实现Page中的一个方法来初始化 //继承page页面,必须实现page中的方法,用来初始化基类 public class Pagebase:Page { protected override v

js条件判断

一.if else 条件判断 if(条件表达式) //如果条件表达式为true,执行此处的语句 }else{ //如果条件表达式为false,执行此处的语句 } if(1){ console.log( true ); }else{ console.log( false ); }//true 注意:条件表达式最后都转化成布尔型,0表示false,大于等于1表示true. 字符串有空格和无空格 var i='king'; var j='king ';//空格区别 if(username==a){ d

js条件判断时隐式类型转换

Javascript 中,数字 0 为假,非0 均为真 在条件判断运算 == 中的转换规则是这样的: 如果比较的两者中有布尔值(Boolean),会把 Boolean 先转换为对应的 Number,即 0 和 1,然后进行比较. 如果比较的双方中有一方为 Number,一方为 String时,会把 String 通过 Number() 方法转换为数字,然后进行比较. 如果比较的双方中有一方为 Boolean,一方为 String时,则会将空字符串""转换为 false,除此外的一切字符

看jquery3.3.1学js类型判断的技巧

需要预习:call , typeof, js数据类型 1. isFunction中typeof的不靠谱 源码: var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML <object> elements // (i.e., `typeof d

js !!条件判断或运算的作用

今天看到一个判断语句非常奇怪: 1 if(!!selected){} //为什么是双'!'号呢? 自己查了下资料终于明白了这其中的原理: 原来'!!'会将表达式转为Boolean类型的数据. 如果'!undefined' //得到的是true但是为了防止值等于undefined的时候不为true的话我们可以使用'!!'进行判断: 其实'!!'就是为了排除{非null/undefined/0/''} 等值: 如 var obj = {flag:true}; var demo = !!obj.fla

JS获取数组小技巧

有如下数组 var infoArray = strTest.split(";");            var points2;            for (var i = 0; i < infoArray.length; i++) {                var info = infoArray[i];                points2 = info + "," + points2;            }           

js 条件判断放大字体

<html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> window.onload=function(){ var btn1=document.getElementById('btn1'); var btn2=document.getElementById('btn2'); va