1.1 創建屬於jQuery對象的插件
前面我看到jQuery插件的方式:通過$.extend方式可以定義屬於jQuery本身的全局性的插件,為此我做了下面的測試,大家先看下面這段js代碼:
;(function($){ // 創建jQuery全局作用域的插件 $.extend({ 'wholeftn':function(){ console.log('你要用jQuery.wholeftn()方式調用,如果jQuery(XX).wholeftn()就會報錯'); }, 'wholeattr':'全局jQuery屬性' }); // 創建jQuery對象的插件 $.fn.extend({ 'partfrn':function(){ console.log('你要用jQuery(XX).wholeftn()方式調用,如果jQuery.wholeftn()就會報錯'); }, 'partattr':'局部jQuery作用域' }); })(jQuery)
測試代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>jQuery.extend和jQuery.fn.extend區別</title> </head> <script type="text/javascript" src="js/jquery-1.7.1.js"></script> <script type="text/javascript" src="js/jquery.extenddiff.js"></script> <body> </body> </html> <script type="text/javascript"> $(document).ready(function(){ $.wholeftn();// 你要用jQuery.wholeftn()方式調用,如果jQuery(XX).wholeftn()就會報錯 console.log($.wholeattr);// 全局jQuery屬性 $('body').partfrn();//你要用jQuery(XX).wholeftn()方式調用,如果jQuery.wholeftn()就會報錯 console.log($('body').partattr);// 局部jQuery作用域 // 錯誤測試 console.log($('body').wholeattr);// undefined console.log($.partattr);// undefined //$('body').wholeftn();// $("body").wholeftn is not a function $.partftn();// $.partftn is not a function }); </script>
我們發現$.extend是創建jQuery對象全局的方法和屬性,這很像java里的靜態方法和靜態變量,而用$.fn.extend創建的是jQuery(XX)對象的方法,二者是有區別的:區別在於一個是屬於全局的一個是屬於對象的。我們平時經常使用的$.ajax就是全局方法而$(‘div’).html()就是屬於jQuery對象的方法,我們仔細瞧瞧jQuery手冊里,jQuery幾乎所有的屬性和方法其實都可以按照屬於jQuery全局和屬於jQuery對象進行分類,因此我有個看法了:
想理解jQuery框架的原理,讀懂它的源代碼,插件技術是一個很好的切入點,整個jQuery框架大致就是分為三大部分:第一部分就是如何構建jQuery對象,第二部分是如何創建屬於jQuery全局的屬性和方法,第三部分就是如何創建屬於jQuery對象的屬性和方法了,而第一部分是jQuery框架的根本,后面兩部分就是jQuery框架的延伸了,只要理解了第一部分,包括如何構建jQuery全局插件和jQuery對象插件我們就可以真正理解jQuery框架設計原理了
下面我就會好好分析下jQuery框架是如何構建jQuery對象的。
1.2 jQuery框架里如何定義jQuery對象
jQuery對象的定義使用的是javascript最基礎的技術:對象的創建和對象的繼承技術,具體點就是創建一個javascript對象以及使用javascript里的prototype技術,大家先看下面的代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>javascript 面向對象</title> </head> <body> </body> </html> <script type="text/javascript"> // 定義一個Person類,也可以說是對象,javascript里面對象和類是混合在一起的 // 類或者說對象的定義又是構造函數和類的定義混合在一起的 function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ console.log(this.name); } } Person.prototype.commonType = 'Human'; Person.prototype.commonFtn = function(){ console.log('Name:' + this.name + ';Age:' + this.age + ';Job:' + this.job); } Person.staticType = 'Animal'; Person.staticFtn = function(){ console.log('All are Mammal'); } var per1 = new Person('張三',30,'man'); var per2 = new Person('Lucy',25,'woman'); per1.sayName();// 張三 per2.sayName();// Lucy per1.commonFtn();// Name:張三;Age:30;Job:man per2.commonFtn();// Name:Lucy;Age:25;Job:woman console.log(per1.commonType);// Human console.log(per2.commonType);// Human console.log(Person.staticType);// Animal Person.staticFtn();//All are Mammal console.log(per1.staticType);// undefined console.log(Person.commonType);// undefined //per1.staticFtn();// per1.staticFtn is not a function //Person.commonFtn();// Person.commonFtn is not a function (new Person('李四',40,'man')).commonFtn();// Name:李四;Age:40;Job:man </script>
代碼里的Person換成jQuery是不是就和我們平時使用jQuery.ajax,jQuery(‘div’).html()很類似了,當然還是有點區別的,比如jQuery對象的定義都是如下模式:
Var Person = {}
這個方式和我們上面寫的function Person(){}是一樣的,為了和jQuery保持一致我們可以代碼修改成這樣:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>javascript 面向對象</title> </head> <body> </body> </html> <script type="text/javascript"> // 定義一個Person類,也可以說是對象,javascript里面對象和類是混合在一起的 // 類或者說對象的定義又是構造函數和類的定義混合在一起的 /*function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ console.log(this.name); } }*/ var Person = function(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ console.log(this.name); } } Person.prototype.commonType = 'Human'; Person.prototype.commonFtn = function(){ console.log('Name:' + this.name + ';Age:' + this.age + ';Job:' + this.job); } Person.staticType = 'Animal'; Person.staticFtn = function(){ console.log('All are Mammal'); } var per1 = new Person('張三',30,'man'); var per2 = new Person('Lucy',25,'woman'); per1.sayName();// 張三 per2.sayName();// Lucy per1.commonFtn();// Name:張三;Age:30;Job:man per2.commonFtn();// Name:Lucy;Age:25;Job:woman console.log(per1.commonType);// Human console.log(per2.commonType);// Human console.log(Person.staticType);// Animal Person.staticFtn();//All are Mammal console.log(per1.staticType);// undefined console.log(Person.commonType);// undefined //per1.staticFtn();// per1.staticFtn is not a function //Person.commonFtn();// Person.commonFtn is not a function (new Person('李四',40,'man')).commonFtn();// Name:李四;Age:40;Job:man </script>
這樣就更加接近jQuery的寫法,但是對於jQuery對象的調用還是有些區別的,至少我們使用jQuery對象時候沒有在前面new一下,而是直接jQuery(XXX)的,消除new這個關鍵字的方式其實很簡單就是使用工廠模式,大家看下面的代碼:
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log(this.name); } return o; } createPerson('Lily','16','woman').sayName();// Lily
大家可以看到,工廠模式消除了new的關鍵字。
我簡單閱讀過jQuery1.0版本的源碼,構建jQuery對象的方式和這種簡單的工廠模式很類似,但是之后的jQuery版本創建對象的方式就更加的有技巧了。下面我給出一個根據改進后的jQuery對象定義的方式定義的xQuery對象:
Xquery1.0.0.js代碼如下:
(function(window, undefined) { // 將經常使用的全局對象重新定義在自己寫好的封閉作用域內是提高你代碼速度以及質量的一致方式 // 因為在javascript這種語言里局部變量的效率永遠都是高於全局變量的 var document = window.document,navigator = window.navigator,location = window.location; var xQuery = (function(){ // 我們在定義xQuery里面再定義一個xQuery對象,這個技巧也是把構建jQuery的代碼封裝到 // 一個獨立的封閉作用域里,這個作用域和插件技術里的xQuery區別開來,從而提升了在定義 // xQuery時候的代碼效率 var xQuery = function(selector,context){ return new xQuery.fn.init(); }; // xQuery.fn是xQuery.prototype的別名 xQuery.fn = xQuery.prototype = { init:function(){ this.length = 0; this.test = function(){ return this.length; } return this; }, xquery:'1.0.0', length:1, size:function(){ return this.length; } }; xQuery.fn.init.prototype = xQuery.fn; // 使用xQuery的原型對象覆蓋init的原型對象 return xQuery; })(); window.xQuery = window.$ = xQuery; })(window);
測試頁面的代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>xQuery 測試</title> </head> <script type="text/javascript" src="js/xquery-1.0.0.js"></script> <body> </body> </html> <script type="text/javascript"> console.log($().xquery);// 1.0.0 console.log($().test());// 0 console.log($().size());// 0 </script>
呵呵,上面的代碼是不是有點jQuery的味道啊,關於代碼的解釋我在注釋里面寫的比較詳細了,大家又不理解的地方可以好好讀下我寫的注釋。
這段代碼就是構建jQuery對象的核心代碼,不過理解這段代碼還是很不容易的,講也不太好講,但是我還是要嘗試把它講明白。
1.3 jQuery框架里定義jQuery對象的原理
我先看這段代碼new xQuery.fn.init();,這就是使用了工廠模式返回jQuery對象,因此我們使用jQuery對象之前從不會new一下。但是jQuery里的new的對象很奇怪,他不是直接new xQuery(),直接new xQuery結果會如何了?這個不用寫測試代碼了,我們仔細看看這個就不行,因為這種寫法會產生重復調用,結果會變成一個死循環直到報出內存溢出的錯誤。
大家要注意,使用new xQuery.fn.init()代碼同時我們一定要加一句話xQuery.fn.init.prototype = xQuery.fn;,如果沒有xQuery.fn.init.prototype = xQuery.fn;這段代碼,代碼也是有問題,我們注釋掉代碼,再執行下代碼結果如下:
<script type="text/javascript"> console.log($().xquery);// undefined console.log($().test());// 0 console.log($().size());// 沒有結果 </script>
我們會發現,xquery和size()都沒有被定義,產生上面的結果的原因就是this指針,前面我的博客里講到過javascript里面this指針的運用,如果有對javascript里this指針的使用不太明白的可以參見我的這篇博文,博文的地址是:
javascript筆記:深入分析javascript里對象的創建(中)
javascript里指針用法的核心是:this在對象的方法中,this總是指向調用該方法的對象。
New xQuery.fn.init()里的this指針是指向init方法內部的元素而非是xQuery.fn下的元素,因此外部調用xquery和size()時候會報出沒有定義的錯誤(這個地方大家要細細體會,這個是關鍵所在),為了讓this指針指向xQuery.fn里的元素我們就得把xQuery.fn.init.prototype指向xQuery.fn,這樣init對象的指針就能指向,xQuery.fn里的屬性和方法了,jQuery里面用這樣的技巧巧妙的改變了this指針的指向。最后我還是要總結下:
jQuery這樣構造對象的原因有以下個理由:
- jQuery對象對外應該是可以直接被使用,而不需要使用new關鍵字來構建,因此jQuery對象的定義使用的是工廠模式;
- 但是普通的工廠模式又有自己的局限,我們希望return是構造對象本身,但是直接這么返回會導致重復調用的錯誤,因此jQuery只得重新定義一個對象作為返回對象避免這種重復調用的錯誤;
- 但是重新定義的返回對象又要包含jQuery本身所具有的屬性和方法,換句話說我們要模擬return new jQuery(),但是又不能讓代碼產生重復調用的錯誤,因此最后使用原型技術改變了this指針的指向。
1.4 再看看$.extend和$.fn.extend的原理
jQuery源碼里是這樣的形式定義$.extend和$.fn.extend方法的:
jQuery.extend = jQuery.fn.extend = function() { ….. });
如果我們只傳一個對象參數到$.extend和$.fn.extend方法里,那么這個對象最后會拷貝到this指針里,$.extend方法里的this指針指向的是jQuery對象本身,而$.fn.extend里的this指針是指向jQuery.fn也就是jQuery.prototype(jQuery的原型對象),所以$.extend可以擴展jQuery的屬性和方法,而$.fn.extend可以擴展jQuery對象的屬性和方法。
其實換種寫法構建jQuery插件和使用$.extend和$.fn.extend方法等價的。
下面我改改我前面寫的插件代碼,代碼如下:
;(function($){ // 創建jQuery全局作用域的插件 $.wholeftn = function(){ console.log('你要用jQuery.wholeftn()方式調用,如果jQuery(XX).wholeftn()就會報錯'); }; $.wholeattr = '全局jQuery屬性'; // 創建jQuery對象的插件 $.fn.partfrn = function(){ console.log('你要用jQuery(XX).wholeftn()方式調用,如果jQuery.wholeftn()就會報錯'); }, $.fn.partattr = '局部jQuery作用域'; })(jQuery)
測試代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>jQuery插件的另一種寫法</title> </head> <script type="text/javascript" src="js/jquery-1.7.1.js"></script> <script type="text/javascript" src="js/jquery.newplug.js"></script> <body> </body> </html> <script type="text/javascript"> $(document).ready(function(){ $.wholeftn();// 你要用jQuery.wholeftn()方式調用,如果jQuery(XX).wholeftn()就會報錯 console.log($.wholeattr);// 全局jQuery屬性 $('body').partfrn();//你要用jQuery(XX).wholeftn()方式調用,如果jQuery.wholeftn()就會報錯 console.log($('body').partattr);// 局部jQuery作用域 // 錯誤測試 console.log($('body').wholeattr);// undefined console.log($.partattr);// undefined //$('body').wholeftn();// $("body").wholeftn is not a function $.partftn();// $.partfrn is not a function }); </script>
結果和使用$.extend和$.fn.extend一樣的,但是這樣的代碼實在是丑陋,沒有使用extend方法來的優雅。
好了,關於jQuery插件技術的分析到此結束,后面我還會寫一篇文章,想聊聊編寫插件的一些規范和技巧,這篇文章應該更加實用點。
只有當你真正理解某個技術的原理時候,你才能使用好這門技術,這就是我現在對我使用jQuery技術的要求,我會一直朝這個方向努力下去的。
我將我最近的研究做成了pdf,大家可以點擊這個地址下載:
源代碼的地址是:
http://files.cnblogs.com/sharpxiajun/jqextendstudy.zip