天天看點

【jQuery學習筆記------jQuery原型技術分解】jQuery原型技術分解

jQuery原型技術分解

起源----原型繼承

   使用者過javascript的都會明白,在javascript腳本中到處都是函數,函數可以歸置代碼段,把相對獨立的功能封閉在一個函數包中。函數也可以實作類,這個類是面向對象程式設計中最基本的概念,也是最高抽象,定義一個灰就相當于制作一個模型,然後借助這個模型複制無數的執行個體。

   例如,下面的就可定義最初的jQuery類,類名就是jQuery,你可以把它視為一個函數,函數名是jQuery。那當然也可以把它視為一個對象,對象名是jQuery。與其也面向對象的程式設計語言比,javascript對于這些概念的界定好像很随意,這降低了程式設計的門檻,反之也降低了javascript作為程式設計語言的層次。

        <script language=”javascript” type=”text/javascript”>

               var jQuery = function(){

                                 //函數體

                    }

         </script>

     上面建立一個空函數,好像什麼都不能夠做,這個函數實際上是所謂的構造函數。構造函數在面向對象語言中是類的一個特殊方法,用來建立類。在javascript中,你可以把任何函數都視為構造函數,這沒有什麼不可以的,這樣不會傷害代碼本身。

     所有類都有最基本的功能,如繼承、派生和重寫等。javascript很奇特,它通過所有函數綁定一個prototype屬性,由這個屬性指向一個原型對象,原型對象中可以定義類的繼承屬性和方法,是以,對于上面的空類,可以繼續擴充原型。(繼承請參見js學習筆記---類和子產品)

        <script language=”javascript” type=”text/javascript”>

               var jQuery = function(){}

               jQuery.prototype = {

                      //擴充原型對象

               }

         </script>

原型對象是javascript實作繼承的基本機制。如果覺得jQuery.prototype名稱太長,沒有關系,我們可以為其重新命名,如fn,當然你可以随便命名。如果直接命名fn,則表示該名稱屬于Window對象,即全局變量名。更安全的方法是為jQuery類定義一個公共屬性jQuery.fn,然後把jQuery的原型對象傳遞給這個公共屬性,實作代碼如下:

        <script language=”javascript” type=”text/javascript”>

               var jQuery = function(){}

               jQuery.fn=jQuery.prototype= {

                      //擴充原型對象

               }

         </script>

這裡jQuery.fn相當于jQuery.prototype的别名,友善以後使用,它們指向一個引用。是以若要調用jQuery的原型方法,直接使用jQuery.fn公共屬性即可,不需要直接引用jQuery.prototype,當然直接使用jQuery.prototype也是可以的。然後原型對象可以使用别名,jQuery類也可以起個别名,我們可以使用$符号來引用它,如下:var $ = jQuery = function(){}

現在模仿jQuery架構源碼,給它添加兩個成員,一個是原型屬性jquery,一個是原型方法size(),如下:

        <script language=”javascript” type=”text/javascript”>

               var $=jQuery = function(){}

              jQuery.fn = jQuery.prototype = {

                    jquery:”1.10.01”  //原型屬性

                    size:function(){    //原型方法

                           return this.length;

                     }

              }

         </script>

生命------傳回執行個體

上例中的代碼相信很多人都寫過,但是隻有John Regis一個人把它發展成了jQuery這樣牛X的架構。

上面定義了兩個原型成員,這個架構基本的樣子就出來啦,但是該如何調用jquery屬性和size()方法呢?

也許,你可采用如下的方法調用:

     <script language=”javascript” type = “text/javascript”>

              var my$ = new $();

             alert(my$.jquery);

             alert(my$.size());

      </script>

但是,jQuery不是這樣調用的。它模仿類似下面的方法進行調用。

             $().jquery

             $().size();

也就是說,我們應該把jQuery看做一個類,同時也應該把它視為一個普通函數,并讓這個函數傳回值為jQuery類的執行個體。是以如下

    <script language=”javascript” type=”text/javascript”>

              var $=jQuery = function(){

                       return  new jQuery();

                }

              jQuery.fn = jQuery.prototype = {

                    jquery:”1.10.01”  //原型屬性

                    size:function(){    //原型方法

                           return this.length;

                     }

              }

//調用

             alert(my$.jquery);

             alert(my$.size());

    </script>

     但是,如果在浏覽器中預覽,則會提示如圖所示的錯誤。記憶體外溢,說明出現了死循環引用。

