jQuery 源碼框架解析

參考資料:饑人谷任務(wù)班任務(wù)42視頻哺眯,jQuery 2.0.3 源碼分析core - 整體架構(gòu)

$調(diào)用方法的實(shí)現(xiàn)

jQuery 的每一個(gè)實(shí)例并沒有通過 new 來新建,而是使用 $jQuery 來新增實(shí)例并直接進(jìn)行方法的調(diào)用蜕径,如:

$(".btn").on("click", function() {
    console.log("do sth.");
});
jQuery.each(function() {
    console.log("do sth.");
});

為使每次調(diào)用 $jQuery 能夠創(chuàng)建實(shí)例,在構(gòu)建 jQuery 對(duì)象時(shí),返回值應(yīng)該是對(duì)象的實(shí)例哥倔。

下面嘗試以 rQuery 進(jìn)行重現(xiàn)史煎。

var rQuery = function(selector, context) {
    //返回實(shí)例
}
rQuery.prototype = {};

如果直接使用 new 創(chuàng)建實(shí)例番甩,會(huì)產(chǎn)生死循環(huán):

var rQuery = function(selector, context) {
    return new rQuery; //沒有盡頭的遞歸
}
rQuery.prototype = {};

因此,要想返回正確的實(shí)例溢豆,必須考慮其他的途徑币绩。由于 new 需要針對(duì)方法來執(zhí)行蜡秽,因此通過返回 rQuery.prototype 來創(chuàng)建實(shí)例也是不可行的。但我們可以在原型中新增一個(gè)方法缆镣,嘗試通過返回這個(gè)方法來創(chuàng)建實(shí)例:

var rQuery = function(selector, context) {
    return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
    init: function() {
        return this; // this 指向?qū)嵗客唬簿褪?rQuery
    }
};

不過,這種方法會(huì)帶來一個(gè)新的問題(如圖):所返回的實(shí)例和 rQuery 對(duì)象并沒有對(duì)應(yīng)的關(guān)系(淡粉色線)董瞻,新的實(shí)例不能調(diào)用 rQuery 中的方法寞蚌。

var rQuery = function(selector, context) {
    return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
    init: function() {
        return this;
    }
    print: function() {
        console.log(this);
    }
};

rQuery.print(); // 報(bào)錯(cuò)

因此田巴,必須顯性地將 init 方法的原型定義為 rQuery 的原型(紅色線):

rQuery/init 的原型關(guān)系圖
var rQuery = function(selector, context) {
    return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
    init: function() {
        return this;
    }
};

rQuery.prototype.init.prototype = rQuery.prototype;

這樣,每次使用 rQuery 時(shí)就會(huì)產(chǎn)生新的實(shí)例挟秤,并且可以正常使用 rQuery 原型中定義的方法壹哺。

定義 window.$ = window.rQuery = rQuery 即可使用 $ 代替 rQuery 進(jìn)行操作。

鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn)

在 jQuery 中艘刚,可以對(duì)方法進(jìn)行鏈?zhǔn)秸{(diào)用管宵。使用鏈?zhǔn)秸{(diào)用節(jié)約代碼、提高效率攀甚,簡(jiǎn)單優(yōu)雅箩朴,可謂 jQuery 的一大魅力。

$(this).removeClass("active").addClass("bye");

要能夠進(jìn)行鏈?zhǔn)秸{(diào)用秋度,則必須在第一個(gè)方法的調(diào)用之后返回對(duì)象本身:

var $tmp = $(this).removeClass("active"); //$tmp === $(this)

$tmp.addClass("bye"); //相當(dāng)于 $(this).addClass("bye")

因此炸庞,在定義可以使用鏈?zhǔn)秸{(diào)用的方法時(shí),需要添加 return this; 以保證鏈?zhǔn)秸{(diào)用能夠?qū)崿F(xiàn):

var rQuery = function(selector, context) {
    return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
    init: function() {
        return this;
    },
    setName: function(name) {
        this.name = name;
        return this;
    },
    printName: function(name) {
        console.log(this.name);
        return this;
    }
};

rQuery.prototype.init.prototype = rQuery.prototype;

rQuery().setName("Hanna").printName(); //Hanna

插件接口的定義與實(shí)現(xiàn)

同其他庫(kù)一樣荚斯,jQuery 提供可以讓開發(fā)者添加擴(kuò)展的接口埠居。開發(fā)者可以通過 jQuery.fn.extend() 來向 jQuery 中添加新的方法。

jQuery 中事期,同時(shí)存在 jQuery.extend()jQuery.fn.extend() 兩種方法滥壕,從源碼中可以發(fā)現(xiàn),二者實(shí)際上是指向同一個(gè)方法的不同引用:

jQuery.extend = jQuery.fn.extend = function() {
    //具體實(shí)現(xiàn)
}

