也许有同学会疑问,为什么可以在此声明plugin变量?实际上js的解释执行,会把所有声明都提前。如果一个变量已经声明过,后面如果不是在函数内声明的,则是没有影响的。所以,就算在别的地方声明过var plugin,我同样也以可以在这里再次声明一次。关于声明的相关资料可以看阮一锋的如何判断Javascript对象是否存在。
基本上,这就可以算是一个插件了。解决了全局污染问题,方法函数可以抽出来放到一单独的文件里面去。
利用闭包包装
上面的例子,虽然可以实现了插件的基本上的功能。不过我们的plugin对象,是定义在全局域里面的。我们知道,js变量的调用,从全局作用域上找查的速度会比在私有作用域里面慢得多得多。所以,我们最好将插件逻辑写在一个私有作用域中。
实现私有作用域,最好的办法就是使用闭包。可以把插件当做一个函数,插件内部的变量及函数的私有变量,为了在调用插件后依旧能使用其功能,闭包的作用就是延长函数(插件)内部变量的生命周期,使得插件函数可以重复调用,而不影响用户自身作用域。
故需将插件的所有功能写在一个立即执行函数中:
;(function(global,undefined) {
var plugin = {
add: function(n1,n2){…}
…
}
// 最后将插件对象暴露给全局对象
‘plugin’ in global && global.plugin = plugin;
})(window);
对上面的代码段传参问题进行解释一下:
在定义插件之前添加一个分号,可以解决js合并时可能会产生的错误问题;
undefined在老一辈的浏览器是不被支持的,直接使用会报错,js框架要考虑到兼容性,因此增加一个形参undefined,就算有人把外面的 undefined 定义了,里面的 undefined 依然不受影响;
把window对象作为参数传入,是避免了函数执行的时候到外部去查找。
其实,我们觉得直接传window对象进去,我觉得还是不太妥当。我们并不确定我们的插件就一定用于浏览器上,也有可能使用在一些非浏览端上。所以我们还可以这么干,我们不传参数,直接取当前的全局this对象为作顶级对象用。
;(function(global,undefined) {
“use strict” //使用js严格模式检查,使语法更规范
var _global;
var plugin = {
add: function(n1,n2){…}
…
}
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)(‘this’); }());
!(‘plugin’ in _global) && (_global.plugin = plugin);
}());
如此,我们不需要传入任何参数,并且解决了插件对环境的依事性。如此我们的插件可以在任何宿主环境上运行了。
上面的代码段中有段奇怪的表达式:(0, eval)(‘this’),实际上(0,eval)是一个表达式,这个表达式执行之后的结果就是eval这一句相当于执行eval(‘this’)的意思,详细解释看此篇:(0,eval)(‘this’)释义或者看一下这篇(0,eval)(‘this’)
关于立即自执行函数,有两种写法:
// 写法一
(function(){})()
//写法二
(function(){}())
上面的两种写法是没有区别的。都是正确的写法。个人建议使用第二种写法。这样子更像一个整体。
附加一点知识:
js里面()括号就是将代码结构变成表达式,被包在()里面的变成了表达式之后,则就会立即执行,js中将一段代码变成表达式有很多种方式,比如:
void function(){…}();
// 或者
!function foo(){…}();
// 或者
+function foot(){…}();
当然,我们不推荐你这么用。而且乱用可能会产生一些歧义。
到这一步,我们的插件的基础结构就已经算是完整的了。
使用模块化的规范包装
虽然上面的包装基本上已经算是ok了的。但是如果是多个人一起开发一个大型的插件,这时我们要该怎么办呢?多人合作,肯定会产生多个文件,每个人负责一个小功能,那么如何才能将所有人开发的代码集合起来呢?这是一个讨厌的问题。要实现协作开发插件,必须具备如下条件:
每功能互相之间的依赖必须要明确,则必须严格按照依赖的顺序进行合并或者加载
每个子功能分别都要是一个闭包,并且将公共的接口暴露到共享域也即是一个被主函数暴露的公共对象
关键如何实现,有很多种办法。最笨的办法就是按顺序加载js
…
但是不推荐这么做,这样做与我们所追求的插件的封装性相背。
不过现在前端界有一堆流行的模块加载器,比如require、seajs,或者也可以像类似于Node的方式进行加载,不过在浏览器端,我们还得利用打包器来实现模块加载,比如browserify。不过在此不谈如何进行模块化打包或者加载的问题,如有问题的同学可以去上面的链接上看文档学习。
为了实现插件的模块化并且让我们的插件也是一个模块,我们就得让我们的插件也实现模块化的机制。
我们实际上,只要判断是否存在加载器,如果存在加载器,我们就使用加载器,如果不存在加载器。我们就使用顶级域对象。
if (typeof module !== “undefined” && module.exports) {
module.exports = plugin;
} else if (typeof define === “function” && define.amd) {
define(function(){return plugin;});
} else {
_globals.plugin = plugin;
}
这样子我们的完整的插件的样子应该是这样子的:
// plugin.js
;(function(undefined) {
“use strict”
var _global;
var plugin = {
add: function(n1,n2){ return n1 + n2; },//加
sub: function(n1,n2){ return n1 - n2; },//减
mul: function(n1,n2){ return n1 * n2; },//乘
div: function(n1,n2){ return n1 / n2; },//除
sur: function(n1,n2){ return n1 % n2; } //余
}
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)(‘this’); }());
if (typeof module !== “undefined” && module.exports) {
module.exports = plugin;
} else if (typeof define === “function” && define.amd) {
define(function(){return plugin;});
} else {
!(‘plugin’ in _global) && (_global.plugin = plugin);
}
}());
我们引入了插件之后,则可以直接使用plugin对象。
with(plugin){
console.log(add(2,1)) // 3
console.log(sub(2,1)) // 1
console.log(mul(2,1)) // 2
console.log(div(2,1)) // 2
console.log(sur(2,1)) // 0
}
插件的API
插件的默认参数
我们知道,函数是可以设置默认参数这种说法,而不管我们是否传有参数,我们都应该返回一个值以告诉用户我做了怎样的处理,比如:
function add(param){
var args = !!param ? Array.prototype.slice.call(arguments) : [];
return args.reduce(function(pre,cur){
return pre + cur;
}, 0);
}
console.log(add()) //不传参,结果输出0,则这里已经设置了默认了参数为空数组
console.log(add(1,2,3,4,5)) //传参,结果输出15
则作为一个健壮的js插件,我们应该把一些基本的状态参数添加到我们需要的插件上去。
假设还是上面的加减乘除余的需求,我们如何实现插件的默认参数呢?道理其实是一样的。
// plugin.js
;(function(undefined) {
“use strict”
var _global;
function result(args,fn){var argsArr = Array.prototype.slice.call(args);if(argsArr.length > 0){return argsArr.reduce(fn);} else {return 0;}
}
var plugin = {add: function(){return result(arguments,function(pre,cur){return pre + cur;});},//加sub: function(){return result(arguments,function(pre,cur){return pre - cur;});},//减mul: function(){return result(arguments,function(pre,cur){return pre * cur;});},//乘div: function(){return result(arguments,function(pre,cur){return pre / cur;});},//除sur: function(){return result(arguments,function(pre,cur){return pre % cur;});} //余
}// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {module.exports = plugin;
} else if (typeof define === "function" && define.amd) {define(function(){return plugin;});
} else {!('plugin' in _global) && (_global.plugin = plugin);
}
}());
// 输出结果为:
with(plugin){
console.log(add()); // 0
console.log(sub()); // 0
console.log(mul()); // 0
console.log(div()); // 0
console.log(sur()); // 0
console.log(add(2,1)); // 3
console.log(sub(2,1)); // 1
console.log(mul(2,1)); // 2
console.log(div(2,1)); // 2
console.log(sur(2,1)); // 0
}
实际上,插件都有自己的默认参数,就以我们最为常见的表单验证插件为例:validate.js
(function(window, document, undefined) {
// 插件的默认参数
var defaults = {
messages: {
required: ‘The %s field is required.’,
matches: ‘The %s field does not match the %s field.’,
“default”: ‘The %s field is still set to default, please change.’,
valid_email: ‘The %s field must contain a valid email address.’,
valid_emails: ‘The %s field must contain all valid email addresses.’,
min_length: ‘The %s field must be at least %s characters in length.’,
max_length: ‘The %s field must not exceed %s characters in length.’,
exact_length: ‘The %s field must be exactly %s characters in length.’,
greater_than: ‘The %s field must contain a number greater than %s.’,
less_than: ‘The %s field must contain a number less than %s.’,
alpha: ‘The %s field must only contain alphabetical characters.’,
alpha_numeric: ‘The %s field must only contain alpha-numeric characters.’,
alpha_dash: ‘The %s field must only contain alpha-numeric characters, underscores, and dashes.’,
numeric: ‘The %s field must contain only numbers.’,
integer: ‘The %s field must contain an integer.’,
decimal: ‘The %s field must contain a decimal number.’,
is_natural: ‘The %s field must contain only positive numbers.’,
is_natural_no_zero: ‘The %s field must contain a number greater than zero.’,
valid_ip: ‘The %s field must contain a valid IP.’,
valid_base64: ‘The %s field must contain a base64 string.’,
valid_credit_card: ‘The %s field must contain a valid credit card number.’,
is_file_type: ‘The %s field must contain only %s files.’,
valid_url: ‘The %s field must contain a valid URL.’,
greater_than_date: ‘The %s field must contain a more recent date than %s.’,
less_than_date: ‘The %s field must contain an older date than %s.’,
greater_than_or_equal_date: ‘The %s field must contain a date that’s at least as recent as %s.’,
less_than_or_equal_date: ‘The %s field must contain a date that’s %s or older.’
},
callback: function(errors) {
}
};var ruleRegex = /^(.+?)\[(.+)\]$/,numericRegex = /^[0-9]+$/,integerRegex = /^\-?[0-9]+$/,decimalRegex = /^\-?[0-9]*\.?[0-9]+$/,emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,alphaRegex = /^[a-z]+$/i,alphaNumericRegex = /^[a-z0-9]+$/i,alphaDashRegex = /^[a-z0-9_\-]+$/i,naturalRegex = /^[0-9]+$/i,naturalNoZeroRegex = /^[1-9][0-9]*$/i,ipRegex = /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/i,base64Regex = /[^a-zA-Z0-9\/\+=]/i,numericDashRegex = /^[\d\-\s]+$/,urlRegex = /^((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)|)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,dateRegex = /\d{4}-\d{1,2}-\d{1,2}/;... //省略后面的代码
})(window,document);
/*
- Export as a CommonJS module
*/
if (typeof module !== ‘undefined’ && module.exports) {
module.exports = FormValidator;
}
当然,参数既然是默认的,那就意味着我们可以随意修改参数以达到我们的需求。插件本身的意义就在于具有复用性。
如表单验证插件,则就可以new一个对象的时候,修改我们的默认参数:
var validator = new FormValidator(‘example_form’, [{
name: ‘req’,
display: ‘required’,
rules: ‘required’
}, {
name: ‘alphanumeric’,
rules: ‘alpha_numeric’
}, {
name: ‘password’,
rules: ‘required’
}, {
name: ‘password_confirm’,
display: ‘password confirmation’,
rules: ‘required|matches[password]’
}, {
name: ‘email’,
rules: ‘valid_email’
}, {
name: ‘minlength’,
display: ‘min length’,
rules: ‘min_length[8]’
}, {
names: [‘fname’, ‘lname’],
rules: ‘required|alpha’
}], function(errors) {
if (errors.length > 0) {
// Show the errors
}
});
插件的钩子
【版权与免责声明】如发现内容存在版权问题,烦请提供相关信息发邮件至 lnkj@3173.top ,我们将及时沟通与处理。 本站内容除了3117站长服务平台( www.3117.cn )原创外,其它均为网友转载内容,涉及言论、版权与本站无关。