回一下,當使用var my$ = new$(); 建立jQuery類的執行個體時,this關鍵字就指向對象my$,是以my$執行個體對象就獲得了jQuery.prototype包含的原型屬性或方法,這些方法内this關鍵字就會自動指向my$執行個體對象。是以可以使用一個工廠方法來建立一個執行個體,這個方法放在jQuery.prototype原型對象中,然後在jQuery函數中傳回這個原型方法的調用。代碼如下:

            <script language=”javascript”type=”text/javascript”>

                   var $ = jQuery = function(){

                                    return  jQuery.fn.init(); //調用原型方法init();

                         }

                   jQuery.fn = jQuery.prototype= {

                          init:function(){

                               return this;

                          }

                         ,jquery:”1.10.01

                         ,size:function(){

                               return  this.length;

                          }

                   }

          //調用

             alert(my$.jquery);    //調用屬性,傳回”1.3.2

             alert(my$.size());     //調用方法,傳回undefined

            </script>

學步-----分隔作用域

我們已經初步實作了讓jQuery函數能夠傳回jQuery執行個體,下面繼續思考:init()方法傳回的是this關鍵字,該關鍵字引用的是jQuery類的執行個體,如果在init()函數中繼續使用this關鍵字,也就是說,假設我們把init()函數也視為一個構造器,則其中的this該如何了解和處理?

例如,在下面的示例中,jQuery原型對象中包含一個length屬性,同時init()從一個普通的函數轉身變成了構造器,它也包含一個length屬性和一個test()方法,運作該示例,我們可以看到,this關鍵字引用init()函數作用域所在的對象,此時它通路length屬性時,傳回0.而this關鍵字也能夠通路一級對象jQury.fn對象的作用域,是以$().jquery傳回”1.3.2”,但是調用$().size()方法時,傳回的是0,而不是1。

            <script language=”javascript”type=”text/javascript”>

                   var $ = jQuery = function(){

                                    return  jQuery.fn.init(); //調用原型方法init();

                         }

                   jQuery.fn = jQuery.prototype= {

                          init:function(){

                                 this.length=0;

                                 this.test = function(){

                                                   return this.length;

                                               }

                               return this;

                          }

                         ,jquery:”1.10.01

                         ,length:1

                         ,size:function(){

                               returnthis.length;

                          }

                   }

          //調用

             alert($().jquery);    //調用屬性,傳回”1.3.2

             alert($().test());     //調用方法,傳回0

             alert($().size());     //調用方法,傳回0

            </script>

    這種設計思路很容易破壞作用域的獨立性,對于jQuery這樣的架構來說,很可能會造成消極影響,是以我們可以看到jQuery架構是通過下面的方式調用init()初始化構造函數的

          <script language=”javascript”type=”text/javascript”>

                 var $=jQuery=function(){

                           return new  jQuery.fn.init(); //執行個體化init初始化類型,分隔作用域

                       }

          </script>

          這樣就可以把init()構造器中的this和jQuery.fn對象中的this關鍵字隔離開來,避免互相混淆。但是,這種方式也會帶來另一個問題,無法通路,jQuery.fn對象的屬性或方法。如下:

            <script language=”javascript”type=”text/javascript”>

                    var $=jQuery=function(){

                            return new  jQuery.fn.init();

                    }

                     jQuery.fn =jQuery.prototype = {

                               init:function(){

                                       this.length = 0;

                                        this.test= function(){

                                                  return this.length;

                                         }

                               },

                               jquery:”1.10.01”,

                               length:1,

                               size:function(){

                                        return this.length;

                               }

                     }

             alert($().jquery);    //調用屬性,傳回 undefined

             alert($().test());     //調用方法,傳回0

             alert($().size());     //調用方法,傳回抛出異常

            </script>

生長-----跨域通路

如何做到既能分隔始始化構造函數與jQuery原型對象的作用域,又能夠在傳回執行個體中通路jQuery原型對象呢?