這樣做的用意是:通過 jQuery.extend() 實(shí)現(xiàn)對(duì) jQuery 本身的屬性和方法的擴(kuò)展刑赶;而通過 jQuery.fn.extend() 來實(shí)現(xiàn)對(duì) jQuery.fn捏浊,即 jQuery 的原型的屬性和方法進(jìn)行擴(kuò)展。同時(shí)撞叨,兩種擴(kuò)展都不會(huì)破壞 jQuery 原有的結(jié)構(gòu)金踪。

之所以在使用 jQuery.extend = jQuery.fn.extend的前提下仍能夠區(qū)別出 jQuery 和 jQuery.fn,是因?yàn)閷?duì) this 的運(yùn)用牵敷。 this 指向的是調(diào)用方法的對(duì)象胡岔。在 jQuery.extend() 中,調(diào)用 extend() 方法的是 jQuery枷餐,所以 this 指向 jQuery靶瘸,而在 jQuery.fn.extend() 中,調(diào)用方法的則是 jQuery.fn毛肋,因此這里 this 指向了 jQuery.fn怨咪,從而成功地區(qū)分出了兩種不同的擴(kuò)展方式。

之前曾參考《使用jquery實(shí)現(xiàn)簡(jiǎn)單的拖動(dòng)效果》實(shí)現(xiàn)了一個(gè)拖拽效果润匙,拿到這里來試一下 extend() 方法的使用(在線DEMO):

//html
<div class="panel">This div is draggable</div>
//javascript
//drag()
var drag = function() {
    $(this).each(function() {
        var me = $(this);
        var isMoved = false;
        var rX, rY;

        me.on("mousedown", function(event) {
            isMoved = true;
            rX = event.pageX - parseInt(me.css("left"));
            rY = event.pageY - parseInt(me.css("top"));

            me.css("cursor", "move");
            return false; //解決拖拽導(dǎo)致的文字選中問題
        });

        $(document).on("mousemove", function(event) {
            if (isMoved) {
                var eX = event.pageX;
                var eY = event.pageY;
                var wX = $(window).width();
                var wY = $(window).height();
                var thisX = me.width();
                var thisY = me.height();
                var thisMarginLeft = parseInt(me.css("margin-left"));
                var nowX = eX - rX;
                var nowY = eY - rY;
                var left, top;

                var thisLeft = (thisMarginLeft > 0) ? 0 : (-thisMarginLeft);

                if (nowX < thisLeft) {
                    left = thisLeft;
                } else if (nowX > wX - thisX + thisLeft) {
                    left = wX - thisX + thisLeft;
                } else {
                    left = nowX;
                }

                if (nowY < 0) {
                    top = 0;
                } else if (nowY > wY - thisY) {
                    top = wY - thisY;
                } else {
                    top = nowY;
                }

                me.css({
                    "left": left,
                    "top": top
                });
            }
        }).on("mouseup", function() {
            isMoved = false;
            me.css("cursor", "auto");
        });
    });
};
// 添加到 jQuery 中
jQuery.fn.extend({drag: drag});
// 調(diào)用
$(".panel").drag();

可以看出诗眨,drag() 方法已成功添加到 jQuery 當(dāng)中,可以用 $(".panel").drag() 進(jìn)行調(diào)用孕讳,并實(shí)現(xiàn)拖拽功能匠楚。

drag() 為例巍膘,來看 extend() 方法的實(shí)現(xiàn):

