关于javascript:5分钟即可掌握的前端高效利器JavaScript-策略模式

35次阅读

共计 5487 个字符,预计需要花费 14 分钟才能阅读完成。

浅谈 JavaScript 中策略模式的应用:

  • 什么是设计模式
  • 什么是策略模式
  • 策略模式在 JavaScript 中的利用(应用策略模式封装百度 AI 辨认调用)
  • 策略模式在 Vue 组件封装中的利用(应用策略模式封装 Select 组件)

什么是设计模式

构想有一个电子爱好者,尽管他没有通过正规的培训,然而却与日俱增地设计并制作出了许多有用的电子设备:业余无线电、盖革计数器、报警器等。有一天这个爱好者决定从新回到学校去攻读电子学学位,来让本人的能力失去正式的认可。随着课程的开展,这个爱好者忽然发现课程内容都似曾相识。似曾相识的不是术语或表述的形式,而是背地的概念。这个爱好者一直学到一些名称和原理,尽管这些名称和原理原来他并不知道,但事实上他多年以来始终都在应用。整个过程只不过是一个接一个的顿悟。

设计模式深思录,John Vlissides,第一章 1.2 节

咱们在写代码的时候,肯定也遇到过许多相似的场景。随着教训的减少,咱们对于这些常见场景的解决越来越得心应手,甚至总结出了针对性的“套路”,下次遇到此类问题间接使用“套路”解决,省心又省力。这些在软件开发过程中逐步积攒下来的“套路”就是设计模式。

设计模式的指标之一就是进步代码的可复用性、可扩展性和可维护性。正因如此,尽管有时候咱们不晓得某个设计模式,然而看了相干书籍或文章后会有一种“啊,原来这就是设计模式”的恍然大明确。

如果你看完这篇文章后也有此感觉,那么祝贺你,你曾经在高效程序员的路线上一路狂奔了。

什么是策略模式

策略模式是一种简略却罕用的设计模式,它的利用场景十分宽泛。咱们先理解下策略模式的概念,再通过代码示例来更清晰的意识它。

策略模式由两局部形成:一部分是封装不同策略的策略组,另一部分是 Context。通过组合和委托来让 Context 领有执行策略的能力,从而实现可复用、可扩大和可保护,并且防止大量复制粘贴的工作。

策略模式的典型利用场景是表单校验中,对于校验规定的封装。接下来咱们就通过一个简略的例子具体理解一下:

毛糙的表单校验

一个常见的登录表单代码如下:

<form id='login-form' action=""method="post">
    <label for="account"> 手机号 </label>
    <input type="number" id="account" name="account">
    <label for="password"> 明码 </label>
    <input type="password" id="password" name="password">
    <button id='login'> 登录 </button>
</form>
<script>
    var loginForm = document.getElementById('login-form');

    loginForm.onsubmit = function (e) {e.preventDefault();  
        var account = document.getElementById("account").value;
        var pwd = document.getElementById("password").value;

        if(account===null||account===''){alert('手机号不能为空');return false;
        }
        if(pwd===null||pwd===''){alert('明码不能为空');return false;
        }
        if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(account)) {alert('手机号格局谬误');return false;
        }
        if(pwd.length<6){alert('明码不能小于六位');return false;
        }
        // ajax 发送申请
    }
</script>

以上代码,尽管性能没问题,然而毛病也很显著:

代码里遍地都是 if 语句,并且它们不足弹性:每新增一种、或者批改原有校验规定,咱们都必须去改 loginForm.onsubmit 外部的代码。另外逻辑的复用性也很差:如果有其它表单也是用同样的规定,这段代码并不能复用,只能复制。当校验规定发生变化时,比方上文的正则校验并不能匹配虚构运营商 14/17 号段,咱们就须要手动同步多处代码变更(Ctrl+C/Ctrl+V)。

优良的表单验证

接下来咱们通过策略模式的思路改写一下上段代码,首先抽离并封装校验逻辑为策略组:

var strategies = {isNonEmpty: function (value, errorMsg) {if (value === '' || value === null) {return errorMsg;}
    },
    isMobile: function (value, errorMsg) { // 手机号码格局
        if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(value)) {return errorMsg;}
    },
    minLength: function (value, length, errorMsg) {if (value.length < length) {return errorMsg;}
    }
};

接下来批改 Context:

var loginForm = document.getElementById('login-form');

loginForm.onsubmit = function (e) {e.preventDefault(); 
    var accountIsMobile = strategies.isMobile(account,'手机号格局谬误');
    var pwdMinLength = strategies.minLength(pwd,8,'明码不能小于 8 位');
    var errorMsg = accountIsMobile||pwdMinLength; 
    if(errorMsg){alert(errorMsg);
        return false;
    }
}

比照两种实现,咱们能够看到:拆散了校验逻辑的代码如果须要扩大校验类型,在策略组中新增定义即可应用;如果须要批改某个校验的实现,间接批改相应策略即可全局失效。对于开发和保护都有显著的效率晋升。

扩大:史诗的表单校验

有趣味的敌人能够理解下 async-validator,element-ui 和 antd 的表单校验都是基于 async-validator 封装的,能够说是史诗级别的表单校验了

