简单逻辑判断
简单的逻辑判断可以使用这几种方式来编写:
- 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')
break
case 2:
case 3:
sendLog('fail')
jumpTo('FailPage')
break
case 4:
sendLog('success')
jumpTo('SuccessPage')
break
case 5:
sendLog('cancel')
jumpTo('CancelPage')
break
default:
sendLog('other')
jumpTo('Index')
break
}
但是这仍然无法避免其写的那些重复代码。通常情况下,我们应该尽可能使用函数进行公共逻辑的抽取,但是不同的判断条件下,通常具有类似的处理代码,此时我们应该尽可能封装逻辑判断条件,将相似代码进行抽象化。
封装逻辑判断条件不仅可以减少代码量,写出优雅的代码,而且最主要的是,能够极大提升其易读性和可维护性。后期想要增加或减少判断条件就会轻而易举。
一元判断封装
一元判断的封装是我们最常使用到的。有三种方法进行封装:
- 数组
- 对象
- Map
(1)数组
在顺序数字的逻辑判断中,比如 0 代表失败,1 代表成功,这通常会出现在表格的数据转化中,这时最好的方法就是运用数组进行封装。
// if/else
if (status === 1) {
status = '成功'
} else {// 这里只有两种情况
status = '失败'
}
// 使用数组进行封装
var statusMap = ['失败', '成功'];
status = statusMap[status];
但是这种方式只适用于其值为数字,且是连续的数字的情况。如果是字符串或者布尔值等,就需要使用下面两种方法。
(2)对象
在一元逻辑判断中最常使用的就是对象封装。如 success 表示成功,fail 表示失败。
// if/else
if (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 = () => { // 小技巧:使用函数来返回一个对象或Map
const 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])
);
});
}