jQuery插件开发的五种形态小结


Posted in Javascript onMarch 04, 2015

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

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

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

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

插件的第一形态

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

function pluginName($selector){

    $.each($selector, function () {

        $(this).css("background-color", "#ccc");

        // to do something...

    });

}

// pluginName(document.getElementsByClassName("demo"));

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

// IIFE(立即调用函数表达式);  [参考 http://suqing.iteye.com/blog/1981591/]

;(function ($) {

    // 扩展这个方法到jQuery.

    // $.extend() 是吧方法扩展到 $ 对象上,和 $.fn.extend 不同。 扩展到 $.fn.xxx 上后,

    // 调用的时候就可以是 $(selector).xxx()

    $.fn.extend({

        // 插件名字

        pluginName: function () {

            // 遍历匹配元素的集合

            // 注意这里有个"return",作用是把处理后的对象返回,实现链式操作

            return this.each(function () {

                // 在这里编写相应的代码进行处理

            });

        }

    });

// 传递jQuery到内层作用域去, 如果window,document用的多的话, 也可以在这里传进去.

// })(jQuery, window, document, undefined);

})(jQuery, undefined);

// 调用方式 $(".selector").pluginName().otherMethod();

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

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

插件的第二形态

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

;(function($){

    $.fn.pluginName = function(options) {

        // 合并参数,通过“extend”合并默认参数和自定义参数

        var args = $.extend({}, $.fn.pluginName.defaults, options);

        return this.each(function() {

            console.log(args.text);

            // to do something...

        });

    };

    // 默认参数

    $.fn.pluginName.defaults = {

        text : "hello"

    };

})(jQuery);

// $(".selector").pluginName({

//     text : "hello world!"

// });

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

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

插件的第三形态

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

;(function($){

    $.fn.pluginName = function (method) {

        // 如果第一个参数是字符串, 就查找是否存在该方法, 找到就调用; 如果是object对象, 就调用init方法;.

        if (methods[method]) {

            // 如果存在该方法就调用该方法

            // apply 是吧 obj.method(arg1, arg2, arg3) 转换成 method(obj, [arg1, arg2, arg3]) 的过程.

            // Array.prototype.slice.call(arguments, 1) 是把方法的参数转换成数组.

            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));

        } else if (typeof method === 'object' || !method) {

            // 如果传进来的参数是"{...}", 就认为是初始化操作.

            return methods.init.apply(this, arguments);

        } else {

            $.error('Method ' + method + ' does not exist on jQuery.pluginName');

        }

    };

    // 不把方法扩展在 $.fn.pluginName 上. 在闭包内建个"methods"来保存方法, 类似共有方法.

    var methods = {

        /**

         * 初始化方法

         * @param _options

         * @return {*}

         */

        init : function (_options) {

            return this.each(function () {

                var $this = $(this);

                var args = $.extend({}, $.fn.pluginName.defaults, _options);

                // ...

            })

        },

        publicMethod : function(){

            private_methods.demoMethod();

        }

    };

    // 私有方法

    function private_methods = {

        demoMethod : function(){}

    }

    // 默认参数

    $.fn.pluginName.defaults = {

    };

})(jQuery);

// 调用方式

// $("div").pluginName({...});  // 初始化

// $("div").pluginName("publicMethod");  // 调用方法

又解决一问题

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

插件的第四形态

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

(function ($) {

    var Plugin = function (element, options) {

        this.element = element;

        this.options = options;

    };

    Plugin.prototype = {

        create: function () {

            console.log(this.element);

            console.log(this.options);

        }

    };

    $.fn.pluginName = function (options) {

        // 合并参数

        return this.each(function () {

            // 在这里编写相应的代码进行处理

            var ui = $._data(this, "pluginName");

            // 如果该元素没有初始化过(可能是新添加的元素), 就初始化它.

            if (!ui) {

                var opts = $.extend(true, {}, $.fn.pluginName.defaults, typeof options === "object" ? options : {});

                ui = new Plugin(this, opts);

                // 缓存插件

                $._data(this, "pluginName", ui);

            }

            // 调用方法

            if (typeof options === "string" && typeof ui[options] == "function") {

                // 执行插件的方法

                ui[options].apply(ui, args);

            }

        });

    };

    $.fn.pluginName.defaults = {};

})(jQuery);

// 调用的方式和之前一样。

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

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

插件的第五形态

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

!function ($) {

    // ecma262v5 的新东西, 强制使用严谨的代码编写.

    "use strict";

    // BUTTON PUBLIC CLASS DEFINITION

    // ==============================

    var Button = function (element, options) {

        this.$element = $(element);

        this.options = $.extend({}, Button.DEFAULTS, options);

    };

    Button.DEFAULTS = {

        loadingText: 'loading...'

    };

    Button.prototype.setState = function (state) {

        // ...

    };

    Button.prototype.toggle = function () {

        // ...

    };

    // BUTTON PLUGIN DEFINITION

    // ========================

    var old = $.fn.button; // 这里的 $.fn.button 有可能是之前已经有定义过的插件,在这里做无冲突处理使用。

    $.fn.button = function (option) {

        return this.each(function () {

            var $this = $(this);

            // 判断是否初始化过的依据

            var data = $this.data('bs.button');

            var options = typeof option == 'object' && option;

            // 如果没有初始化过, 就初始化它

            if (!data) $this.data('bs.button', (data = new Button(this, options)));

            if (option == 'toggle') data.toggle();

            else if (option) data.setState(option)

        })

    };

    // ① 暴露类名, 可以通过这个为插件做自定义扩展

    $.fn.button.Constructor = Button;

    // 扩展的方式

    // 设置 : $.fn.button.Constructor.newMethod = function(){}

    // 使用 : $btn.button("newMethod");

    // ② 无冲突处理

    $.fn.button.noConflict = function () {

        $.fn.button = old;

        return this

    };

    // ③ 事件代理, 智能初始化

    $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {

        var $btn = $(e.target);

        // 查找要初始化的对象

        if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn');

        // 直接调用方法, 如果没有初始化, 内部会先进行初始化

        $btn.button('toggle');

        e.preventDefault();

    });

}(jQuery);

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

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

