首页 文章 精选 留言 我的

精选列表

搜索[javascript引擎],共10000篇文章
优秀的个人博客,低调大师

JavaScript 复杂判断的更优雅写法

前提 我们编写js代码时经常遇到复杂逻辑判断的情况,通常大家可以用if/else或者switch来实现多个条件判断,但这样会有个问题,随着逻辑复杂度的增加,代码中的if/else/switch会变得越来越臃肿,越来越看不懂,那么如何更优雅的写判断逻辑,本文带你试一下。 举个例子 先看一段代码 /** * 按钮点击事件 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 */ constonButtonClick=(status)=>{ if(status==1){ sendLog('processing')jumpTo('IndexPage') }elseif(status==2){ sendLog('fail')jumpTo('FailPage') }elseif(status==3){ sendLog('fail')jumpTo('FailPage') }elseif(status==4){ sendLog('success')jumpTo('SuccessPage') }elseif(status==5){ sendLog('cancel')jumpTo('CancelPage') }else{ sendLog('other')jumpTo('Index') } } 通过代码可以看到这个按钮的点击逻辑:根据不同活动状态做两件事情,发送日志埋点和跳转到对应页面,大家可以很轻易的提出这段代码的改写方案,switch出场: /** * 按钮点击事件 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 */ constonButtonClick=(status)=>{ switch(status){ case1: sendLog('processing') jumpTo('IndexPage') break case2: case3: sendLog('fail') jumpTo('FailPage') break case4: sendLog('success') jumpTo('SuccessPage') break case5: sendLog('cancel') jumpTo('CancelPage') break default: sendLog('other') jumpTo('Index') break } } 嗯,这样看起来比if/else清晰多了,细心的同学也发现了小技巧,case 2和case 3逻辑一样的时候,可以省去执行语句和break,则case 2的情况自动执行case 3的逻辑。 这时有同学会说,还有更简单的写法: constactions={ '1':['processing','IndexPage'], '2':['fail','FailPage'], '3':['fail','FailPage'], '4':['success','SuccessPage'], '5':['cancel','CancelPage'], 'default':['other','Index'], } /** * 按钮点击事件 * @param {number} status 活动状态:1开团进行中 2开团失败 3 商品售罄 4 开团成功 5 系统取消 */ constonButtonClick=(status)=>{ letaction=actions[status]||actions['default'], logName=action[0], pageName=action[1] sendLog(logName) jumpTo(pageName) } 上面代码确实看起来更清爽了,这种方法的聪明之处在于:将判断条件作为对象的属性名,将处理逻辑作为对象的属性值,在按钮点击的时候,通过对象属性查找的方式来进行逻辑判断,这种写法特别适合一元条件判断的情况。 是不是还有其他写法呢?有的: constactions=newMap([ [1,['processing','IndexPage']], [2,['fail','FailPage']], [3,['fail','FailPage']], [4,['success','SuccessPage']], [5,['cancel','CancelPage']], ['default',['other','Index']] ]) /** * 按钮点击事件 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 */ constonButtonClick=(status)=>{ letaction=actions.get(status)||actions.get('default') sendLog(action[0]) jumpTo(action[1]) } 这样写用到了es6里的Map对象,是不是更爽了?Map对象和Object对象有什么区别呢? 一个对象通常都有自己的原型,所以一个对象总有一个"prototype"键。 一个对象的键只能是字符串或者Symbols,但一个Map的键可以是任意值。 你可以通过size属性很容易地得到一个Map的键值对个数,而对象的键值对个数只能手动确认。 我们需要把问题升级一下,以前按钮点击时候只需要判断status,现在还需要判断用户的身份: /** * 按钮点击事件 * @param {number} status 活动状态:1开团进行中 2开团失败 3 开团成功 4 商品售罄 5 有库存未开团 * @param {string} identity 身份标识:guest客态 master主态 */ constonButtonClick=(status,identity)=>{ if(identity=='guest'){ if(status==1){ //do sth }elseif(status==2){ //do sth }elseif(status==3){ //do sth }elseif(status==4){ //do sth }elseif(status==5){ //do sth }else{ //do sth } }elseif(identity=='master'){ if(status==1){ //do sth }elseif(status==2){ //do sth }elseif(status==3){ //do sth }elseif(status==4){ //do sth }elseif(status==5){ //do sth }else{ //do sth } } } 原谅我不写每个判断里的具体逻辑了,因为代码太冗长了。 原谅我又用了if/else,因为我看到很多人依然在用if/else写这种大段的逻辑判断。 从上面的例子我们可以看到,当你的逻辑升级为二元判断时,你的判断量会加倍,你的代码量也会加倍,这时怎么写更清爽呢? constactions=newMap([ ['guest_1',()=>{/*do sth*/}], ['guest_2',()=>{/*do sth*/}], ['guest_3',()=>{/*do sth*/}], ['guest_4',()=>{/*do sth*/}], ['guest_5',()=>{/*do sth*/}], ['master_1',()=>{/*do sth*/}], ['master_2',()=>{/*do sth*/}], ['master_3',()=>{/*do sth*/}], ['master_4',()=>{/*do sth*/}], ['master_5',()=>{/*do sth*/}], ['default',()=>{/*do sth*/}], ]) /** * 按钮点击事件 * @param {string} identity 身份标识:guest客态 master主态 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 开团成功 4 商品售罄 5 有库存未开团 */ constonButtonClick=(identity,status)=>{ letaction=actions.get(`${identity}_${status}`)||actions.get('default') action.call(this) } 上述代码核心逻辑是:把两个条件拼接成字符串,并通过以条件拼接字符串作为键,以处理函数作为值的Map对象进行查找并执行,这种写法在多元条件判断时候尤其好用。 当然上述代码如果用Object对象来实现也是类似的: constactions={ 'guest_1':()=>{/*do sth*/}, 'guest_2':()=>{/*do sth*/}, //.... } constonButtonClick=(identity,status)=>{ letaction=actions[`${identity}_${status}`]||actions['default'] action.call(this) } 如果有些同学觉得把查询条件拼成字符串有点别扭,那还有一种方案,就是用Map对象,以Object对象作为key: constactions=newMap([ [{ identity:'guest', status:1 },()=>{/*do sth*/}], [{ identity:'guest', status:2 },()=>{/*do sth*/}], //... ]) constonButtonClick=(identity,status)=>{ letaction=[...actions].filter(([key,value])=>(key.identity==identity&&key.status==status)) action.forEach(([key,value])=>value.call(this)) } 是不是又高级了一点点? 这里也看出来Map与Object的区别,Map可以用任何类型的数据作为key。 我们现在再将难度升级一点点,假如guest情况下,status1-4的处理逻辑都一样怎么办,最差的情况是这样: constactions=newMap([ [{ identity:'guest', status:1 },()=>{/* functionA */}], [{ identity:'guest', status:2 },()=>{/* functionA */}], [{ identity:'guest', status:3 },()=>{/* functionA */}], [{ identity:'guest', status:4 },()=>{/* functionA */}], [{ identity:'guest', status:5 },()=>{/* functionB */}], //... ]) 好一点的写法是将处理逻辑函数进行缓存: constactions=()=>{ constfunctionA=()=>{/*do sth*/} constfunctionB=()=>{/*do sth*/} returnnewMap([ [{ identity:'guest', status:1 },functionA], [{ identity:'guest', status:2 },functionA], [{ identity:'guest', status:3 },functionA], [{ identity:'guest', status:4 },functionA], [{ identity:'guest', status:5 },functionB], //... ]) } constonButtonClick=(identity,status)=>{ letaction=[...actions()].filter(([key,value])=>(key.identity==identity&&key.status==status)) action.forEach(([key,value])=>value.call(this)) } 这样写已经能满足日常需求了,但认真一点讲,上面重写了4次functionA还是有点不爽,假如判断条件变得特别复杂,比如identity有3种状态,status有10种状态,那你需要定义30条处理逻辑,而往往这些逻辑里面很多都是相同的,这似乎也是笔者不想接受的,那可以这样实现: constactions=()=>{ constfunctionA=()=>{/*do sth*/} constfunctionB=()=>{/*do sth*/} returnnewMap([ [/^guest_[1-4]$/,functionA], [/^guest_5$/,functionB], //... ]) } constonButtonClick=(identity,status)=>{ letaction=[...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`))) action.forEach(([key,value])=>value.call(this)) } 这里Map的优势更加凸显,可以用正则类型作为key了,这样就有了无限可能,假如需求变成,凡是guest情况都要发送一个日志埋点,不同status情况也需要单独的逻辑处理,那我们可以这样写: constactions=()=>{ constfunctionA=()=>{/*do sth*/} constfunctionB=()=>{/*do sth*/} constfunctionC=()=>{/*send log*/} returnnewMap([ [/^guest_[1-4]$/,functionA], [/^guest_5$/,functionB], [/^guest_.*$/,functionC], //... ]) } constonButtonClick=(identity,status)=>{ letaction=[...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`))) action.forEach(([key,value])=>value.call(this)) } 也就是说利用数组循环的特性,符合正则条件的逻辑都会被执行,那就可以同时执行公共逻辑和单独逻辑,因为正则的存在,你可以打开想象力解锁更多的玩法,本文就不赘述了。 总结 本文已经教你了8种逻辑判断写法,包括: if/else switch 一元判断时:存到Object里 一元判断时:存到Map里 多元判断时:将condition拼接成字符串存到Object里 多元判断时:将condition拼接成字符串存到Map里 多元判断时:将condition存为Object存到Map里 多元判断时:将condition写作正则存到Map里 至此,本文也将告一段落,愿你未来的人生里,不只是有if/else/switch。 原文发布时间为:2018-11-13 本文作者:大转转FE/Think 本文来自云栖社区合作伙伴“编程微刊”,了解相关信息可以关注“编程微刊”。

优秀的个人博客,低调大师

JavaScript中发布/订阅模式的理解

订阅发布模式的介绍 发布订阅模式,它定义了一种一对多的关系,可以使多个观察者对象对一个主题对象进行监听,当这个主题对象发生改变时,依赖的所有对象都会被通知到。 在生活中我们常常遇到这样一种情况,我们在使用新闻APP看新闻的时候,每个人喜欢的新闻类型各不一样,比如我喜欢NBA,但是我们总不可能一天24小时在手机上一遍又一遍的刷新,我们就会去新闻频道中选择NBA专栏来收藏,当勇士或者湖人有最新消息,就会通知我们去观看。 当然从上面的场景中是一个典型的发布订阅模式,APP的NBA专栏属于发布者,像我一样广大爱好篮球的小伙伴梦就属于订阅者,当一有最新的消息,它们就会发布给我们。 实际用途 1.在jquery中很多地方都有发布订阅的踪迹,例如事件中on和trigger中封装的方法。 2.尤大大的Vue,中子父组件通信使用的emit()和on()方法,使得组件得到解耦,开发更加高效。 如何实现订阅发布模式 1、首先想好谁是发布者(比如上边的APP的NBA专栏就是发布者); 2、然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如上面的我们球迷爱好者收藏了NBA专栏,相当于向发布者注入了通知我们的函数); 3、最后就是发布消息,发布者遍历这个缓存列表,依次触发订阅的函数。 表捉急,端起小板凳,先看一下这个简单的发布订阅模式: let NBAcol={};//自定义一个NBA专栏对象 NBAcol.list=[];// 这里放一个列表用来缓存订阅者的回调函数 NBAcol.on=function(fun){ this.list.push(fun); //把fn先存到列表中 }; //发布事件 NBAcol.emit=function(){ this.list.forEach(cb => { cb.apply(this, arguments); });// 当发布的时候再把列表里存的函数依次执行 }; //小明的订阅NBA专栏 NBAcol.on(function(team){ console.log("我订阅的球队是:"+team) }) //小李的订阅NBA专栏 NBAcol.on(function(team){ console.log("我订阅的球队是:"+team) }) NBAcol.emit('湖人'); NBAcol.emit('勇士'); /* 我订阅的球队是:湖人; 我订阅的球队是:湖人; 我订阅的球队是:勇士; 我订阅的球队是:勇士; */ 上面就实现了一个简单的订阅发布模式,不过从打印结果来看,有些尴尬,因为其实,小明只想订阅湖人,小李要订阅勇士。可是专栏都给他们推送了,显然不太合理。之所以出现这种情况是因为在执行on方法的时候将订阅函数列表中的函数依次都执行了。所以我们要对代码进行改造,我们可以先增加一个key,使订阅者只订阅自己感兴趣的消息。 let NBAcol={};//自定义一个NBA专栏对象 NBAcol.list={};// 这里放一个列表用来缓存订阅者的回调函数 NBAcol.on=function(key,fun){ // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表 if(!this.list[key]){ this.list[key]=[]; } this.list[key].push(fun); //把fn先存到列表中 }; //发布事件 NBAcol.emit=function(){ let key=Array.prototype.shift.call(arguments);// 取出消息类型名称 let funs=this.list[key];//匹配对应的回调函数的结合 if(!funs||funs.length===0){//如果没有订阅过消息,则return; return; }; funs.forEach(fun => { fun.apply(this, arguments); });// 当发布的时候再把列表里存的函数依次执行 }; //小明的订阅NBA专栏 NBAcol.on('xiaomin',function(team){ console.log("我订阅的球队是:"+team) }) //小李的订阅NBA专栏 NBAcol.on('xiaoli',function(team){ console.log("我订阅的球队是:"+team) }) NBAcol.emit('xiaomin','湖人'); NBAcol.emit('xiaoli','勇士'); /* 我订阅的球队是:湖人; 我订阅的球队是:勇士; */ 这样子就可以啦,这个订阅发布的核心功能已经体现了。 如何取消事件的订阅 比如上面的列子,假如我们订阅了很多东西,不喜欢的时候我们要取消订阅,该怎么办呢?看如下代码: NBAcol.remove=function(key, fun) { // 这回我们加入了取消订阅的方法 let funs = this.list[key]; // 如果缓存列表中没有函数,返回false if (!funs) return false; // 如果没有传对应函数的话 // 就会将key值对应缓存列表中的函数都清空掉 if (!fun) { funs && (funs.length = 0); } else { // 遍历缓存列表,看看传入的fun与哪个函数相同 // 如果相同就直接从缓存列表中删掉即可 funs.forEach((cb, i) => { if (cb === fun) { funs.splice(i, 1); } }); } } // 取消dog方法的订阅 NBAcol.remove('xiaoli',function(team){ console.log("我订阅的球队是:"+team) }); 这样就可以取消订阅啦,但是实际的开源代码中,封装远比这要复杂,比如要考虑订阅数量,还有多模块订阅的封装等等,所以在这里我们还得在实际的业务模块中详细考虑。 发布订阅模式的缺点: 当然一个任何一个东西都是有两面性的,同样发布订阅模式存在以下问题: 1、创建订阅者需要消耗一定的时间和内存。 2、虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。 原文发布时间为:2018年06月24日 原文作者:viyoung 本文来源:掘金 如需转载请联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册