简单逻辑判断

简单的逻辑判断可以使用这几种方式来编写:

  • if / else
  • &&
  • ||
  • ?:

if / else

在简单逻辑中,多用于只有两个条件:是或否的判断。

单项成立需要处理时:

  1. function(a) {
  2. if(!a) return;
  3. }
  4. // 多于一条的处理
  5. if(xx) {
  6. //...
  7. //...
  8. }

多项需要处理时:

  1. // 多于一条的处理
  2. if (xxx) {
  3. //...
  4. } else {
  5. //...
  6. }

&& 和 || 的短路操作

  • && 当前面条件为 true 时,才执行后面的
  • || 当前面条件为 false 时,才会执行后面的

只处理逻辑中的单项成立需要做的事情:

  1. cb && cb();
  2. a = a || 1;

三元运算符 ?:

在简单逻辑操作中,多用于赋值,以及一条语句的执行。

  1. a = b == "1" ? a : "0";// 根据某个条件判断其值
  2. a === condition ? fn() : null;// 也可以用 && 代替
  3. a === condition ? fn1() : fn2;
  4. // 多条语句执行
  5. a === condition ? (fn1(), fn2()) : null;

复杂逻辑判断

  • if/else
  • switch
  • 一元判断时:存到 Object 里
  • 一元判断时:存到 Map 里
  • 多元判断时:将 condition 拼接成字符串存到 Object 里
  • 多元判断时:将 condition 拼接成字符串存到 Map 里
  • 多元判断时:将 condition 存为 Object 存到 Map 里
  • 多元判断时:将 condition 写作正则存到 Map 里

我们进行复杂逻辑判断的时候,通常情况下,最快想到的办法就是使用 if/else。但是随着逻辑复杂度的增加,代码中的 if/else 会变得越来越臃肿,越来越看不懂。

  1. if (status == 1) {
  2. sendLog('processing') jumpTo('IndexPage')
  3. } else if (status == 2) {
  4. sendLog('fail') jumpTo('FailPage')
  5. } else if (status == 3) {
  6. sendLog('fail') jumpTo('FailPage')
  7. } else if (status == 4) {
  8. sendLog('success') jumpTo('SuccessPage')
  9. } else if (status == 5) {
  10. sendLog('cancel') jumpTo('CancelPage')
  11. } else {
  12. sendLog('other') jumpTo('Index')
  13. }

我们可以使用 switch 进行一定的优化,通过 case 来合并完全相同的处理逻辑:

  1. switch (status) {
  2. case 1:
  3. sendLog('processing')
  4. jumpTo('IndexPage')
  5. break
  6. case 2:
  7. case 3:
  8. sendLog('fail')
  9. jumpTo('FailPage')
  10. break
  11. case 4:
  12. sendLog('success')
  13. jumpTo('SuccessPage')
  14. break
  15. case 5:
  16. sendLog('cancel')
  17. jumpTo('CancelPage')
  18. break
  19. default:
  20. sendLog('other')
  21. jumpTo('Index')
  22. break
  23. }

但是这仍然无法避免其写的那些重复代码。通常情况下,我们应该尽可能使用函数进行公共逻辑的抽取,但是不同的判断条件下,通常具有类似的处理代码,此时我们应该尽可能封装逻辑判断条件,将相似代码进行抽象化

封装逻辑判断条件不仅可以减少代码量,写出优雅的代码,而且最主要的是,能够极大提升其易读性和可维护性。后期想要增加或减少判断条件就会轻而易举。

一元判断封装

一元判断的封装是我们最常使用到的。有三种方法进行封装:

  • 数组
  • 对象
  • Map

(1)数组

在顺序数字的逻辑判断中,比如 0 代表失败,1 代表成功,这通常会出现在表格的数据转化中,这时最好的方法就是运用数组进行封装。

  1. // if/else
  2. if (status === 1) {
  3. status = '成功'
  4. } else {// 这里只有两种情况
  5. status = '失败'
  6. }
  7. // 使用数组进行封装
  8. var statusMap = ['失败', '成功'];
  9. status = statusMap[status];

但是这种方式只适用于其值为数字,且是连续的数字的情况。如果是字符串或者布尔值等,就需要使用下面两种方法。

(2)对象

在一元逻辑判断中最常使用的就是对象封装。如 success 表示成功,fail 表示失败。

  1. // if/else
  2. if (status === 'success') {
  3. status = '成功'
  4. } else {// 这里只有两种情况
  5. status = '失败'
  6. }
  7. // 使用对象进行封装
  8. var statusMap = {
  9. 'fail': '失败',
  10. 'success': '成功'
  11. };
  12. status = statusMap[status];

前面 if/else 的复杂判断就可以使用对象进行改写。

  1. const actions = {
  2. '1': ['processing', 'IndexPage'], // 小技巧:将每个条件执行需要的特殊字段封装成一个数组
  3. '2': ['fail', 'FailPage'],
  4. '3': ['fail', 'FailPage'],
  5. '4': ['success', 'SuccessPage'],
  6. '5': ['cancel', 'CancelPage'],
  7. 'default': ['other', 'Index'],
  8. }
  9. /**
  10. * 按钮点击事件
  11. * @param {number} status 活动状态:1开团进行中 2开团失败 3 商品售罄 4 开团成功 5 系统取消
  12. */
  13. const onButtonClick = (status) => {
  14. let action = actions[status] || actions['default'],
  15. logName = action[0],
  16. pageName = action[1];
  17. sendLog(logName);
  18. jumpTo(pageName);
  19. }

(3)Map