jQuery架構巧妙地通過原型傳遞解決了這個問題,它把jQuery.fn傳遞給jQuery.fn.init.prototype,也就是說jQuery的原型對象覆寫init構造器的原型對象,進而實作跨域通路,

            <script language=”javascript”type=”text/javascript”>

                    var $=jQuery=function(){

                            return new  jQuery.fn.init();

                    }

                     jQuery.fn =jQuery.prototype = {

                               init:function(){

                                       this.length = 0;

                                        this.test= function(){

                                                  return this.length;

                                         }

                               },

                               jquery:”1.10.01”,

                               length:1,

                               size:function(){

                                        return this.length;

                               }

                     }

             jQuery.fn.init.prototype =jQuery.fn; //使用jQuery的原型對象覆寫init的原型對象。

             alert($().jquery);    //調用屬性,傳回 1.10.01

             alert($().test());     //調用方法,傳回0

             alert($().size());     //調用方法,0

            </script>

成熟------選擇器

jQuery傳回的是jQuery對象,它是一個類數組對象,本質上它就是一個對象,但是它擁有數組的長度和下标,卻沒有繼承數組的方法。

jQuery函數包含兩個參數selector和context,其中selector表示選擇器,而context表示選擇内容範圍,它表示一個DOM元素,為了簡化操作,我們假設選擇的類型僅限定為标簽選擇器。

            <div></div>

            <div></div>

            <div></div>

            <script language=”javascript”type=”text/javascript”>

                    var $=jQuery=function(){

                            return new jQuery.fn.init();

                    }

                     jQuery.fn = jQuery.prototype = {

                               init:function(selector,context){

                                       selector = selector|| document;

                                       context= context || document;

                                       if(selector.nodeType){

                                             this[0] = selector;

                                             this.length = 1;

                                             this.context = selector;

                                              return this;

                                         }

                                        if(typeof selector = “string”){

                                              var e = context.getElementByTagName(selector);

                                                for(vari=0;i<e.length;i++){

                                                   this[i] = e[i];

                                               }

                                                this.length = e.length;

                                                 this.context = context;

                                                  return this;

                                         }else{

                                                this.length = 0;

                                                this.context = context;

                                                 return this;

                                          }

                               },

                               jquery:”1.10.01”,

                               length:1,

                               size:function(){

                                        return this.length;

                               }

                     }

             jQuery.fn.init.prototype =jQuery.fn; //使用jQuery的原型對象覆寫init的原型對象。

             alert($(“div”).size());    //傳回3

            </script>

延續----疊代器

在jQuery架構中,jQuery對象是一個很奇怪的概念,具有多重身份:

第一、  jQuery對象是一個資料集合,它不是一個個體對象,是以,你無法直接使用javascript的方法。

第二、  jQuery對象實際上是一個普通 的對象,因為它是通過new運作符建立的一個新的執行個體對象。它可以繼承原型方法或屬性,同時也擁有Object類型的方法和屬性。

第三、  jQuery對象包含數組特性,因為它指派了數組元素,以數組結構存儲傳回的資料。我們可以以javascript的概念了解jQuery對象,如:

<script language=”javascript” type = “text/javascript”>

       var jquery= {

                name:”jquery”,

                value:”1.2.3”

};

                    jquery[0] = “jquery”;

                   jquery[1] = “1.2.3”;

                   alert(jquery.name);  //傳回”jquery”

                   alert(jquery[0]); //傳回”jquery”

</script>

            jQuery對象的結構是按這種形式設計的。可以說,jQuery對象就是對象和數組的混合體,但是它不擁有數組的方法,因為它的數組結構是人為附加的,也就說它不是Array類型的資料,而是Object類型的資料。

第四、  jQuery對象包含的資料都是DOM元素,是通過數組形式存儲的,即通過jQuery[n]形式擷取。同時jQuery對象又定義了幾個模仿Array基本特性的屬性,如length等。

是以jQuery對象是不允許直接操作,隻有分别讀取它包含的每一個DOM元素,才能夠實作各種操作。

延續-----功能擴充

