在Jquery中,$是JQuery的別名,所有使用$的地方也都可以使用JQuery來(lái)替換,如$('#msg')等同于JQuery('#msg') 的寫法。
1、如何給一個(gè)id為casper的標(biāo)簽添加一個(gè)名為“world”的class
考慮下面一個(gè)場(chǎng)景,假設(shè)我們頁(yè)面上有個(gè)id為casper的div標(biāo)簽,如下所示
<div id="casper" class="hello">casper是個(gè)大傻瓜,啦啦啦啦啦</div>
現(xiàn)在我們想要給它添加一個(gè)class,比如“world”,用jquery的話如何實(shí)現(xiàn)?很簡(jiǎn)單,不賣關(guān)子
$('#casper').addClass('world');
很好,接下來(lái)我們思考:如何不用jquery,我們?nèi)绾稳绾螌?shí)現(xiàn)實(shí)現(xiàn)上述功能?最簡(jiǎn)單的方式:
var node = document.getElementById('casper'); node.className += ' world';
getElementById、getElementsByTagName神馬的,名字老長(zhǎng)老長(zhǎng)的,寫著有點(diǎn)不爽,于是把getElementById這個(gè)方法用美元($)包裝下:
function $(id){ return document.getElementById(id); }
$('casper').className += ' world';
className品字符串神馬的,jquery的調(diào)用方式相比麻煩多了,那再改進(jìn)下:
function $(id){
var node = document.getElementById(id); node.addClass = function(addName){ node.className += ' ' + addName; }; return document.getElementById(id); } $('casper').addClass('world');
看上去挺像那么一回事了,多優(yōu)雅的接口啊(熱淚盈眶中)~
真的是這樣嗎,再仔細(xì)瞧瞧?于是果斷發(fā)現(xiàn)不對(duì)勁的地方:對(duì)于$,每次調(diào)用,都會(huì)給返回的dom元素上添加一個(gè)addClass方法,這對(duì)空間來(lái)說(shuō)是極大的浪費(fèi)。當(dāng)然,可以將addClass方法抽取出來(lái):
function addClass(className){
//實(shí)現(xiàn)略 } function $(id){ var node = document.getElementById(id); node.addClass = addClass; return document.getElementById(id); } $('casper').addClass('world');
原先的空間浪費(fèi)問(wèn)題可以在很大程度上得到解決,但明顯這解決方法還不夠好。如果有那么一種實(shí)現(xiàn)方式,讓所有的對(duì)象實(shí)例都共享一個(gè)方法。。。
2、jQuery中的實(shí)現(xiàn)思路
同樣不必賣關(guān)子,這里說(shuō)的就是原型方法,我們?cè)倏聪耲query的調(diào)用方式
$('#casper').addClass('world');
$('#casper')并不是像我們上面那樣,簡(jiǎn)單地將id為casper的元素返回。實(shí)際上,$('#casper')返回的是一個(gè)jQuery對(duì)象,該對(duì)象特征如下:
擁有一個(gè)length屬性,length等于你調(diào)用$選中的元素的數(shù)目,在$('#casper')中為1
擁有0~n-1的實(shí)例屬性,分別對(duì)應(yīng)調(diào)用$時(shí)選中的第1~第n個(gè)元素,如本例中$('#casper')[0]即為目標(biāo)dom元素
擁有一堆原型方法,如常見(jiàn)的addClass、removeClass、bind等
根據(jù)上面三點(diǎn),很容易對(duì)我們之前寫的代碼進(jìn)行修改,如下:
function $(id){
this[0] = document.getElementById(id); this.length = 1; } $.prototype.addClass = function(className){ this[0].className += ' ' + className; }; var noode = new $('casper'); node.addClass('world');
其實(shí)就幾行代碼的事情,但。。。還是覺(jué)得有些不對(duì)勁,new $('casper'),平常在用jquery的時(shí)候似乎不需要new一下的說(shuō),想想看,我們代碼中一坨new是多么可怕的事情~
好吧,其實(shí)是因?yàn)閖Query幫你完成了構(gòu)造函數(shù)調(diào)用的這部分工作,這一小小的細(xì)節(jié)改善對(duì)jQuery的流行起到了很大的幫助。按照這個(gè)思路,繼續(xù)修改之前的代碼:
function $(id){
if(!(this instanceof $) return new $(id); //加了這么個(gè)語(yǔ)句 this[0] = document.getElementById(id); this.length = 1; } //其他一樣,節(jié)省空間不貼代碼
在上面的代碼中,只有一點(diǎn)小小的修改,就是加了個(gè)判斷語(yǔ)句 if(!(this instanceof $)) ,作用在于判斷,當(dāng)$被調(diào)用時(shí),究竟是采用以下兩種調(diào)用方式的哪一種,關(guān)于這種判斷方式,可參考之前寫的《【經(jīng)驗(yàn)總結(jié)】構(gòu)造函數(shù)的強(qiáng)制調(diào)用》:
$('casper'),直接調(diào)用,于是this為window
new $('casper'),此時(shí)$為構(gòu)造方法,this instanceof $ == true
3、jQuery中的源碼實(shí)現(xiàn)以及問(wèn)題所在(俺的疑惑)
羅嗦了這么多,我們看看關(guān)于這點(diǎn),jQuery里是如何實(shí)現(xiàn)的,源碼大致如下,一些不相干的代碼略過(guò):
(function( window, undefined ) {
//去掉無(wú)關(guān)變量聲明等,防止干擾分析 var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }, //一堆無(wú)關(guān)細(xì)節(jié)暫時(shí)略過(guò) jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { //繼續(xù)略過(guò) } }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; return jQuery; })(); window.jQuery = window.$ = jQuery; })( window );
對(duì)于研究過(guò)jQuery源碼或曾經(jīng)打算研究jQuery源碼的同學(xué)來(lái)說(shuō),上面這段代碼肯定不會(huì)陌生,它有一個(gè)特點(diǎn):看上去比較晦澀,特別是是結(jié)合了jQuery源碼里面比較詭異的代碼縮進(jìn)~
通過(guò)閉包返回的jQuery對(duì)象,閉包里面是有jQuery函數(shù)定義,jQuery函數(shù)里面return了new jQuery.fn.init 。。�?焖倏炊厦孢@段代碼的秘訣在于:一個(gè)支持代碼高亮和職能中括號(hào)匹配的編輯器,比如webstorm。。。
上面只是開個(gè)小玩笑,繞了這么久,無(wú)法是想做下面幾件事情:
無(wú)論有沒(méi)有new,只要調(diào)用$,都給你返回一個(gè)jQuery對(duì)象(實(shí)際上jQuery.fn.init才是實(shí)際的構(gòu)造函數(shù))
將jQuery.fn.init.fn指向jQuery.prototype,這樣的話,當(dāng)我們通過(guò)$.fn.newPrototypeAttr 方式向jQuery添加原型屬性或方法,其實(shí)最終都成為了jQuery.fn.init地原型屬性或方法
將constructor屬性指向jQuery,不然$('#casper').constructor 獲得的會(huì)是jQuery.fn.init
個(gè)人覺(jué)得上面這段代碼有些費(fèi)解,似乎完全可以采用相對(duì)不那么曲折的方式實(shí)現(xiàn),如下所示,其實(shí)思路都是相同的:
然后,就是添加各種原型方法了,兼容性處理和優(yōu)雅的API,這塊才是精華,這里還沒(méi)講到。
(function(){
var jQuery = function(id){ return new _jquery(id); }; var _jquery = function(id){ //此處各種選擇分支神馬的都忽略~ this[0] = document.getElementById(id); this.length = 1; }; jQuery.fn = jQuery.prototype = { constructor: jQuery, addClass: function(className){ this[0].className += ' ' + className; } }; _jquery.prototype = jQuery.fn; window.$ = window.jQuery = jQuery; })();