使用 Map 其实与使用对象的相似的。

  1. // 使用 Map 进行封装
  2. var statusMap = new Map([ // Map 构造函数支持传入一个二维数组进行初始化
  3. ['fail', '失败'], // 键值对数组
  4. ['success', '成功]'
  5. ]);
  6. status = statusMap.get(status);

来看看前面 if/else 例子的改写:

  1. const actions = new Map([
  2. [1, ['processing', 'IndexPage']],
  3. [2, ['fail', 'FailPage']],
  4. [3, ['fail', 'FailPage']],
  5. [4, ['success', 'SuccessPage']],
  6. [5, ['cancel', 'CancelPage']],
  7. ['default', ['other', 'Index']]
  8. ])
  9. /**
  10. * 按钮点击事件
  11. * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
  12. */
  13. const onButtonClick = (status) => {
  14. let action = actions.get(status) || actions.get('default')
  15. sendLog(action[0])
  16. jumpTo(action[1])
  17. }

Map 相对于对象而言,有哪些优势?

  • 对象的键只能是字符串或者 Symbols,但 Map 的键可以是任意值
  • 可以通过 size 属性很容易地得到一个 Map 的键值对个数,而对象的键值对个数只能手动确认

这些优势更能体现在多元逻辑判断中。

多元逻辑判断

(1)字符串拼接

字符串拼接的方式适用于对象和 Map。其原理在于将多个逻辑判断条件使用特殊方式进行拼接,变成一个判断条件。

  1. const actions = {
  2. 'guest_1': () => { /*do sth*/ }, // 小技巧:使用一个匿名函数进行不同逻辑的整理
  3. 'guest_2': () => { /*do sth*/ }, // 这些函数还可以进行传参
  4. //....
  5. }
  6. const onButtonClick = (identity, status) => {
  7. let action = actions[`${identity}_${status}`] || actions['default']
  8. action.call(this)
  9. }

Map 就不再赘余。

(2)存为对象

利用 Map 的键可以是任意值的特性,将逻辑判断变成对象存入 Map 中。

  1. const actions = new Map([
  2. [{
  3. identity: 'guest',
  4. status: 1
  5. }, () => { /*do sth*/ }],
  6. [{
  7. identity: 'guest',
  8. status: 2
  9. }, () => { /*do sth*/ }],
  10. //...
  11. ])
  12. const onButtonClick = (identity, status) => {
  13. // 注意对象是引用类型,地址不同则其值不同,不能直接取值,需要一定判断
  14. let action = [...actions].filter(([key, value]) => (key.identity == identity && key.status == status))
  15. action.forEach(([key, value]) => value.call(this))
  16. }

(3)存为正则

当逻辑判断条件中,具有多个相同或者相似处理逻辑时,我们可以将其封装成一个函数进行缓存:

  1. const actions = () => { // 小技巧:使用函数来返回一个对象或Map
  2. const functionA = () => { /*do sth*/ }// 缓存处理逻辑函数,可以通过传递参数进行相似逻辑的处理
  3. const functionB = () => { /*do sth*/ }
  4. return new Map([
  5. [{
  6. identity: 'guest',
  7. status: 1
  8. }, functionA],
  9. [{
  10. identity: 'guest',
  11. status: 2
  12. }, functionA],
  13. [{
  14. identity: 'guest',
  15. status: 3
  16. }, functionA],
  17. [{
  18. identity: 'guest',
  19. status: 4
  20. }, functionA],
  21. [{
  22. identity: 'guest',
  23. status: 5
  24. }, functionB],
  25. //...
  26. ])
  27. }
  28. const onButtonClick = (identity, status) => {
  29. let action = [...actions()].filter(([key, value]) => (key.identity == identity && key.status == status))
  30. action.forEach(([key, value]) => value.call(this))
  31. }

但是在逻辑判断的封装中依然显得有所冗余,最好的方式是可以将相同处理逻辑的逻辑判断进行合并。

此时就需要使用到 Map 的特性了,我们可以利用字符串拼接以及正则来完成。

  1. const actions = () => {
  2. const functionA = () => { /*do sth*/ }
  3. const functionB = () => { /*do sth*/ }
  4. const functionC = () => { /*send log*/ }
  5. return new Map([
  6. [/^guest_[1-4]$/, functionA],
  7. [/^guest_5$/, functionB],
  8. [/^guest_.*$/, functionC],
  9. //...
  10. ])
  11. }
  12. const onButtonClick = (identity, status) => {
  13. let action = [...actions()].filter(([key, value]) => (key.test(`${identity}_${status}`)))
  14. action.forEach(([key, value]) => value.call(this))
  15. }

利用数组循环的特性,符合正则条件的逻辑都会被执行,那就可以同时执行公共逻辑和单独逻辑,因为正则的存在,你可以处理更多复杂的情形。

模拟开闭区间

  1. function checkRanges(num, ranges) {
  2. function handle(num, range, type) {
  3. switch (type) {
  4. case '[':
  5. return num >= range;
  6. case '(':
  7. return num > range;
  8. case ']':
  9. return num <= range;
  10. case ')':
  11. return num < range;
  12. }
  13. }
  14. let _ranges = ranges.split(/,(?=[\[\(])/g);
  15. console.log(_ranges);
  16. let nowRange = [];
  17. return _ranges.some((item) => {
  18. nowRange = item.match(/[\(\)\[\]]|(\-?(Infinity|0|([1-9]\d*))(\.\d+)?)/g);
  19. return (
  20. handle(num, +nowRange[1], nowRange[0]) &&
  21. handle(num, nowRange[2], nowRange[3])
  22. );
  23. });
  24. }