今日は、仕事でのプラグインのニーズについてお話したいと思います。コードを作成するときに、すべてのビジネス コードやロジック コードを抽出して再利用する必要はありません。まず、頻繁に繰り返されるコードの一部を将来の使用のために別のファイルに抽象化する必要があるかどうかを確認する必要があります。私たちのビジネス ロジックがチームに役立つかどうかをもう一度見てみましょう。プラグインは無造作に記述されるのではなく、独自のビジネス ロジックに従って抽象化されます。すべてに対応する万能のプラグインはなく、プラグインのみが呼び出されます。これは、プラグインがすぐに使用できるため、またはいくつかの構成パラメータを追加するだけで実現できるためです。私たちが必要とする結果。これらの条件が満たされれば、プラグインの作成を検討します。
プラグインのカプセル化の条件
再利用可能なプラグインは、次の条件を満たす必要があります:
プラグイン自体のスコープは、ユーザーの現在のスコープ、つまりプラグイン内のプライベート変数から独立しています。はユーザーの環境変数に影響を与えることはできません。
プラグインにはデフォルトの設定パラメーターが必要です。
ユーザーはプラグインのデフォルトのパラメーターを変更できるようにする必要があります。 - ユーザー定義のプラグイン効果を実現するための API を介した関数。
プラグインはチェーン呼び出しをサポートします。
プラグインは、要素とプラグインがそれに応じてプラグイン効果が得られます。
プラグインのカプセル化の条件については、次の記事を参照してください: NativeJavaScriptプラグイン作成ガイド
そして、私が説明したいのは、プラグインのカプセル化を段階的に実装する方法です。そこで、簡単なメソッド関数から始めます。
プラグインの外側のパッケージ
は関数でラップされています
いわゆるプラグインは、実際にはクロージャにカプセル化された関数のセットです。私が初めて js を書き始めたとき、これが私がやったことを覚えています。必要なロジックを関数に記述し、さまざまなニーズに応じてさまざまなパラメーターを渡しました。
たとえば、2 つの数値を加算するメソッドを実装したいとします。
function add(n1,n2) { return n1 + n2; } // 调用 add(1,2) // 输出:3
これは、必要な関数の簡単な実装です。このような単純なロジックを実装するだけで十分です。特別なことをする必要はありません。 js 関数自体でほとんどの問題を解決できます。ただし、実際の作業やアプリケーションでは、一般的な要件はさらに複雑です。
この時点で製品が通知してきた場合、2 つの数値を加算するだけでなく、減算、乗算、除算、剰余などの関数も必要になります。このとき、私たちは何をすべきでしょうか?
もちろん、なぜこれがそんなに難しいのかと思うでしょう。これらの関数をすべて書き出すだけで完了です。次に、それらをすべて js ファイルに入れます。必要なときに、呼び出すだけです。
// 加 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、これで必要な機能はすべて実装されました。そして、これらの関数も js に記述しました。単独で使用している場合は、何かを定義したかどうか、どのような内容を記述したか、どのページに必要かが明確にわかります。その後、この js ファイルを直接インポートするだけで完了します。
ただし、2人以上のチームであったり、他の人と共同でコードを書いている場合、現時点では他の人はあなたがaddメソッドを書いたかどうか知りませんし、同じaddメソッドを定義していることになります。方法。そうなると、名前の競合が発生します。これは一般的に変数のグローバル汚染と呼ばれます
このグローバル変数汚染の問題を解決するには、グローバル オブジェクトでラップします
。この時点で、ツール関数を受け取るための js オブジェクトを定義できます。
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)
上記の方法では、このプラグインの名前は plugin であることが合意されており、チームメンバーは命名ルールを遵守する必要があり、地球規模の汚染の問題はある程度解決されました。チームで共同作業を行う場合は、名前付けルールに同意し、他の生徒に通知するだけです。もちろん、誰かがあなたのプロジェクトを引き継ぎ、このグローバル変数が定義されていることを知らない可能性が排除されません。このとき、その人はそれを再度定義し、値を割り当てます。もちろん、名前の競合の問題を解決するためにこれを行うこともできます:
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设置为只读效果
以上がネイティブ JS を使用して必要なプラグインをカプセル化する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。