jQuery插件制作的實例教程

字號:


    一、jQuery插件的類型
    1. jQuery方法
    很大一部分的jQuery插件都是這種類型,由于此類插件是將對象方法封裝起來,在jQuery選擇器獲取jQuery對象過程中進行操作,從而發(fā)揮jQuery強大的選擇器優(yōu)勢。
    2. 全局函數(shù)法
    可以把自定義的功能函數(shù)獨立附加到j(luò)Query命名空間下,從而作為jQuery作用域下的一個公共函數(shù)使用。
    但全局函數(shù)沒有被綁定到j(luò)Query對象上,故不能在選擇器獲取的jQuery對象上調(diào)用。需要通過jQuery.fn()或$.fn()方式進行引用。
    3. 選擇器法
    如果覺得jQuery提供的選擇器不夠用或不方便的話,可以考慮自定義選擇器。
    二、jQuery插件的機制
    1.   jQuery.extend()方法
    這種方法能夠創(chuàng)建全局函數(shù)或選擇器。
    所謂全局函數(shù),就是jQuery對象的方法,實際上就是位于jQuery命名空間內(nèi)部的函數(shù),有人把這類函數(shù)稱為實用工具函數(shù),這些函數(shù)都有一個共同特征,就是不直接操作DOM元素,而是操作Javascript的非元素對象,或者執(zhí)行其他非對象的特定操作,如jQuery的each()函數(shù)和noConflict()函數(shù)。
    例如,在jQuery命名空間上創(chuàng)建兩個公共函數(shù):
    jQuery.extend({ 
    min : function(a,b){ 
    return a<b?a:b; 
    }, 
    max : function(a,b){ 
    return a<b?b:a; 
    } 
    }) 
    $(function(){ 
    $("input").click(function(){ 
    var a = prompt("請輸入一個數(shù):"); 
    var b = prompt("再輸入一個數(shù):"); 
    var c = jQuery.min(a,b); 
    var d = jQuery.max(a,b); 
    alert("最大值是:" + d + "\n最小值是:" + c); 
    }); 
    }) 
    <input type="button" value="jQuery擴展測試" />
    jQuery.extend({ 
    min : function(a,b){ 
    return a<b?a:b; 
    }, 
    max : function(a,b){ 
    return a<b?b:a; 
    } 
    }) 
    $(function(){ 
    $("input").click(function(){ 
    var a = prompt("請輸入一個數(shù):"); 
    var b = prompt("再輸入一個數(shù):"); 
    var c = jQuery.min(a,b); 
    var d = jQuery.max(a,b); 
    alert("最大值是:" + d + "\n最小值是:" + c); 
    }); 
    }) 
    <input type="button" value="jQuery擴展測試" />
    jQuery.extend()方法除了可以創(chuàng)建插件外,還可以用來擴展jQuery對象。
    例如,調(diào)用jQuery.extend()方法把對象a和對象b合并為一個新的對象,并返回合并對象將其賦值給變量c:
    var a = {name : "aaa",pass : 777}; 
    var b = {name : "bbb",pass : 888,age : 9}; 
    var c = jQuery.extend(a,b); 
    $(function(){ 
    for(var name in c){ 
    $("div").html($("div").html() + "<br />"+ name + ":" + c[name]); 
    } 
    })
    如果要向jQuery命名空間上添加一個函數(shù),只需要將這個新函數(shù)制定為jQuery對象的一個屬性即可。其中jQuery對象名也可以簡寫為$,jQuery.smalluv==$.smalluv。
    例如,創(chuàng)建jQuery全局函數(shù):
    jQuery.smalluv = { 
    min : function(a,b){ 
    return a<b?a:b; 
    }, 
    max : function(a,b){ 
    return a<b?b:a; 
    } 
    } 
    $(function(){ 
    $("input").click(function(){ 
    var a = prompt("請輸入一個數(shù):"); 
    var b = prompt("再輸入一個數(shù):"); 
    var c = jQuery.smalluv.min(a,b); 
    var d = jQuery.smalluv.max(a,b); 
    alert("最大值是:" + d + "\n最小值是:" + c); 
    }); 
    })
    2. jQuery.fn.extend()方法
    這種方法能夠創(chuàng)建jQuery對象方法。
    舉一個最簡單的jQuery對象方法例子:
    jQuery.fn.test = function(){ 
    alert("jQuery對象方法"); 
    } 
    $(function(){ 
    $("div").click(function(){ 
    $(this).test(); 
    }); 
    })
    三、初步總結(jié)
    在jQuery匿名函數(shù)中,采用jQuery.extend();方法創(chuàng)建jQuery插件
    在jQuery匿名函數(shù)中,采用對象.屬性=函數(shù)的方式創(chuàng)建jQuery插件
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
        <title>最簡單的jquery插件</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type="text/javascript" src="../res/jquery/jquery-1.4.4.min.js"></script>
        <script type="text/javascript">
          (function($) {
            jQuery.extend({//寫法1
              a: function(h){
                $("#ad").html(h);
              },
              b:function(h){
                alert(h);
              }
            })
          })(jQuery);
          (function($) {//寫法2
            jQuery.a=function(h){
              $("#ad").html(h);
            }
            jQuery.b=function(h){
              alert(h);
            }
          })(jQuery);
          $(document).ready(function(){
            $.a("abc");
            $.b("xyz");
          });
        </script>
      </head>
      <body>
        <h3>最簡單的jQuery插件</h3>
        <div id="ad"></div>
      </body>
    </html>
    四、編寫實例
    寫法一
    插件主體:
    (function($, window){
      // 初始態(tài)定義
      var _oDialogCollections = {};
      // 插件定義
      $.fn.MNDialog = function (_aoConfig) {
        // 默認參數(shù),可被重寫
        var defaults = {
          // string
          sId : "",
          // num
          nWidth : 400,
          // bollean
          bDisplayHeader : true,
          // object
          oContentHtml : "",
          // function
          fCloseCallback : null
        };
        var _oSelf = this,
          $this = $(this);
        // 插件配置
        this.oConfig = $.extend(defaults, _aoConfig);
        // 初始化函數(shù)
        var _init = function () {
          if (_oDialogCollections) {
            // 對于已初始化的處理
            // 如果此時已經(jīng)存在彈框,則remove掉再添加新的彈框
          }
          // 初始化彈出框數(shù)據(jù)
          _initData();
          // 事件綁定
          _loadEvent();
          // 加載內(nèi)容
          _loadContent();      
        }
        // 私有函數(shù)
        var _initData = function () {};
        var _loadEvent = function () {};
        var _loadContent = function () {
          // 內(nèi)容(分字符和函數(shù)兩種,字符為靜態(tài)模板,函數(shù)為異步請求后組裝的模板,會延遲,所以特殊處理)
          if($.isFunction(_oSelf.oConfig.oContentHtml)) {
            _oSelf.oConfig.oContentHtml.call(_oSelf, function(oCallbackHtml) {
              // 便于傳帶參函數(shù)進來并且執(zhí)行
              _oSelf.html(oCallbackHtml);
              // 有回調(diào)函數(shù)則執(zhí)行
              _oSelf.oConfig.fLoadedCallback && _oSelf.oConfig.fLoadedCallback.call(_oSelf, _oSelf._oContainer$);
            });
          } else if ($.type(_oSelf.oConfig.oContentHtml) === "string") {
            _oSelf.html(_oSelf.oConfig.oContentHtml);
            _oSelf.oConfig.fLoadedCallback && _oSelf.oConfig.fLoadedCallback.call(_oSelf, _oSelf._oContainer$);
          } else {
            console.log("彈出框的內(nèi)容格式不對,應(yīng)為function或者string。");
          }
        };
        // 內(nèi)部使用參數(shù)
        var _oEventAlias = {
          click     : 'D_ck',
          dblclick   : 'D_dbl'
        };
        // 提供外部函數(shù)
        this.close = function () {
          _close();
        }    
        // 啟動插件
        _init();
        // 鏈式調(diào)用
        return this;    
      };
      // 插件結(jié)束
    })(jQuery, window);
    調(diào)用
    var MNDialog = $("#header").MNDialog({
      sId : "#footer",    //覆蓋默認值
      fCloseCallback : dialog,//回調(diào)函數(shù)
      oContentHtml : function(_aoCallback){
          _aoCallback(_oEditGrpDlgView.el);
        }
      }
    });
    // 調(diào)用提供的函數(shù)
    MNDialog.close;
    function dialog(){
    }
    點評
    1. 自調(diào)用匿名函數(shù)
    (function($, window) {
     // jquery code
    })(jQuery, window);
    用處:通過定義一個匿名函數(shù),創(chuàng)建了一個“私有”的命名空間,該命名空間的變量和方法,不會破壞全局的命名空間。這點非常有用也是一個JS框架必須支持的功能,jQuery被應(yīng)用在成千上萬的JavaScript程序中,必須確保jQuery創(chuàng)建的變量不能和導(dǎo)入他的程序所使用的變量發(fā)生沖突。
    2. 匿名函數(shù)為什么要傳入window
    通過傳入window變量,使得window由全局變量變?yōu)榫植孔兞浚?dāng)在jQuery代碼塊中訪問window時,不需要將作用域鏈回退到頂層作用域,這樣可以更快的訪問window;這還不是關(guān)鍵所在,更重要的是,將window作為參數(shù)傳入,可以在壓縮代碼時進行優(yōu)化,看看jquery.min.js:
    (function(a,b){})(jQuery, window); // jQuery被優(yōu)化為a, window 被優(yōu)化為 b
    3. 全局變量this定義
    var _oSelf = this,
    $this = $(this);
    使得在插件的函數(shù)內(nèi)可以使用指向插件的this
    4. 插件配置
    this.oConfig = $.extend(defaults, _aoConfig);
    設(shè)置默認參數(shù),同時也可以再插件定義時傳入?yún)?shù)覆蓋默認值
    5. 初始化函數(shù)
    一般的插件會有init初始化函數(shù)并在插件的尾部初始化
    6. 私有函數(shù)、公有函數(shù)
    私有函數(shù):插件內(nèi)使用,函數(shù)名使用”_”作為前綴標識
    共有函數(shù):可在插件外使用,函數(shù)名使用”this.”作為前綴標識,作為插件的一個方法供外部使用
    7. return this
    最后返回jQuery對象,便于jQuery的鏈式操作
    寫法二
    主體結(jié)構(gòu)
    (function($){
      $.fn.addrInput = function(_aoOptions){
        var _oSelf = this;
        _oSelf.sVersion = 'version-1.0.0';
        _oSelf.oConfig = {
          nInputLimitNum : 9
        };
        // 插件配置
        $.extend(_oSelf.oConfig, _aoOptions);
        // 調(diào)用這個對象的方法,傳遞this
        $.fn.addrInput._initUI.call(_oSelf, event);
        $.fn.addrInput._initEvents.call(_oSelf);
        // 提供外部函數(shù)
        this.close = function () {
          _close();
        }
        //返回jQuery對象,便于Jquery的鏈式操作  
        return _oSelf;          
      }
      $.fn.addrInput._initUI = function(event){
        var _oSelf = this,
          _oTarget = $(event.currentTarget);
      }
      $.fn.addrInput._initEvents = function(){}
    })(window.jQuery);
    點評
    1. 美觀
    插件的方法寫在外部,并通過在插件主體傳遞this的方式調(diào)用
    2. 定義插件版本號
    不過在這里還是沒有用到
    3. 關(guān)于call
    這里的第一個參數(shù)為傳遞this,后面的均為參數(shù)
    語法:
    call([thisObj[,arg1[, arg2[, [,.argN]]]]])
    定義:調(diào)用一個對象的一個方法,以另一個對象替換當(dāng)前對象。
    說明:call 方法可以用來代替另一個對象調(diào)用一個方法。call 方法可將一個函數(shù)的對象上下文從初始的上下文改變?yōu)橛?thisObj 指定的新對象。如果沒有提供 thisObj 參數(shù),那么 Global 對象被用作 thisObj。
    4. 關(guān)于”this”
    在插件的方法中,可能有用到指向插件的this、和指向事件觸發(fā)的this,所以事件觸發(fā)的this用event來獲取:event.cuerrntTarget
    event.currentTarget:指向事件所綁定的元素,按照事件冒泡的方式,向上找到元素
    event.target:始終指向事件發(fā)生時的元素
    如:
    html代碼
    <div id="wrapper"> 
      <a href="#" id="inner">click here!</a> 
    </div>
     js代碼
    $('#wrapper').click(function(e) { 
      console.log('#wrapper'); 
      console.log(e.currentTarget); 
      console.log(e.target); 
    }); 
    $('#inner').click(function(e) { 
      console.log('#inner'); 
      console.log(e.currentTarget); 
      console.log(e.target); 
    });
    結(jié)果輸出
    #inner
    <a href="#" id="inner">click here!</a>
    <a href="#" id="inner">click here!</a>
    #wrapper
    <div id="wrapper"><a href="#" id="inner">click here!</a></div>
    <a href="#" id="inner">click here!</a>
    寫法三(原生寫法)
    主體結(jié)構(gòu)
    var MemberCard = function(_aoOption){
      // 配置(默認是從外面?zhèn)鬟M來)
      _aoOption || (_aoOption = {}) ;
      // 初始化函數(shù)
      _init(this);
    }
    var _init = function(_aoSelf) {
      // 函數(shù)執(zhí)行
      _initData(_aoSelf);
      // 調(diào)用對象的私有方法
      _aoSelf._timedHide();
    }
    var _initData = function ( _aoSelf ) {}
    // 私有方法
    MemberCard.prototype._timedHide = function(_aoOptions) {
      var _oSelf = this;
      clearTimeout(this.iHideTimer);  
      // 使用underscore.js的extend方法來實現(xiàn)屬性覆蓋
      var oDefault = extend( { nHideTime: 300 }, _aoOptions );
      _oSelf.iHideTimer = setTimeout( function(){
        // 調(diào)用對象的共有方法
        _oSelf.hide();
      }, oDefault.nHideTime);    
    }
    // 公有方法
    MemberCard.prototype.hide = function(_aoOptions) {}
    使用
    var oColleagueCard = new MemberCard({ nHideTime: 200 });
    oColleagueCard.hide();
    點評:
    1. 關(guān)于屬性覆蓋(對象深拷貝)
    原生函數(shù)實現(xiàn)方法
    function getType(o){
      return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase();
    }
    function extend(destination,source){
      for(var p in source){
        if(getType(source[p])=="array"||getType(source[p])=="object"){
          destination[p]=getType(source[p])=="array"?[]:{};
          arguments.callee(destination[p],source[p]);
        }else{
          destination[p]=source[p];
        }
      }
    }
    demo:
    var test={a:"ss",b:[1,2,3],c:{d:"css",e:"cdd"}};
    var test1={};
    extend(test1,test);
    test1.b[0]="change"; //改變test1的b屬性對象的第0個數(shù)組元素
    alert(test.b[0]); //不影響test,返回1
    alert(test1.b[0]); //返回change
    基于jQuery的實現(xiàn)方法:
    jQuery.extend([deep], target, object1, [objectN]);
    用一個或多個其他對象來擴展一個對象,返回被擴展的對象。
    如果不指定target,則給jQuery命名空間本身進行擴展。這有助于插件作者為jQuery增加新方法。 如果第一個參數(shù)設(shè)置為true,則jQuery返回一個深層次的副本,遞歸地復(fù)制找到的任何對象。否則的話,副本會與原對象共享結(jié)構(gòu)。 未定義的屬性將不會被復(fù)制,然而從對象的原型繼承的屬性將會被復(fù)制。
    demo:
    var options = {id: "nav", class: "header"}
    var config = $.extend({id: "navi"}, options); //config={id: "nav", class: "header"}
     2. 關(guān)于this
    這個對象的所有方法的this都指向這個對象,所以就不需要重新指定
    寫法四
    主體結(jié)構(gòu)
    function EditorUtil() {
      this._editorContent = $( '#editor_content' );
      this._picBtn = $( '#project_pic' );
      this.ieBookmark = null;
    }
    EditorUtil.prototype = {
      consturctor: EditorUtil,
      noteBookmark: function() {
      },
      htmlReplace: function( text ) {
        if( typeof text === 'string' ) {
          return text.replace( /[<>"&]/g, function( match, pos, originalText ) {
            switch( match ) {
              case '<':
                return '<';
              case '>':
                return '>';
              case '&':
                return '&';
              case '"':
                return '"';
            }
          });
        }
        return '';
      },
      init: function() {
        this._memBtn.bind( 'click', function( event ) {
          $(".error_content").hide();
          return false;
        });
      }
    };
    // 初始化富文本編輯器
    var editor = new EditorUtil();
    editor.init();
    總結(jié)
    寫法四和寫法三其實都差不多,但是你們有沒有看出其中的不一樣呢?
    兩種都是利用原型鏈給對象添加方法:
    寫法三:
    MemberCard.prototype._timedHide
    MemberCard.prototype.hide
    寫法四:
    EditorUtil.prototype = {
      consturctor: EditorUtil,
      noteBookmark: function(){},  
      htmlReplace: function(){}
    }
    細看寫法四利用“對象直接量”的寫法給EditorUtil對象添加方法,和寫法三的區(qū)別在于寫法四這樣寫會造成consturctor屬性的改變
    constructor屬性:始終指向創(chuàng)建當(dāng)前對象的構(gòu)造函數(shù)
    每個函數(shù)都有一個默認的屬性prototype,而這個prototype的constructor默認指向這個函數(shù)。如下例所示:
    function Person(name) { 
      this.name = name; 
    }; 
    Person.prototype.getName = function() { 
      return this.name; 
    }; 
    var p = new Person("ZhangSan"); 
    console.log(p.constructor === Person); // true 
    console.log(Person.prototype.constructor === Person); // true 
    // 將上兩行代碼合并就得到如下結(jié)果 
    console.log(p.constructor.prototype.constructor === Person); // true
    function Person(name) { 
      this.name = name; 
    }; 
    Person.prototype.getName = function() { 
      return this.name; 
    }; 
    var p = new Person("ZhangSan"); 
    console.log(p.constructor === Person); // true 
    console.log(Person.prototype.constructor === Person); // true 
    // 將上兩行代碼合并就得到如下結(jié)果 
    console.log(p.constructor.prototype.constructor === Person); // true
    當(dāng)時當(dāng)我們重新定義函數(shù)的prototype時(注意:和上例的區(qū)別,這里不是修改而是覆蓋),constructor屬性的行為就有點奇怪了,如下示例:
    function Person(name) { 
      this.name = name; 
    }; 
    Person.prototype = { 
      getName: function() { 
        return this.name; 
      } 
    }; 
    var p = new Person("ZhangSan"); 
    console.log(p.constructor === Person); // false 
    console.log(Person.prototype.constructor === Person); // false 
    console.log(p.constructor.prototype.constructor === Person); // false
    為什么呢?
    原來是因為覆蓋Person.prototype時,等價于進行如下代碼操作:
    Person.prototype = new Object({ 
      getName: function() { 
        return this.name; 
      } 
    });
    Person.prototype = new Object({ 
      getName: function() { 
        return this.name; 
      } 
    });
    而constructor屬性始終指向創(chuàng)建自身的構(gòu)造函數(shù),所以此時Person.prototype.constructor === Object,即是:
    function Person(name) { 
      this.name = name; 
    }; 
    Person.prototype = { 
      getName: function() { 
        return this.name; 
      } 
    }; 
    var p = new Person("ZhangSan"); 
    console.log(p.constructor === Object); // true 
    console.log(Person.prototype.constructor === Object); // true 
    console.log(p.constructor.prototype.constructor === Object); // true
    function Person(name) { 
      this.name = name; 
    }; 
    Person.prototype = { 
      getName: function() { 
        return this.name; 
      } 
    }; 
    var p = new Person("ZhangSan"); 
    console.log(p.constructor === Object); // true 
    console.log(Person.prototype.constructor === Object); // true 
    console.log(p.constructor.prototype.constructor === Object); // true
    怎么修正這種問題呢?方法也很簡單,重新覆蓋Person.prototype.constructor即可:
    function Person(name) { 
      this.name = name; 
    }; 
    Person.prototype = new Object({ 
      getName: function() { 
        return this.name; 
      } 
    }); 
    Person.prototype.constructor = Person; 
    var p = new Person("ZhangSan"); 
    console.log(p.constructor === Person); // true 
    console.log(Person.prototype.constructor === Person); // true 
    console.log(p.constructor.prototype.constructor === Person); // true
    function Person(name) { 
      this.name = name; 
    }; 
    Person.prototype = new Object({ 
      getName: function() { 
        return this.name; 
      } 
    }); 
    Person.prototype.constructor = Person; 
    var p = new Person("ZhangSan"); 
    console.log(p.constructor === Person); // true 
    console.log(Person.prototype.constructor === Person); // true 
    console.log(p.constructor.prototype.constructor === Person); // true