根據一般設計習慣,如果要為jQuery或jQuery.prototype添加函數或方法,可以直接通過點(.)文法實作,或者在jQuery.prototype對象結構中增加一個屬性即可。但是,如果分析jQuery架構的源代碼,你會發現它是通過extend()函數來實作功能擴充的。例如下面兩段代碼都是jQuery架構通過extend()函數來擴充功能的

     jQuery.extend({

           noConflict:function(deep){},

           isFunction:function(obj){},

           isArray:function(obj){},

           isXMLDoc:function(elem){},

           globalEval:function(data){},

    })

      或者

     jQuery.fn.extend({

           show:function(speed,callback){},

           hide:function(speed,callback){},

           toggle:function(fn,fn2){},

           fadeTo:function(speed,to,callback){},

           animate:function(prop,speed,easing,callback){},

           stop:function(clearQueue,gotoEnd){},

    })

   extend()函數能夠友善使用者快速擴充jQuery架構的功能。但是不會破壞架構的原型結構,進而避免後期人工手動添加工具函數或者方法時破壞jQuery架構的單純性,同時也友善管理。如果不需要某個插件,隻需要簡單地删除即可,而不是需要在jQuery架構源代碼中去篩選和删除。

    extend()函數的功能實作起來也很簡單,它隻要把指定對象的方法複制給jQuery對象或jQuery.prototype對象。例如,在下面的示例中,我們為jQuery類和原型定義一個擴充功能函數extend(),該函數的功能很簡單,它能夠把指定參數對象包含的所有屬性複制給jQuery或者jQuery.prototype對象,這樣就可以在應用中随時調用它,并動态擴充jQuery對象的方法。

     //jQuery功能擴充函數

    jQuery.fn.extend = jQuery.extend =function(obj){

          for(var prop in obj) this[prop] =obj[prop];

           return this;

    }

jQuery架構定義的extend()函數的功能要強大很多,它不僅能夠完成基本的功能擴充,還可以實作對象合并等功能,詳細代碼和解釋如下:

     jQuery.extend = jQuery.fn.extend =function(){

         //定義複制操作的目标對象

         var target = arguments[0]||{},i=1,length=arguments.length,deep = false,options;

         //擷取是否深度複制處理

         if(typeof target ===”boolean”){

               deep = target;

               target = arguments[1] || {};

               //跳出布爾值和目标對象

               i = 2;

         }

         if(typeof target!=”object” &&!jQuery.isFunction(target)) target = {};

         if(length==i){

             target = this;

              --i;

         }

         for(;i<length;i++){

             //若參數不為null,則進行處理

             if((options=arguments[i])!=null){

                          //extend the baseobject

                        for(var name in options){ //周遊參數對象

                              var src =target[name],copy = options[name];

                              if(target===copy)continue;

                               //遞歸運算

                               if(deep&& copy && typeof copy===”object” && !copy.nodeType)

                                   target[name]= jquery.extend(deep,

                                                 //不要複制原對象

                                                       src || (copy.length!=null?[]:{}),copy);

                              else if(copy!==undefined)

                                    target[name] = copy;

                        }

                   return target;

              }

         }

     }

延續----參數處理

對于參數不固定,傳遞參數的最好辦法是對象直接量。

命名空間

var jQuery =function(){}

jQuery=function(){}

上面所示的代碼是兩種不同的寫法,且都是合法的,但是它們的主義完全不同。第一行代碼聲明了一個變量。而第二行代碼定義了Window對象的一個屬性,也就是說它等同于下面的語句

window.jQuery =function(){};

在全局作用域中,變量和Window對象的屬性可以是相等的,也是可以是互通的,但是當在其他環境中(如局部作用域中),則它們是不相等的,也是無法互通的,是以如果希望jquery具有類似$.method();調用的能力,就需要将jQuery調協為Window對象的一個屬性,是以你就會看到jQuery架構中是這樣定義的。

     var jQuery = window.jQuery = window.$ =function(selectio,context){

                return new jQuery.fn.init(selector,context);

     }

     <script language=”javascript” type=text/javascript”>

           (function(){

                 function f1(){

                           return “f1()”;

                  }

           })();

             function f2(){

                     return “f2()”;

              }

           alert(f2());  //傳回”f2()”

           alert(12());  //抛出異常,禁止通路

     </script>

   實際上,上面的匿名函數是所謂的閉包,閉包是javascript函數中一個最核心的概念。當然,$和jQuery名字并非是jQuery架構的專利,其他一些經典架構中也會用到$名字,為此jQuery提供了一個noConfilit()方法,該方法能夠實作禁止jQuery架構使用這兩個名字。為了實作這樣的目的,jQuery在架構的最前面,先使用_$和_jQuery臨時寄存這兩個變量的内容,當需要禁用jQuery架構的名字時,可以使用一個臨時變量_$和_jQuery恢複和jQuery這兩個變量的實際内容。如下代碼:

  (function(){

        var window = this,

            undefined,

            _jQuery = window.jQuery,

            _$ = window.$,

            jQuery = window.jQuery = window.$ =function(selector,context){

              return newjQuery.fn.init(selector,context);

            };

        jQuery.fn = jQuery.prototype = {

            init:function(selector,context){}

        }

    })()