简单逻辑判断
简单的逻辑判断可以使用这几种方式来编写:
- if / else
- &&
- ||
- ?:
if / else
在简单逻辑中,多用于只有两个条件:是或否的判断。
单项成立需要处理时:
function(a) {if(!a) return;}// 多于一条的处理if(xx) {//...//...}
多项需要处理时:
// 多于一条的处理if (xxx) {//...} else {//...}
&& 和 || 的短路操作
- && 当前面条件为 true 时,才执行后面的
- || 当前面条件为 false 时,才会执行后面的
只处理逻辑中的单项成立需要做的事情:
cb && cb();a = a || 1;
三元运算符 ?:
在简单逻辑操作中,多用于赋值,以及一条语句的执行。
a = b == "1" ? a : "0";// 根据某个条件判断其值a === condition ? fn() : null;// 也可以用 && 代替a === condition ? fn1() : fn2;// 多条语句执行a === condition ? (fn1(), fn2()) : null;
复杂逻辑判断
- if/else
- switch
- 一元判断时:存到 Object 里
- 一元判断时:存到 Map 里
- 多元判断时:将 condition 拼接成字符串存到 Object 里
- 多元判断时:将 condition 拼接成字符串存到 Map 里
- 多元判断时:将 condition 存为 Object 存到 Map 里
- 多元判断时:将 condition 写作正则存到 Map 里
我们进行复杂逻辑判断的时候,通常情况下,最快想到的办法就是使用 if/else。但是随着逻辑复杂度的增加,代码中的 if/else 会变得越来越臃肿,越来越看不懂。
if (status == 1) {sendLog('processing') jumpTo('IndexPage')} else if (status == 2) {sendLog('fail') jumpTo('FailPage')} else if (status == 3) {sendLog('fail') jumpTo('FailPage')} else if (status == 4) {sendLog('success') jumpTo('SuccessPage')} else if (status == 5) {sendLog('cancel') jumpTo('CancelPage')} else {sendLog('other') jumpTo('Index')}
我们可以使用 switch 进行一定的优化,通过 case 来合并完全相同的处理逻辑:
switch (status) {case 1:sendLog('processing')jumpTo('IndexPage')breakcase 2:case 3:sendLog('fail')jumpTo('FailPage')breakcase 4:sendLog('success')jumpTo('SuccessPage')breakcase 5:sendLog('cancel')jumpTo('CancelPage')breakdefault:sendLog('other')jumpTo('Index')break}
但是这仍然无法避免其写的那些重复代码。通常情况下,我们应该尽可能使用函数进行公共逻辑的抽取,但是不同的判断条件下,通常具有类似的处理代码,此时我们应该尽可能封装逻辑判断条件,将相似代码进行抽象化。
封装逻辑判断条件不仅可以减少代码量,写出优雅的代码,而且最主要的是,能够极大提升其易读性和可维护性。后期想要增加或减少判断条件就会轻而易举。
一元判断封装
一元判断的封装是我们最常使用到的。有三种方法进行封装:
- 数组
- 对象
- Map
(1)数组
在顺序数字的逻辑判断中,比如 0 代表失败,1 代表成功,这通常会出现在表格的数据转化中,这时最好的方法就是运用数组进行封装。
// if/elseif (status === 1) {status = '成功'} else {// 这里只有两种情况status = '失败'}// 使用数组进行封装var statusMap = ['失败', '成功'];status = statusMap[status];
但是这种方式只适用于其值为数字,且是连续的数字的情况。如果是字符串或者布尔值等,就需要使用下面两种方法。
(2)对象
在一元逻辑判断中最常使用的就是对象封装。如 success 表示成功,fail 表示失败。
// if/elseif (status === 'success') {status = '成功'} else {// 这里只有两种情况status = '失败'}// 使用对象进行封装var statusMap = {'fail': '失败','success': '成功'};status = statusMap[status];
前面 if/else 的复杂判断就可以使用对象进行改写。
const actions = {'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 系统取消*/const onButtonClick = (status) => {let action = actions[status] || actions['default'],logName = action[0],pageName = action[1];sendLog(logName);jumpTo(pageName);}
(3)Map
使用 Map 其实与使用对象的相似的。
// 使用 Map 进行封装var statusMap = new Map([ // Map 构造函数支持传入一个二维数组进行初始化['fail', '失败'], // 键值对数组['success', '成功]']);status = statusMap.get(status);
来看看前面 if/else 例子的改写:
const actions = new Map([[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 系统取消*/const onButtonClick = (status) => {let action = actions.get(status) || actions.get('default')sendLog(action[0])jumpTo(action[1])}
Map 相对于对象而言,有哪些优势?
- 对象的键只能是字符串或者 Symbols,但 Map 的键可以是任意值
- 可以通过 size 属性很容易地得到一个 Map 的键值对个数,而对象的键值对个数只能手动确认
这些优势更能体现在多元逻辑判断中。
多元逻辑判断
(1)字符串拼接
字符串拼接的方式适用于对象和 Map。其原理在于将多个逻辑判断条件使用特殊方式进行拼接,变成一个判断条件。
const actions = {'guest_1': () => { /*do sth*/ }, // 小技巧:使用一个匿名函数进行不同逻辑的整理'guest_2': () => { /*do sth*/ }, // 这些函数还可以进行传参//....}const onButtonClick = (identity, status) => {let action = actions[`${identity}_${status}`] || actions['default']action.call(this)}
Map 就不再赘余。
(2)存为对象
利用 Map 的键可以是任意值的特性,将逻辑判断变成对象存入 Map 中。
const actions = new Map([[{identity: 'guest',status: 1}, () => { /*do sth*/ }],[{identity: 'guest',status: 2}, () => { /*do sth*/ }],//...])const onButtonClick = (identity, status) => {// 注意对象是引用类型,地址不同则其值不同,不能直接取值,需要一定判断let action = [...actions].filter(([key, value]) => (key.identity == identity && key.status == status))action.forEach(([key, value]) => value.call(this))}
(3)存为正则
当逻辑判断条件中,具有多个相同或者相似处理逻辑时,我们可以将其封装成一个函数进行缓存:
const actions = () => { // 小技巧:使用函数来返回一个对象或Mapconst functionA = () => { /*do sth*/ }// 缓存处理逻辑函数,可以通过传递参数进行相似逻辑的处理const functionB = () => { /*do sth*/ }return new Map([[{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],//...])}const onButtonClick = (identity, status) => {let action = [...actions()].filter(([key, value]) => (key.identity == identity && key.status == status))action.forEach(([key, value]) => value.call(this))}
但是在逻辑判断的封装中依然显得有所冗余,最好的方式是可以将相同处理逻辑的逻辑判断进行合并。
此时就需要使用到 Map 的特性了,我们可以利用字符串拼接以及正则来完成。
const actions = () => {const functionA = () => { /*do sth*/ }const functionB = () => { /*do sth*/ }const functionC = () => { /*send log*/ }return new Map([[/^guest_[1-4]$/, functionA],[/^guest_5$/, functionB],[/^guest_.*$/, functionC],//...])}const onButtonClick = (identity, status) => {let action = [...actions()].filter(([key, value]) => (key.test(`${identity}_${status}`)))action.forEach(([key, value]) => value.call(this))}
利用数组循环的特性,符合正则条件的逻辑都会被执行,那就可以同时执行公共逻辑和单独逻辑,因为正则的存在,你可以处理更多复杂的情形。
模拟开闭区间
function checkRanges(num, ranges) {function handle(num, range, type) {switch (type) {case '[':return num >= range;case '(':return num > range;case ']':return num <= range;case ')':return num < range;}}let _ranges = ranges.split(/,(?=[\[\(])/g);console.log(_ranges);let nowRange = [];return _ranges.some((item) => {nowRange = item.match(/[\(\)\[\]]|(\-?(Infinity|0|([1-9]\d*))(\.\d+)?)/g);return (handle(num, +nowRange[1], nowRange[0]) &&handle(num, nowRange[2], nowRange[3]));});}
