jQuery源碼-美元背后的一點小技巧


寫在前面:本文比較基礎,僅是一枚菜鳥接觸jquery過程中的一點思考和總結,內容較基礎,希望能對剛接觸jQuery的童鞋有一點幫助 :)   

按照國際慣例(其實就是俺寫作的習慣),首先拋出待問題的場景。至於問題的答案,文章並不會急着揭曉,而是通過逐層遞進的方式,展現思考、解決一個問題的過程

 

1、如何給一個id為casper的標簽添加一個名為“world”的class

考慮下面一個場景,假設我們頁面上有個id為casper的div標簽,如下所示

<div id="casper" class="hello">casper是個大傻瓜,啦啦啦啦啦</div>

現在我們想要給它添加一個class,比如“world”,用jquery的話如何實現?很簡單,不賣關子

$('#casper').addClass('world');

很好,接下來我們思考:如何不用jquery,我們如何如何實現實現上述功能?最簡單的方式:

var node = document.getElementById('casper');
node.className += ' world'; 

getElementById、getElementsByTagName神馬的,名字老長老長的,寫着有點不爽,於是把getElementById這個方法用美元($)包裝下:

function $(id){
  return document.getElementById(id);
}
$('casper').className += ' world';

className品字符串神馬的,jquery的調用方式相比麻煩多了,那再改進下:

function $(id){
    var node = document.getElementById(id);
    node.addClass = function(addName){
        node.className += ' ' + addName;
    };
    return document.getElementById(id);
}
$('casper').addClass('world');

看上去挺像那么一回事了,多優雅的接口啊(熱淚盈眶中)~

真的是這樣嗎,再仔細瞧瞧?於是果斷發現不對勁的地方:對於$,每次調用,都會給返回的dom元素上添加一個addClass方法,這對空間來說是極大的浪費。當然,可以將addClass方法抽取出來:

function addClass(className){
    //實現略
}
function $(id){
    var node = document.getElementById(id);
    node.addClass = addClass;
    return document.getElementById(id);
}
$('casper').addClass('world');

原先的空間浪費問題可以在很大程度上得到解決,但明顯這解決方法還不夠好。如果有那么一種實現方式,讓所有的對象實例都共享一個方法。。。

 

2、jQuery中的實現思路

同樣不必賣關子,這里說的就是原型方法,我們再看下jquery的調用方式

$('#casper').addClass('world');

$('#casper')並不是像我們上面那樣,簡單地將id為casper的元素返回。實際上,$('#casper')返回的是一個jQuery對象,該對象特征如下:

  1. 擁有一個length屬性,length等於你調用$選中的元素的數目,在$('#casper')中為1
  2. 擁有0~n-1的實例屬性,分別對應調用$時選中的第1~第n個元素,如本例中$('#casper')[0]即為目標dom元素
  3. 擁有一堆原型方法,如常見的addClass、removeClass、bind等

根據上面三點,很容易對我們之前寫的代碼進行修改,如下:

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');

其實就幾行代碼的事情,但。。。還是覺得有些不對勁,new $('casper'),平常在用jquery的時候似乎不需要new一下的說,想想看,我們代碼中一坨new是多么可怕的事情~

好吧,其實是因為jQuery幫你完成了構造函數調用的這部分工作,這一小小的細節改善對jQuery的流行起到了很大的幫助。按照這個思路,繼續修改之前的代碼:

function $(id){
    if(!(this instanceof $) return new $(id);    //加了這么個語句
    this[0] = document.getElementById(id);
    this.length = 1;
}

//其他一樣,節省空間不貼代碼

在上面的代碼中,只有一點小小的修改,就是加了個判斷語句 if(!(this instanceof $)) ,作用在於判斷,當$被調用時,究竟是采用以下兩種調用方式的哪一種,關於這種判斷方式,可參考之前寫的《【經驗總結】構造函數的強制調用》

  1. $('casper'),直接調用,於是this為window
  2. new $('casper'),此時$為構造方法,this instanceof $ == true

 

3、jQuery中的源碼實現以及問題所在(俺的疑惑)

羅嗦了這么多,我們看看關於這點,jQuery里是如何實現的,源碼大致如下,一些不相干的代碼略過:

(function( window, undefined ) {

//去掉無關變量聲明等,防止干擾分析
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 );
    },

    //一堆無關細節暫時略過

    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        init: function( selector, context, rootjQuery ) {
            //繼續略過
        }
    };
    // Give the init function the jQuery prototype for later instantiation
    jQuery.fn.init.prototype = jQuery.fn;

    return jQuery;

})();

window.jQuery = window.$ = jQuery;

})( window );

對於研究過jQuery源碼或曾經打算研究jQuery源碼的同學來說,上面這段代碼肯定不會陌生,它有一個特點:看上去比較晦澀,特別是是結合了jQuery源碼里面比較詭異的代碼縮進~

通過閉包返回的jQuery對象,閉包里面是有jQuery函數定義,jQuery函數里面return了new jQuery.fn.init 。。。快速看懂上面這段代碼的秘訣在於:一個支持代碼高亮和職能中括號匹配的編輯器,比如webstorm。。。

上面只是開個小玩笑,繞了這么久,無法是想做下面幾件事情:

  1. 無論有沒有new,只要調用$,都給你返回一個jQuery對象(實際上jQuery.fn.init才是實際的構造函數)
  2. 將jQuery.fn.init.fn指向jQuery.prototype,這樣的話,當我們通過$.fn.newPrototypeAttr 方式向jQuery添加原型屬性或方法,其實最終都成為了jQuery.fn.init地原型屬性或方法
  3. 將constructor屬性指向jQuery,不然$('#casper').constructor 獲得的會是jQuery.fn.init

 

個人覺得上面這段代碼有些費解,似乎完全可以采用相對不那么曲折的方式實現,如下所示,其實思路都是相同的:

然后,就是添加各種原型方法了,兼容性處理和優雅的API,這塊才是精華,這里還沒講到。

(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;

})();

 

問題:jQuery源碼的那種實現方式,至今不明白作用在哪?是有其他的考慮??知道的筒子往不吝賜教! 

 

寫在后面

  文中示例如有錯漏,請指出;如覺得文章對您有用,可點擊“推薦” :)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM