Today I want to discuss with you our needs for plug-ins at work. When we write code, not all business or logic code must be extracted and reused. First, we have to see if we need to abstract some of the frequently repeated code into a separate file for future use. Let's take another look at whether our business logic can serve the team. Plug-ins are not written casually, but abstracted according to their own business logic. There is no one-size-fits-all plug-in, only plug-ins. The reason why they are called plug-ins is that they can be used out of the box, or we only need to add some configuration parameters to achieve the results we need. If these conditions are met, we will consider making a plug-in.
Conditions for plug-in encapsulation
A reusable plug-in needs to meet the following conditions:
The scope of the plug-in itself is independent of the user's current scope, that is, the plug-in Internal private variables cannot affect the user's environment variables;
The plug-in must have default setting parameters;
In addition to the basic functions that have been implemented, the plug-in must provide some APIs, and users can This API modifies the default parameters of the plug-in function to achieve user-defined plug-in effects;
The plug-in supports chain calls;
The plug-in needs to provide a monitoring entrance and monitor the specified element, so that the Elements and plug-ins respond to plug-in effects.
Regarding the conditions for plug-in encapsulation, you can view an article: NativeJavaScriptPlug-in Writing Guide
What I want to explain is how to implement my plug-in encapsulation step by step. So, I'll start with simple method functions.
Outer packaging of plug-ins
Packaging with functions
The so-called plug-in is actually a set of functions encapsulated in a closure. I remember when I first started writing js, this is what I did. I wrote the logic I wanted into a function, and then passed in different parameters according to different needs.
For example, I want to implement the method of adding two numbers:
function add(n1,n2) { return n1 + n2; } // 调用 add(1,2) // 输出:3
This is a simple implementation of the function we want. If you just implement such simple logic, that's enough. There is no need to do anything fancy. The js function itself can solve most problems. However, in our actual work and applications, the general requirements are much more complex.
If at this time, the product comes to tell you, I not only need to add two numbers, but I also need functions such as subtraction, multiplication, division, remainder, etc. At this time, what should we do?
Of course, you will think, why is this so difficult? Just write out all these functions and you're done. Then put them all in a js file. When needed, just call it.
// 加 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; }
OK, now we have implemented all the functions we need. And we also wrote these functions into a js. If a person is using it, he can clearly know whether he has defined anything, and knows what content he has written, and which page I need it on, then he can directly import this js file and it will be done.
However, if it is a team of more than two people, or you collaborate with others to write code, at this time, the other person does not know whether you have written the add method, and he also defines the same add method. Then there will be a naming conflict between you, which is generally called global pollution of variables
Use global objects to wrap
In order to solve this problem of global variable pollution. At this time, we can define a js object to receive our tool functions.
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)
In the above method, it is agreed that the plug-in is named plugin, so that team members must abide by the naming rules, which has solved the problem of global pollution to a certain extent. In team collaboration, you only need to agree on the naming rules and inform other students. Of course, it is not ruled out that someone takes over your project and does not know that this global variable has been defined. Then he defines it again and assigns a value. At this time, your object will be overwritten. Of course, you may do this to solve the naming conflict problem:
if(!plugin){ //这里的if条件也可以用: (typeof plugin == 'undefined') var plugin = { // 以此写你的函数逻辑 } }
or
var plugin; if(!plugin){ plugin = { // ... } }
In this way, there will be no naming conflict.
Basically, this can be regarded as a plug-in. The global pollution problem is solved, and the method function can be extracted and placed in a separate file.
Use closure packaging
上面的例子,虽然可以实现了插件的基本上的功能。不过我们的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设置为只读效果
The above is the detailed content of How to use native JS to encapsulate the plug-ins you need. For more information, please follow other related articles on the PHP Chinese website!