Aujourd'hui, je souhaite discuter avec vous de nos besoins en plug-ins au travail. Lorsque nous écrivons du code, tout le code métier ou logique ne doit pas être extrait et réutilisé. Tout d’abord, nous devons voir si nous devons extraire une partie du code fréquemment répété dans un fichier séparé pour une utilisation future. Voyons à nouveau si notre logique métier peut servir l'équipe. Les plug-ins ne sont pas écrits au hasard, mais résumés selon leur propre logique métier. Il n'existe pas de plug-in unique, seulement des plug-ins. La raison pour laquelle ils sont appelés plug-ins est qu'ils peuvent être utilisés directement, ou qu'il suffit d'ajouter quelques paramètres de configuration pour y parvenir. les résultats dont nous avons besoin. Si ces conditions sont remplies, nous envisagerons de réaliser un plug-in.
Conditions d'encapsulation du plug-in
Un plug-in réutilisable doit remplir les conditions suivantes :
La portée du plug-in lui-même est indépendante de l'état actuel de l'utilisateur. portée, c'est-à-dire que les variables privées internes du plug-in ne peuvent pas affecter les variables d'environnement de l'utilisateur ;
Les plug-ins doivent avoir des paramètres de configuration par défaut
En plus des fonctions de base qui ont été implémentées ; , les plug-ins doivent fournir certaines API, et les utilisateurs peuvent Cette API modifie les paramètres par défaut de la fonction du plug-in pour obtenir des effets de plug-in définis par l'utilisateur
les plug-ins prennent en charge les appels en chaîne
les plug-ins doivent fournir des entrées de surveillance et surveiller les éléments spécifiés, afin que les éléments et les plug-ins répondent aux effets du plug-in. Concernant les conditions d'encapsulation du plug-in, vous pouvez consulter cet article : NatifJavaScriptGuide d'écriture du plug-inCe que je veux expliquer, c'est comment implémenter mon plug-in en encapsulation étape par étape. Je vais donc commencer par des fonctions de méthode simples.
Par exemple, je souhaite implémenter la méthode d'addition de deux nombres :
function add(n1,n2) { return n1 + n2; } // 调用 add(1,2) // 输出:3
Si à ce moment-là, le produit vient vous le dire, j'ai non seulement besoin d'ajouter deux nombres, mais j'ai aussi besoin de fonctions telles que la soustraction, la multiplication, la division, le reste, etc. À ce moment-là, que devons-nous faire ?
Bien sûr, vous vous demanderez pourquoi est-ce si difficile. Écrivez simplement toutes ces fonctions et vous avez terminé. Ensuite, mettez-les tous dans un fichier js. En cas de besoin, appelez-le.
// 加 function add(n1,n2) { return n1 + n2; } // 减 function sub(n1,n2) { return n1 - n2; } // 乘 function mul(n1,n2) { return n1 * n2; } // 除 function div(n1,n2) { return n1 / n2; } // 求余 function sur(n1,n2) { return n1 % n2; }
Cependant, s'il s'agit d'une équipe de plus de deux personnes, ou si vous collaborez avec d'autres pour écrire du code, à ce moment-là, l'autre personne ne sait pas si vous avez écrit la méthode add, puis elle définit également la même chose. ajouter une méthode. Il y aura alors un conflit de noms entre vous, qui est généralement appelé pollution globale des variables
var plugin = { add: function(n1,n2){...},//加 sub: function(n1,n2){...},//减 mul: function(n1,n2){...},//乘 div: function(n1,n2){...},//除 sur: function(n1,n2){...} //余 } // 调用 plugin.add(1,2)
if(!plugin){ //这里的if条件也可以用: (typeof plugin == 'undefined') var plugin = { // 以此写你的函数逻辑 } }
var plugin; if(!plugin){ plugin = { // ... } }
上面的例子,虽然可以实现了插件的基本上的功能。不过我们的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); }());
如此,我们不需要传入任何参数,并且解决了插件对环境的依事性。如此我们的插件可以在任何宿主环境上运行了。
关于立即自执行函数,有两种写法:
/ 写法一
(function(){})()//
写法二
(function(){}())
使用模块化的规范包装
虽然上面的包装基本上已经算是ok了的。但是如果是多个人一起开发一个大型的插件,这时我们要该怎么办呢?多人合作,肯定会产生多个文件,每个人负责一个小功能,那么如何才能将所有人开发的代码集合起来呢?这是一个讨厌的问题。要实现协作开发插件,必须具备如下条件:
每功能互相之间的依赖必须要明确,则必须严格按照依赖的顺序进行合并或者加载
每个子功能分别都要是一个闭包,并且将公共的接口暴露到共享域也即是一个被主函数暴露的公共对象
关键如何实现,有很多种办法。最笨的办法就是按顺序加载js
<script type="text/javascript" src="part1.js"></script> <script type="text/javascript" src="part2.js"></script> <script type="text/javascript" src="part3.js"></script>...<script type="text/javascript" src="main.js"></script>
但是不推荐这么做,这样做与我们所追求的插件的封装性相背。
不过现在前端界有一堆流行的模块加载器,比如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 } });
插件的钩子
我们知道,设计一下插件,参数或者其逻辑肯定不是写死的,我们得像函数一样,得让用户提供自己的参数去实现用户的需求。则我们的插件需要提供一个修改默认参数的入口。
如上面我们说的修改默认参数,实际上也是插件给我们提供的一个API。让我们的插件更加的灵活。如果大家对API不了解,可以百度一下API
通常我们用的js插件,实现的方式会有多种多样的。最简单的实现逻辑就是一个方法,或者一个js对象,又或者是一个构造函数等等。
** 然我们插件所谓的API,实际就是我们插件暴露出来的所有方法及属性。 **
我们需求中,加减乘除余插件中,我们的API就是如下几个方法:
... 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; } } ...
可以看到plubin暴露出来的方法则是如下几个API:
add
sub
mul
div
sur
在插件的API中,我们常常将容易被修改和变动的方法或属性统称为钩子(Hook),方法则直接叫钩子函数。这是一种形象生动的说法,就好像我们在一条绳子上放很多挂钩,我们可以按需要在上面挂东西。
实际上,我们即知道插件可以像一条绳子上挂东西,也可以拿掉挂的东西。那么一个插件,实际上就是个形象上的链。不过我们上面的所有钩子都是挂在对象上的,用于实现链并不是很理想。
相信看了这些案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
相关阅读:
在html里怎么添加flash视频格式(flv、swf)文件
怎样通过disabled和readonly将input设置为只读效果
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!