A-A+

jQuery插件的开发

2016年03月22日 jquery 暂无评论 阅读 87 次
关于jQuery插件的开发自己也做了少许研究,自己也写过多个插件,在自己的团队了也分享过一次关于插件的课。开始的时候整觉的很复杂的代码,现在再次看的时候就清晰了许多。这里我把我自己总结出来的东西分享出来,帮助那些和我一样曾经遇到过同样问题的人。

我要做什么

javascript 插件 : 插件是一种遵循一定规范的应用程序接口编写出来的代码,用于处理特定的问题。

我想要得到的javascript 插件应该会有以下几个特征

  1. 代码相对独立
  2. 链式操作
  3. 插件可配置
  4. 有可操作的方法,插件的生命周期可控制
  5. 配置可被缓存
  6. 可扩展
  7. 无冲突处理
  8. 事件代理,动态初始化

* 以下的代码均假设存在 jQuery

插件的第一形态

面对这种情况,通常我们会通过定义function的方式来实现。

  1. function pluginName($selector){
  2.     $.each($selector, function () {
  3.         $(this).css("background-color""#ccc");
  4.         // to do something...
  5.     });
  6. }
  7. // pluginName(document.getElementsByClassName("demo"));

因为我谈的是jQuery插件开发,那么我现在把这段代码扩展到jQuery上,代码如下:

但是还差的远,目前只解决了两个问题

  1. 代码相对独立
  2. 链式操作
  3. 插件可配置
  4. 有可操作的方法,插件的生命周期可控制
  5. 配置可被缓存
  6. 可扩展
  7. 无冲突处理
  8. 事件代理,动态初始化

插件的第二形态

现在来给插件添加参数支持。代码如下

  1. // IIFE(立即调用函数表达式);  [参考 http://suqing.iteye.com/blog/1981591/]
  2. ;(function ($) {
  3.     // 扩展这个方法到jQuery.
  4.     // $.extend() 是吧方法扩展到 $ 对象上,和 $.fn.extend 不同。 扩展到 $.fn.xxx 上后,
  5.     // 调用的时候就可以是 $(selector).xxx()
  6.     $.fn.extend({
  7.         // 插件名字
  8.         pluginName: function () {
  9.             // 遍历匹配元素的集合
  10.             // 注意这里有个"return",作用是把处理后的对象返回,实现链式操作
  11.             return this.each(function () {
  12.                 // 在这里编写相应的代码进行处理
  13.             });
  14.         }
  15.     });
  16. // 传递jQuery到内层作用域去, 如果window,document用的多的话, 也可以在这里传进去.
  17. // })(jQuery, window, document, undefined);
  18. })(jQuery, undefined);
  19. // 调用方式 $(".selector").pluginName().otherMethod();

添加参数支持还比较容易些,又解决一问题

  1. 代码相对独立
  2. 链式操作
  3. 插件可配置
  4. 有可操作的方法,插件的生命周期可控制
  5. 配置可被缓存
  6. 可扩展
  7. 无冲突处理
  8. 事件代理,动态初始化

插件的第三形态

现在来添加方法的支持,我前面所提到的生命周期可控制,意思差不多,例如添加reInit,destory等方法来控制插件。

  1. ;(function($){
  2.     $.fn.pluginName = function (method) {
  3.         // 如果第一个参数是字符串, 就查找是否存在该方法, 找到就调用; 如果是object对象, 就调用init方法;.
  4.         if (methods[method]) {
  5.             // 如果存在该方法就调用该方法
  6.             // apply 是吧 obj.method(arg1, arg2, arg3) 转换成 method(obj, [arg1, arg2, arg3]) 的过程.
  7.             // Array.prototype.slice.call(arguments, 1) 是把方法的参数转换成数组.
  8.             return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  9.         } else if (typeof method === 'object' || !method) {
  10.             // 如果传进来的参数是"{...}", 就认为是初始化操作.
  11.             return methods.init.apply(this, arguments);
  12.         } else {
  13.             $.error('Method ' + method + ' does not exist on jQuery.pluginName');
  14.         }
  15.     };
  16.     // 不把方法扩展在 $.fn.pluginName 上. 在闭包内建个"methods"来保存方法, 类似共有方法.
  17.     var methods = {
  18.         /**
  19.          * 初始化方法
  20.          * @param _options
  21.          * @return {*}
  22.          */
  23.         init : function (_options) {
  24.             return this.each(function () {
  25.                 var $this = $(this);
  26.                 var args = $.extend({}, $.fn.pluginName.defaults, _options);
  27.                 // ...
  28.             })
  29.         },
  30.         publicMethod : function(){
  31.             private_methods.demoMethod();
  32.         }
  33.     };
  34.     // 私有方法
  35.     function private_methods = {
  36.         demoMethod : function(){}
  37.     }
  38.     // 默认参数
  39.     $.fn.pluginName.defaults = {
  40.     };
  41. })(jQuery);
  42. // 调用方式
  43. // $("div").pluginName({...});  // 初始化
  44. // $("div").pluginName("publicMethod");  // 调用方法

又解决一问题

  1. 代码相对独立
  2. 链式操作
  3. 插件可配置
  4. 有可操作的方法,插件的生命周期可控制
  5. 配置可被缓存
  6. 可扩展
  7. 无冲突处理
  8. 事件代理,动态初始化

插件的第四形态