jQuery.extend = jQuery.fn.extend = function() {
  var src, copyIsArray, copy, name, options, clone,
    target = arguments[0] || {}, // 傳入的參數(shù),以第一個(gè)參數(shù)為目標(biāo)對(duì)象(這里是 {drag: drag})
    i = 1,
    length = arguments.length,
    deep = false; // 深拷貝的參數(shù)

  // Handle a deep copy situation
  if ( typeof target === "boolean" ) { // 如果第一個(gè)參數(shù)是 true/false芋簿,通常為 true
    deep = target; //deep 為 true/false
    target = arguments[1] || {}; // 將第二個(gè)參數(shù)作為目標(biāo)對(duì)象
    // skip the boolean and the target
    i = 2; // 跳過前兩個(gè)參數(shù)峡懈,從第三個(gè)開始
  }

  // Handle case when target is a string or something (possible in deep copy)
  if ( typeof target !== "object" && !jQuery.isFunction(target) ) { // 如果作為目標(biāo)對(duì)象的參數(shù)不是對(duì)象類型
    target = {};
  }

  // extend jQuery itself if only one argument is passed
  if ( length === i ) { // 如果只有一個(gè)參數(shù)
    target = this; // 就將 jQuery(.fn) 自身作為目標(biāo)對(duì)象(這里,因?yàn)橹粋魅肓?{drag: drag}与斤,因此目標(biāo)對(duì)象為 jQuery(.fn)肪康,也就實(shí)現(xiàn)了向 jQuery 中添加對(duì)象(方法))
    --i; // i=0
  }

  for ( ; i < length; i++ ) {
    // Only deal with non-null/undefined values
    if ( (options = arguments[ i ]) != null ) { // 這里 options = {drag: drag}
      // Extend the base object
      for ( name in options ) {
        src = target[ name ]; // 向目標(biāo)對(duì)象(這里是 jQuery(.fn)) 中添加屬性,并將屬性值賦予變量 src幽告,如果目標(biāo)對(duì)象中沒有該屬性梅鹦,那么此時(shí) src = undefined(這里 src = undefined)
        copy = options[ name ]; // copy = drag (drag方法)

        // Prevent never-ending loop
        if ( target === copy ) { // 防止自引用 (這里防止將 jQuery(.fn) 添加到 jQuery(.fn) 中)
          continue;
        }

        // Recurse if we're merging plain objects or arrays
        if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
          // 如果是深拷貝(deep),而且要拷貝的屬性值為對(duì)象(這里deep = false冗锁,不做深拷貝)
          if ( copyIsArray ) { // 如果要拷貝的屬性值是數(shù)組
            copyIsArray = false;
            clone = src && jQuery.isArray(src) ? src : [];

          } else { // 要拷貝的屬性值為普通對(duì)象
            clone = src && jQuery.isPlainObject(src) ? src : {}; // clone 為目標(biāo)對(duì)象中的屬性值或空對(duì)象
          }

          // Never move original objects, clone them
          target[ name ] = jQuery.extend( deep, clone, copy ); // 遞歸

        // Don't bring in undefined values
        } else if ( copy !== undefined ) { // 這里 drag() 非 undefined
          target[ name ] = copy; // 將屬性添加到目標(biāo)對(duì)象中(這里, jQuery(.fn)[drag] = drag)
        }
      }
    }
  }

  // Return the modified object
  return target; // 返回目標(biāo)對(duì)象(這里返回的是添加了 drag() 方法的 jQuery(.fn))
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嗤栓,一起剝皮案震驚了整個(gè)濱河市冻河,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茉帅,老刑警劉巖叨叙,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異堪澎,居然都是意外死亡擂错,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門樱蛤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钮呀,“玉大人,你說我怎么就攤上這事昨凡∷祝” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵便脊,是天一觀的道長(zhǎng)蚂四。 經(jīng)常有香客問我,道長(zhǎng)哪痰,這世上最難降的妖魔是什么遂赠? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮晌杰,結(jié)果婚禮上跷睦,老公的妹妹穿的比我還像新娘。我一直安慰自己乎莉,他們只是感情好送讲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布奸笤。 她就那樣靜靜地躺著,像睡著了一般哼鬓。 火紅的嫁衣襯著肌膚如雪监右。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天异希,我揣著相機(jī)與錄音健盒,去河邊找鬼。 笑死称簿,一個(gè)胖子當(dāng)著我的面吹牛扣癣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播憨降,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼父虑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了授药?” 一聲冷哼從身側(cè)響起士嚎,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悔叽,沒想到半個(gè)月后莱衩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娇澎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年笨蚁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趟庄。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡括细,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岔激,到底是詐尸還是另有隱情勒极,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布虑鼎,位于F島的核電站辱匿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏炫彩。R本人自食惡果不足惜匾七,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望江兢。 院中可真熱鬧昨忆,春花似錦、人聲如沸杉允。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拢驾,卻和暖如春奖磁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背繁疤。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工咖为, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稠腊。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓躁染,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親架忌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吞彤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 在線閱讀 http://interview.poetries.top[http://interview.poetr...
    程序員poetry閱讀 114,255評(píng)論 24 450
  • 請(qǐng)參看我github中的wiki,不定期更新叹放。https://github.com/ivonzhang/Front...
    zhangivon閱讀 7,105評(píng)論 2 19
  • 前六章的內(nèi)容在上一篇文章中已有介紹备畦,后面的內(nèi)容主要是一些JQuery的插件的介紹,有一些感覺是很有用的许昨,但是有一些...
    范小飯_閱讀 1,921評(píng)論 5 31
  • 1.JQuery 基礎(chǔ) 改變web開發(fā)人員創(chuàng)造搞交互性界面的方式。設(shè)計(jì)者無需花費(fèi)時(shí)間糾纏JS復(fù)雜的高級(jí)特性褥赊。 1....
    LaBaby_閱讀 1,162評(píng)論 0 1
  • 教練三部林珊10月14日感悟: 明天即將開課了糕档,所有工作都在最后關(guān)頭等著與小華部長(zhǎng)溝通最終定奪。事情多了拌喉,整個(gè)人都...
    落子無悔ss閱讀 177評(píng)論 0 0