补充

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

支持jQuery和Zepto

if (window.jQuery || window.Zepto) {

  (function ($) {

      // plugin code...

  })(window.jQuery || window.Zepto);

}

中间件支持,node

if (typeof(module) !== 'undefined')

{

  module.exports = pluginName;

}

requirejs(AMD) support

if (typeof define === 'function' && define.amd) {

  define([], function () {

      'use strict';

      return pluginName;

  });

}

seajs(CMD) support

if (typeof define === 'function') {

  define([], function () {

      'use strict';

      return pluginName;

  });

}

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

好了,今天的总结就先到这里了,如果大家有更好的插件开发方式,还请告知一下。希望大家能够喜欢本文。

Javascript 相关文章推荐
JavaScript高级程序设计 阅读笔记(四) ECMAScript中的类型转换
Feb 27 Javascript
window.location.reload()方法刷新页面弹出要再次显示该网页对话框
Apr 24 Javascript
js带按钮的提示框可供选择示例代码
Sep 17 Javascript
easyui Droppable组件实现放置特效
Aug 19 Javascript
详解WordPress开发中get_current_screen()函数的使用
Jan 11 Javascript
微信小程序 navigation API实例详解
Oct 02 Javascript
Bootstrap优化站点资源、响应式图片、传送带使用详解3
Oct 14 Javascript
利用nginx + node在阿里云部署https的步骤详解
Dec 19 Javascript
Vue兼容ie9的问题全面解决方案
Jun 19 Javascript
Vue三种常用传值示例(父传子、子传父、非父子)
Jul 24 Javascript
Vue防止白屏添加首屏动画的实例
Oct 31 Javascript
Vue.js中v-bind指令的用法介绍
Mar 13 Vue.js
深入理解JavaScript系列(36):设计模式之中介者模式详解
Mar 04 #Javascript
百度UEditor编辑器如何关闭抓取远程图片功能
Mar 03 #Javascript
jQuery实现复选框成对选择及对应取消的方法
Mar 03 #Javascript
js实现文本框中输入文字页面中div层同步获取文本框内容的方法
Mar 03 #Javascript
JS实现文字放大效果的方法
Mar 03 #Javascript
jQuery实现的感应鼠标悬停图片色彩渐显效果
Mar 03 #Javascript
js给网页加上背景音乐及选择音效的方法
Mar 03 #Javascript
You might like
PHP删除数组中的特定元素的代码
2012/06/28 PHP
Zend的AutoLoad机制介绍
2012/09/27 PHP
phpmyadmin提示The mbstring extension is missing的解决方法
2014/12/17 PHP
简单了解PHP编程中数组的指针的使用
2015/11/30 PHP
Laravel中9个不经常用的小技巧汇总
2019/04/16 PHP
不要在cookie中使用特殊字符的原因分析
2010/07/13 Javascript
Jquery判断IE6等浏览器的代码
2011/04/05 Javascript
javascript刷新父页面的各种方法汇总
2014/09/03 Javascript
jQuery寻找n以内完全数的方法
2015/06/24 Javascript
浅述Javascript的外部对象
2016/12/07 Javascript
jQuery实现单击按钮遮罩弹出对话框效果(2)
2017/02/20 Javascript
vue页面切换到滚动页面显示顶部的实例
2018/03/13 Javascript
javascriptvoid(0)含义以及与"#"的区别讲解
2019/01/19 Javascript
javascript异步编程的六种方式总结
2019/05/17 Javascript
vue 导航内容设置选中状态样式的例子
2019/11/01 Javascript
JS实现点击下拉列表文本框中出现对应的网址,点击跳转按钮实现跳转
2019/11/25 Javascript
python之wxPython应用实例
2014/09/28 Python
python中OrderedDict的使用方法详解
2017/05/05 Python
基于Python中求和函数sum的用法详解
2018/06/28 Python
pandas 将索引值相加的方法
2018/11/15 Python
详解Python计算机视觉 图像扭曲(仿射扭曲)
2019/03/27 Python
Django框架验证码用法实例分析
2019/05/10 Python
Django REST framework 如何实现内置访问频率控制
2019/07/23 Python
解决Jupyter notebook更换主题工具栏被隐藏及添加目录生成插件问题
2020/04/20 Python
利用CSS3的checked伪类实现OL的隐藏显示的方法
2010/12/18 HTML / CSS
使用CSS3的box-sizing属性解决div宽高被内边距撑开的问题
2016/06/28 HTML / CSS
关于HTML5的22个初级技巧(图文教程)
2012/06/21 HTML / CSS
英语专业学生个人求职信
2014/01/28 职场文书
法律七进实施方案
2014/03/15 职场文书
公司领导九九重阳节发言稿2014
2014/09/25 职场文书
小学见习报告
2014/10/31 职场文书
2014年学习委员工作总结
2014/11/14 职场文书
禁毒心得体会范文
2016/01/15 职场文书
古诗文之爱国名句(77句)
2019/09/24 职场文书
仅仅使用 HTML/CSS 实现各类进度条的方式汇总
2021/11/11 HTML / CSS
【海涛解说】pis亲自推荐,其实你从来不会玩NW
2022/04/01 DOTA