第三形态的插件修改就已经可以应对大多数插件的需求了。精益求精嘛,继续升级。
第四形态的插件是照帮司徒正美《javascript框架设计》的代码。加了点面向对象的知识。

  1. (function ($) {
  2.     var Plugin = function (element, options) {
  3.         this.element = element;
  4.         this.options = options;
  5.     };
  6.     Plugin.prototype = {
  7.         create: function () {
  8.             console.log(this.element);
  9.             console.log(this.options);
  10.         }
  11.     };
  12.     $.fn.pluginName = function (options) {
  13.         // 合并参数
  14.         return this.each(function () {
  15.             // 在这里编写相应的代码进行处理
  16.             var ui = $._data(this"pluginName");
  17.             // 如果该元素没有初始化过(可能是新添加的元素), 就初始化它.
  18.             if (!ui) {
  19.                 var opts = $.extend(true, {}, $.fn.pluginName.defaults, typeof options === "object" ? options : {});
  20.                 ui = new Plugin(this, opts);
  21.                 // 缓存插件
  22.                 $._data(this"pluginName", ui);
  23.             }
  24.             // 调用方法
  25.             if (typeof options === "string" && typeof ui[options] == "function") {
  26.                 // 执行插件的方法
  27.                 ui[options].apply(ui, args);
  28.             }
  29.         });
  30.     };
  31.     $.fn.pluginName.defaults = {};
  32. })(jQuery);
  33. // 调用的方式和之前一样。

这里特别要提下缓存这个东西,插件用多了,觉的这个真的是好东西。
在传统面向对象的插件开发中,至少会声明个变量保存它,但是我到目前写的jQuery插件中都没有,用起来很麻烦。自从把初始化后的插件缓存起来后,方便了许多。通过代码$("#target").data("pluginName")就可以取到对象了。 来看看还有什么问题没有解决

  1. 代码相对独立
  2. 链式操作
  3. 插件可配置
  4. 有可操作的方法,插件的生命周期可控制
  5. 配置可被缓存
  6. 可扩展
  7. 无冲突处理
  8. 事件代理,动态初始化

插件的第五形态

看了上面的代码是否脑子有点晕了,如果是,休息片刻,稍后回来,下面的代码更精彩。 最后一个方案算是比较全面的了。方案来自Bootstrap,下面代码以 Bootstrap 的 button 插件为例.

  1. !function ($) {
  2.     // ecma262v5 的新东西, 强制使用严谨的代码编写.
  3.     "use strict";
  4.     // BUTTON PUBLIC CLASS DEFINITION
  5.     // ==============================
  6.     var Button = function (element, options) {
  7.         this.$element = $(element);
  8.         this.options = $.extend({}, Button.DEFAULTS, options);
  9.     };
  10.     Button.DEFAULTS = {
  11.         loadingText: 'loading...'
  12.     };
  13.     Button.prototype.setState = function (state) {
  14.         // ...
  15.     };
  16.     Button.prototype.toggle = function () {
  17.         // ...
  18.     };
  19.     // BUTTON PLUGIN DEFINITION
  20.     // ========================
  21.     var old = $.fn.button; // 这里的 $.fn.button 有可能是之前已经有定义过的插件,在这里做无冲突处理使用。
  22.     $.fn.button = function (option) {
  23.         return this.each(function () {
  24.             var $this = $(this);
  25.             // 判断是否初始化过的依据
  26.             var data = $this.data('bs.button');
  27.             var options = typeof option == 'object' && option;
  28.             // 如果没有初始化过, 就初始化它
  29.             if (!data) $this.data('bs.button', (data = new Button(this, options)));
  30.             if (option == 'toggle') data.toggle();
  31.             else if (option) data.setState(option)
  32.         })
  33.     };
  34.     // ① 暴露类名, 可以通过这个为插件做自定义扩展
  35.     $.fn.button.Constructor = Button;
  36.     // 扩展的方式
  37.     // 设置 : $.fn.button.Constructor.newMethod = function(){}
  38.     // 使用 : $btn.button("newMethod");
  39.     // ② 无冲突处理
  40.     $.fn.button.noConflict = function () {
  41.         $.fn.button = old;
  42.         return this
  43.     };
  44.     // ③ 事件代理, 智能初始化
  45.     $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
  46.         var $btn = $(e.target);
  47.         // 查找要初始化的对象
  48.         if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn');
  49.         // 直接调用方法, 如果没有初始化, 内部会先进行初始化
  50.         $btn.button('toggle');
  51.         e.preventDefault();
  52.     });
  53. }(jQuery);

来看看还有什么问题没有解决

  1. 代码相对独立
  2. 链式操作
  3. 插件可配置
  4. 有可操作的方法,插件的生命周期可控制
  5. 配置可被缓存
  6. 可扩展
  7. 无冲突处理
  8. 事件代理,动态初始化

补充

现在的插件都要求灵活性要高,比如希望插件可以同时适配jQueryZepto,又或者需要支持AMD或者CMD规范。

  • 支持jQuery和Zepto
    1. if (window.jQuery || window.Zepto) {
    2.     (function ($) {
    3.         // plugin code...
    4.     })(window.jQuery || window.Zepto);
    5. }
  • 中间件支持,node
    1. if (typeof(module) !== 'undefined')
    2. {
    3.     module.exports = pluginName;
    4. }
  • requirejs(AMD) support
    1. if (typeof define === 'function' && define.amd) {
    2.     define([], function () {
    3.         'use strict';
    4.         return pluginName;
    5.     });
    6. }
  • seajs(CMD) support
    1. if (typeof define === 'function') {
    2.     define([], function () {
    3.         'use strict';
    4.         return pluginName;
    5.     });
    6. }

呼~,问题都解决了,代码若有看不懂的地方可以多看看。后面的几个看不懂也没有关系,在实际的开发中,前面几个够用了。要强调下,并不是越高级的写法越好,要看自己项目的需求合理的选择。
当然还有更多更好的插件开发方式,有待大家自己去发现了。

原文地址:http://www.linwu.name/post/query-plugin-develop.html

标签:

给我留言

Copyright © web前端技术开发个人博客 保留所有权利  京ICP备14060653号 Theme  Ality

用户登录