通过表单校验的比照,置信大家都对策略模式有所理解,那么接下来通过两个例子具体理解下 JavaScript 中策略模式的利用:

应用策略模式调用百度 AI 图像识别

因为百度 AI 图像识别的接口类型不同,所需的参数格局也不尽相同。然而图像的压缩及上传、错误处理等局部是专用的。所以能够采纳策略模式封装:

定义策略组

通过定义策略组来封装不同的接口及其参数:比方身份证辨认接口的 side 字段,自定义辨认的 templateSign 字段,以及行驶证辨认的接管参数为poparamstData

/**
 * 策略组
 * IDCARD: 身份证辨认
 * CUSTOMIZED: 自定义辨认
 * VL: 行驶证辨认
 */
var strategies = {IDCARD: function (base64) {
        return {
            path: 'idcard',
            param: {
                'side': 'front',
                'base64': base64
            }
        };
    },

    CUSTOMIZED: function (base64) {
        return {
            path: 'customized',
            param: {
                'templateSign': '52cc2d402155xxxx',
                'base64': base64
            }
        };
    },
    VL: function (base64) {
        return {
            path: 'vehicled',
            poparamstData: {'base64': base64}
        };
    },
};

定义 Context

var ImageReader = function () {};

/**
 * 读取图像, 调用接口, 获取辨认后果
 * 
 * @param {*} type 待辨认文件类型
 * @param {*} base64 待辨认文件 BASE64 码
 * @param {*} callBack 辨认后果回调
 */
ImageReader.prototype.getOcrResult = function (type, base64, callBack) {let fileSize = (base64.length / (1024 * 1024)).toFixed(2);
    let compressedBase64 = '';
    let image = new Image();
    image.src = base64;
    image.onload = function () {
        /**
         * 图片压缩解决及异样解决,代码略
         */
         

        let postData = strategies[type](compressedBase64);

        ajax(
            host + postData.path, {
                data: postData.param,
                type: 'POST',
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                success: function (res) {var data = JSON.parse(res);
                    // 裸露给 UI 层的对立的错误码
                    if (data.error_code !== undefined && data.error_code !== 0) {
                        var errorData = {
                            error: 1,
                            title: '谬误' + data.error_code,
                            content: 'error message'
                        };
                        callBack(errorData);
                    } else {callBack(data);
                    }
                }
            });
    };
};

调用形式

var imageReader = new ImageReader();
imageReader.getOcrResult('IDCARD', this.result.toString(), callback);

应用策略模式封装 Vue Select 组件

某我的项目中多处用到了 element-ui 的 select 组件,其内在逻辑相似,都是初始化时获取下拉列表的数据源,而后在选中某一项时 dispatch 不同的 action。遂思考应用策略模式封装。

Context

在本例中,组件向内部裸露一个 prop,调用方指定该 prop 从而加载不同的策略。那么定义 Context 如下:

<template>
  <el-select v-model="selectedValue" placeholder="请抉择" @change="optionChanged" size="mini" clearable>
    <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id">
    </el-option>
  </el-select>
</template>
data() {
    return {
      selectedValue: undefined,
      options: [],
      action: "",
    };
  },
  props: {
    // 裸露给内部的 select-type
    selectType: {type: String},
  },
  created() {
   // 获取 options
   this.valuation();},
    methods: {optionChanged() {this.$emit(this.action, this.selectedValue);
    },
    setOptions(option) {this.$store.dispatch(this.action, option);
    },
    valuation() {// 获取 options 数据}
  },

内部通过如下形式调用组件:

<MySelect selectType="product"/>

strategies

而后定义策略组:

let strategies = {
    source: {
        action: "sourceOption",
        getOptions:  function() {// 拉取 options}
    },
    product: {
        action: "productOption",
        getOptions:  function() {// 拉取 options}
    },
    ...
}

异步

至此该组件的根本构造曾经清晰,但还存在一个问题:组件加载时是异步拉取的 options,而页面初始化的时候很可能 options 还没有返回,导致 select 的 options 仍为空。所以此处应该批改代码,同步获取 options:

// 策略组批改
source: {
    action: "sourceOption",
    getOptions: async function() {// await 拉取 options}
  },
// 组件批改
methods: {
    ...
    async valuation() {...}
}

持续优化

但咱们不是每次加载组件都须要拉取 options,如果这些 options 在其余组件或者页面也被应用到,那么能够思考将其存入 vuex 中。

最开始的思路是高阶组件,即定义一个包装后的 select 模板, 通过高阶组件的形式扩大其数据源与 action(变动的局部)然而这个思路不是那么的 vue(次要是 slots 不太好解决)于是思考策略模式改写该组件

总结

通过以上两个例子,咱们能够看到:

  • 策略模式合乎凋谢 - 关闭准则
  • 如果代码里须要写大量的 if-else 语句,那么思考应用策略模式
  • 如果多个组件(类)之间的区别仅在于它们的行为,思考采纳策略模式

参考
JavaScript 设计模式与开发实际(曾探) 第五章 策略模式

正文完
 0