常見的API擴展形式
prototype
比如我現在有一個需求,給定一個字串,給方法傳遞一個引數為數字型別來確定當前字串重復次數,例如:
'abc'.repeatStringNumTimes(3) // abcabcabc
如果按照一般的思維就是我們把這個方法系結到String的原型上,如下代碼:
String.prototype.repeatStringNumTimes = String.prototype.repeatStringNumTimes || function(times) { var str = ''; for(var i = 0; i < times; i++) { str += this; } return str; }
jQuery
根據《jQuery高級編程》的描述,jQuery插件開發方式主要有三種:
-
通過$.extend()來擴展jQuery
-
通過$.fn 向jQuery添加新的方法
-
通過$.widget()應用jQuery UI的部件工廠方式創建
通常我們使用第二種方法來進行簡單插件開發,說簡單是相對于第三種方式,第三種方式是用來開發更高級jQuery部件的,該模式開發出來的部件帶有很多jQuery內建的特性,比如插件的狀態資訊自動保存,各種關于插件的常用方法等,非常貼心,這里不細說,
而第一種方式又太簡單,僅僅是在jQuery命名空間或者理解成jQuery身上添加了一個靜態方法而以,所以我們呼叫通過.extend()添加的函數時直接通過.extend()添加的函式時直接通過符號呼叫($.myfunction())而不需要選中DOM元素($('#example').myfunction()),請看下面的例子,
$.extend({ sayHello: function(name) { console.log('Hello,' + (name ? name : 'Dude') + '!'); } }) $.sayHello(); //呼叫 $.sayHello('Wayou'); //帶參呼叫
看一個jquery封裝的面向物件的插件開發代碼:
//定義Beautifier的建構式 var Beautifier = function(ele, opt) { this.$element = ele, this.defaults = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' }, this.options = $.extend({}, this.defaults, opt) } //定義Beautifier的方法 Beautifier.prototype = { beautify: function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }); } } //在插件中使用Beautifier物件 $.fn.myPlugin = function(options) { //創建Beautifier的物體 var beautifier = new Beautifier(this, options); //呼叫其方法 return beautifier.beautify(); }
呼叫方式:
$(function() { $('a').myPlugin({ 'color': '#2C9929', 'fontSize': '20px' }); })
感興趣的可以詳細查看文章:《jQuery插件開發進階》
vue
插件通常會為 Vue 添加全域功能,插件的范圍沒有限制——一般有下面幾種:
- 1.添加全域方法或者屬性,如: vue-custom-element
- 2.添加全域資源:指令/過濾器/過渡等,如 vue-touch
- 3.通過全域 mixin 方法添加一些組件選項,如: vue-router
- 4.添加 Vue 實體方法,通過把它們添加到 Vue.prototype 上實作,
- 5.一個庫,提供自己的 API,同時提供上面提到的一個或多個功能,如 vue-router
Vue.js 的插件應當有一個公開方法 install ,這個方法的第一個引數是 Vue 構造器,第二個引數是一個可選的選項物件:
MyPlugin.install = function (Vue, options) { // 1. 添加全域方法或屬性 Vue.myGlobalMethod = function () { // 邏輯... } // 2. 添加全域資源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 邏輯... } ... }) // 3. 注入組件 Vue.mixin({ created: function () { // 邏輯... } ... }) // 4. 添加實體方法 Vue.prototype.$myMethod = function (methodOptions) { // 邏輯... } } export default MyPlugin
封裝的插件怎樣使用?通過全域方法 Vue.use() 使用插件:
// 呼叫 `MyPlugin.install(Vue)` Vue.use(MyPlugin)
也可以傳入一個選項物件:
Vue.use(MyPlugin, { someOption: true })
實作一個表單驗證
設計表單驗證的規則就是開放-封閉驗證,使用策略模式封裝,
一般方案
我們先寫一個html代碼片段好驗證代碼:
<form action="" id="registerForm" method="post" onsubmit="return submitValidate()"> <p> <label>請輸入用戶名:</label> <input type="text" name="userName" /> </p> <p> <label>請輸入密碼:</label> <input type="text" name="password" /> </p> <p> <label>請輸入手機號碼:</label> <input type="text" name="phoneNumber" /> </p> <div> <button type="submit">提交</button> </div> </form>
在form上系結的submit,想實作的submitValidate方法代碼如下:
function submitValidate() { var registerForm = document.getElementById("registerForm"); var rulesArr = [ { el: registerForm.userName.value, rules: [{rule: 'isNonEmpty',message: '用戶名不能為空'},{rule:'minLength:3',message: '用戶名長度不能小于3位'}] }, { el: registerForm.password.value, rules: [{rule: 'isNonEmpty',message: '密碼不能為空'},{rule:'minLength:6',message: '密碼的長度不能小于6位'}] }, { el: registerForm.phoneNumber.value, rules: [{rule: 'isNonEmpty',message: '手機號不能為空'},{rule:'isMobile',message: '手機號碼格式不正確'}] } ] var resultMsg = validate.check(rulesArr); if(resultMsg) { alert(resultMsg); return false; } return true; }
下面我們撰寫validate驗證方法,代碼如下:
var validate = (function() { // 校驗規則的各種演算法 var rules = { // 判斷非空 isNonEmpty: function(value,errorMsg) { if(!value) { return errorMsg; } }, // 判斷最小長度 minLength: function(value,length,errorMsg) { if(value.toString().length < length) { return errorMsg; } }, // 判斷最大長度 maxLength: function(value,length,errorMsg) { if(value.toString().length > length) { return errorMsg; } }, // 判斷手機號 isMobile: function(value,errorMsg) { if (!/(^1[0-9]{10}$)/.test(value)) { return errorMsg; } }, // 判斷座機電話 isTel: function(value,errorMsg) { if(!/^\d{3}-d{8}|d{4}-d{7}|d{11}$/.test(value)) { return errorMsg; } } } return { /** 校驗方法 * @param {Array} arr * @return {*} * */ check: function(arr) { var ruleMsg; var checkRule; var _rule; for(var i = 0, len = arr.length; i < len; i++) { // 沒有當前校驗欄位 if(arr[i].el === undefined) { return '沒有當前欄位' } for(var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule規則存在minLenth:6這樣的校驗 _rule = checkRule.shift(); // 獲取校驗演算法名稱 checkRule.unshift(arr[i].el); // checkRule首位存入校驗的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校驗的message ruleMsg = rules[_rule].apply(null,checkRule); if(ruleMsg) { return ruleMsg; } } } } } })();
以上代碼就是常規實作的表單驗證,看似也夠用了,但是如果一個系統中有多個表單提交驗證,并且有部分驗證方式不是那么通用,我們不可能再去修改代碼中的rules,那樣這個rules校驗的演算法越來越多,并且很多不是很通用的,那么我們怎樣來解決呢?
在驗證函式中增加一個添加規則演算法的方法,代碼如下:
var validate = (function() { // 校驗規則的各種演算法 var rules = { // 判斷非空 isNonEmpty: function(value,errorMsg) { if(!value) { return errorMsg; } }, // 判斷最小長度 minLength: function(value,length,errorMsg) { if(value.toString().length < length) { return errorMsg; } }, // 判斷最大長度 maxLength: function(value,length,errorMsg) { if(value.toString().length > length) { return errorMsg; } }, // 判斷手機號 isMobile: function(value,errorMsg) { if (!/(^1[0-9]{10}$)/.test(value)) { return errorMsg; } }, // 判斷座機電話 isTel: function(value,errorMsg) { if(!/^\d{3}-d{8}|d{4}-d{7}|d{11}$/.test(value)) { return errorMsg; } } } return { /** 校驗方法 * @param {Array} arr * @return {*} * */ check: function(arr) { var ruleMsg; var checkRule; var _rule; for(var i = 0, len = arr.length; i < len; i++) { // 沒有當前校驗欄位 if(arr[i].el === undefined) { return '沒有當前欄位' } for(var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule規則存在minLenth:6這樣的校驗 _rule = checkRule.shift(); // 獲取校驗演算法名稱 checkRule.unshift(arr[i].el); // checkRule首位存入校驗的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校驗的message ruleMsg = rules[_rule].apply(null,checkRule); if(ruleMsg) { return ruleMsg; } } } }, // 添加規則 addRule: function(ruleName,fn) { rules[ruleName] = fn; } } })();
比如用戶名只能是字母跟數字的組合,那么我們就添加一個規則,代碼如下:
validate.addRule('isAlphaNum', function(value, errorMsg) {
if (/[^a-zA-Z0-9]/.test(value)) {
return errorMsg;
}
})
submitValidate方法的代碼修改為如下:
function submitValidate() { var registerForm = document.getElementById("registerForm"); validate.addRule('isAlphaNum', function(value, errorMsg) { if (/[^a-zA-Z0-9]/.test(value)) { return errorMsg; } }) var rulesArr = [{ el: registerForm.userName.value, rules: [{ rule: 'isNonEmpty', message: '用戶名不能為空' }, { rule: 'minLength:3', message: '用戶名長度不能小于3位' }, { rule: 'isAlphaNum', message: '用戶名只能是數字跟字母的組合' }] }, { el: registerForm.password.value, rules: [{ rule: 'isNonEmpty', message: '密碼不能為空' }, { rule: 'minLength:6', message: '密碼的長度不能小于6位' }] }, { el: registerForm.phoneNumber.value, rules: [{ rule: 'isNonEmpty', message: '手機號不能為空' }, { rule: 'isMobile', message: '手機號碼格式不正確' }] } ] var resultMsg = validate.check(rulesArr); if (resultMsg) { alert(resultMsg); return false; } return true; }
運行效果如圖所示:

升級方案
如果我們想實作如圖這樣的驗證結果:

那么我們就需要保存所有元素的錯誤資訊,那么我們新添加一個checkAll的方法,代碼如下:
var validate = (function() { // 校驗規則的各種演算法 var rules = { // 判斷非空 isNonEmpty: function(value, errorMsg) { if (!value) { return errorMsg; } }, // 判斷最小長度 minLength: function(value, length, errorMsg) { if (value.toString().length < length) { return errorMsg; } }, // 判斷最大長度 maxLength: function(value, length, errorMsg) { if (value.toString().length > length) { return errorMsg; } }, // 判斷手機號 isMobile: function(value, errorMsg) { if (!/(^1[0-9]{10}$)/.test(value)) { return errorMsg; } }, // 判斷座機電話 isTel: function(value, errorMsg) { if (!/^\d{3}-d{8}|d{4}-d{7}|d{11}$/.test(value)) { return errorMsg; } } } return { /** 校驗方法 * @param {Array} arr * @return {*} * */ check: function(arr) { var ruleMsg; var checkRule; var _rule; for (var i = 0, len = arr.length; i < len; i++) { // 沒有當前校驗欄位 if (arr[i].el === undefined) { return '沒有當前欄位' } for (var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule規則存在minLenth:6這樣的校驗 _rule = checkRule.shift(); // 獲取校驗演算法名稱 checkRule.unshift(arr[i].el); // checkRule首位存入校驗的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校驗的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { return ruleMsg; } } } }, // 校驗所有介面 checkAll: function(arr) { var ruleMsg; var checkRule; var _rule; var reusltMsg = []; for (var i = 0, len = arr.length; i < len; i++) { // 沒有當前校驗欄位 if (arr[i].el === undefined) { return '沒有當前欄位' } for (var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule規則存在minLenth:6這樣的校驗 _rule = checkRule.shift(); // 獲取校驗演算法名稱 checkRule.unshift(arr[i].el); // checkRule首位存入校驗的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校驗的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { reusltMsg.push({ el: arr[i].el, rules: _rule, message: ruleMsg, alias: arr[i].alias // 系結一個別名用處:系結到具體的一個DOM元素上顯示錯誤資訊 }) break; // 跳出當前回圈,不用把當前一個元素上多個驗證不通過結果都存盤起來 } } } return reusltMsg.length > 0 ? reusltMsg : false; }, // 添加規則 addRule: function(ruleName, fn) { rules[ruleName] = fn; } } })();
我們調整下html代碼:
<form action="" id="registerForm" method="post" onsubmit="return submitValidate()"> <p> <label>請輸入用戶名:</label> <input type="text" name="userName" /> <span class="error"></span> </p> <p> <label>請輸入密碼:</label> <input type="text" name="password" /> <span class="error"></span> </p> <p> <label>請輸入手機號碼:</label> <input type="text" name="phoneNumber" /> <span class="error"></span> </p> <div> <button type="submit">提交</button> </div> </form>
css樣式:
<style> .error { color: red; } </style>
submitValidate方法的代碼調整為:
function submitValidate() { var registerForm = document.getElementById("registerForm"); validate.addRule('isAlphaNum', function(value, errorMsg) { if (/[^a-zA-Z0-9]/.test(value)) { return errorMsg; } }) var rulesArr = [{ el: registerForm.userName.value, alias: 'userName', rules: [{ rule: 'isNonEmpty', message: '用戶名不能為空' }, { rule: 'minLength:3', message: '用戶名長度不能小于3位' }, { rule: 'isAlphaNum', message: '用戶名只能是數字跟字母的組合' }] }, { el: registerForm.password.value, alias: 'password', rules: [{ rule: 'isNonEmpty', message: '密碼不能為空' }, { rule: 'minLength:6', message: '密碼的長度不能小于6位' }] }, { el: registerForm.phoneNumber.value, alias: 'phoneNumber', rules: [{ rule: 'isNonEmpty', message: '手機號不能為空' }, { rule: 'isMobile', message: '手機號碼格式不正確' }] } ] var resultMsg = validate.checkAll(rulesArr); if (resultMsg) { for(var re = 0, len = resultMsg.length; re < len; re++) { var curResult = resultMsg[re]; var errorDom = document.querySelector('#registerForm p [name="'+curResult.alias+'"]').nextElementSibling; errorDom.innerHTML = curResult.message; } return false; } return true; }
這樣得到的結果就是我們剛才截圖的結果了,
參考地址
- JS策略模式參考-《[JS設計模式]:策略模式及應用-計算獎金、表單驗證的實作(5)》
- 重構 - 設計API的擴展機制
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/170572.html
標籤:JavaScript
下一篇:JS常見的API擴展形式(prototype、jquery、vue插件封裝)以及怎樣設計出易擴展的表單驗證功能?
