常見的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; }
這樣得到的結果就是我們剛才截圖的結果了。
兼容失去焦點的方案
如果想兼容失去焦點也觸發后面的錯誤信息提示,暫寫了一個草稿代碼如下:
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 {Object} vertifyObj 驗證結構如:{userName:[{rule: 'isNonEmpty',message: '用戶名不能為空'},{rule: 'minLength:3',message: '用戶名長度不能小於3位'}} * @param {Array} arr * @return {*} * */ check: function(vertifyObj,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 = vertifyObj[arr[i].alias].length; j < ruleLen; j++) { var ruleObj = vertifyObj[arr[i].alias][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(vertifyObj,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 = vertifyObj[arr[i].alias].length; j < ruleLen; j++) { var ruleObj = vertifyObj[arr[i].alias][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; }, // 用戶觸發驗證事件 trigger: function(params) { var self = this; for(var key in params.rules) { if(params.rules.hasOwnProperty(key)) { var requireEl = document.querySelector(params.el + ' [name="'+key+'"]'); var rulesArr = params.rules[key]; var resultRules = rulesArr.filter(function(rule) { if(!rule.trigger || rule.trigger === '') return true; if(Array.isArray(rule.trigger)) { return rule.trigger.indexOf('blur') > -1 } else { return rule.trigger === 'blur'; } }).map(function(rule){return JSON.parse(JSON.stringify(rule))}); (function(dom,curDomRules){ dom.addEventListener('blur',function(event){ var val = dom.value; var ruleMsg = ''; for (var j = 0, ruleLen = curDomRules.length; j < ruleLen; j++) { var ruleObj = curDomRules[j]; var checkRule = ruleObj.rule.split(':'); // rule規則存在minLenth:6這樣的校驗 var _rule = checkRule.shift(); // 獲取校驗算法名稱 checkRule.unshift(val); // checkRule首位存入校驗的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校驗的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { var errorDom = dom.nextElementSibling; errorDom.innerHTML = ruleObj.message; break; // 跳出當前循環,不用把當前一個元素上多個驗證不通過結果都存儲起來 } } if(!ruleMsg) { var errorDom = dom.nextElementSibling; errorDom.innerHTML = ''; } },false); })(requireEl,resultRules); } } }, // 添加規則 addRule: function(ruleName, fn) { rules[ruleName] = fn; } } })(); // 添加-自定義校驗算法 validate.addRule('isAlphaNum', function(value, errorMsg) { if (/[^a-zA-Z0-9]/.test(value)) { return errorMsg; } }) var rules = { userName: [ {rule: 'isNonEmpty',message: '用戶名不能為空', trigger: 'blur'}, {rule: 'minLength:3',message: '用戶名長度不能小於3位', trigger: 'blur'}, {rule: 'isAlphaNum',message: '用戶名只能是數字跟字母的組合', trigger: 'blur'} ], password: [ {rule: 'isNonEmpty',message: '密碼不能為空', trigger: 'blur'}, {rule: 'minLength:6',message: '密碼的長度不能小於6位', trigger: 'blur'} ], phoneNumber: [ {rule: 'isNonEmpty',message: '手機號不能為空', trigger: 'blur'}, {rule: 'isMobile',message: '手機號碼格式不正確', trigger: 'blur'} ] } validate.trigger({ el: '#registerForm', rules: rules }) function submitValidate() { var registerForm = document.getElementById("registerForm"); var rulesArr = [{ el: registerForm.userName.value, alias: 'userName' }, { el: registerForm.password.value, alias: 'password' }, { el: registerForm.phoneNumber.value, alias: 'phoneNumber' } ] var resultMsg = validate.checkAll(rules,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; }
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>