jquery源碼學習 (一) 概況

jQuery

jQuery是繼 prototype以後又一個優秀的Javascript框架。它是輕量級的js庫 ,它 兼容CSS3,還 兼容各類瀏覽器(IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+)jQuery2.0及後續版本將再也不支持IE6/7/8瀏覽器。jQuery使用戶能更方便地處理HTML(標準通用標記語言下的一個應用)、events、實現動畫效果,而且方便地爲網站提供 AJAX交互。jQuery還有一個比較大的優點是,它的文檔說明很全,並且各類應用也說得很詳細,同時還有許多成熟的插件可供選擇。jQuery可以使用戶的html頁面保持代碼和html內容分離,也就是說,不用再在html裏面插入一堆js來調用命令了,只需定義id便可。

jquery大體能夠分爲 DOM 、 ajax 、選擇器 、 事件 、 動畫。固然jquery有13個模塊之多,這裏的5個,是從另一個角度劃分的。javascript

(令: jquery從2.0以後就不兼容IE 6/7/8 了)html

1、 整體架構
;(function(global, factory) {
    // 傳入window能夠將其做爲一個局部變量使用,縮短了做用域鏈,大大加快執行速度
    factory(global);
}(typeof window !== "undefined" ? window : this, function(window, noGlobal) {
    // jquery方法
    var jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context );
    };
    // jQuery.fn 是 jquery對象的原型對象
    jQuery.fn = jQuery.prototype = {};
    // 核心方法
    // 回調系統
    // 異步隊列
    // 數據緩存
    // 隊列操做
    // 選擇器引
    // 屬性操做
    // 節點遍歷
    // 文檔處理
    // 樣式操做
    // 屬性操做
    // 事件體系
    // AJAX交互
    // 動畫引擎
    if ( typeof noGlobal === strundefined ) {
        window.jQuery = window.$ = jQuery;
    }

    return jQuery;
}));

關於上述代碼,解釋以下:java

  1. jQuery的總體代碼包裹在一個當即執行的自調用匿名的函數中,這樣能夠儘可能減小和其餘第三方庫的干擾;自動初始化這個函數,讓其只構建一次。
  2. 在上述代碼最後,將jQuery對象添加到全局window上,併爲之設置變量$,從而使得能夠在外界訪問jQuery;
  3. 爲自調用匿名函數設置參數global,並傳入參數window,這樣一方面能夠縮短做用域鏈,能夠儘快訪問到window;
  • 自調用函數的原理(當即執行的自調用函數)
jQuery使用()將匿名函數括起來,而後後面再加一對小括號(包含參數列表)的目的,簡單來講就是小括號括起來後,就看成是一個表達式來處理,獲得的就是一個 function 對象了。同時小括號也取得了這個函數的引用位置,而後傳入參數就能夠直接執行了。
總結: 全局變量是魔鬼, 匿名函數能夠有效的保證在頁面上寫入JavaScript,而不會形成全局變量的污染,經過小括號,讓其加載的時候當即初始化,這樣就造成了一個單例模式的效果從而只會執行一次。

(function( global, factory ) {

        // 由於jquery既要再普通環境下運行,也要再例如AMD、commonJs下運行,因此咱們須要作不一樣的適應
        // node CommonJs規範
        if ( typeof module === "object" && typeof module.exports === "object" ) {
            module.exports = global.document ?
                factory( global, true ) :
                function( w ) {
                    if ( !w.document ) {
                            throw new Error( "jQuery requires a window with a document" );
                    }
                    return factory( w );
                };
        } else {
            // AMD
            factory( global );
        }


      // 傳入參數(window和一個執行函數)  
    }(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
         
         // 【1】
         // 建立jQuery對象, 其實是jQuery.fn.init所返回的對象
         var jQuery = function( selector, context ) {
              return new jQuery.fn.init( selector, context );
              // 若是調用new jQuery, 生成的jQuery會被丟棄,最後返回jQuery.fn.init對象
              // 所以能夠直接調用jQuery(selector, context), 不須要使用new
              // 若是使用new jQuery()容易出現死循環
              // 咱們日常使用 $() ,就是直接調用jquery函數了
         }

         // 【2】
         // 建立jQuery對象原型,爲jQuery添加各類方法
         jQuery.fn = jQuery.prototype = {
             ...
         }    
         
         // 【3】
         // 在調用new jQuery.fn.init後, jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype
         // 至關於將全部jQuery.fn的方法都掛載到一開始jQuery函數返回的對象上
         // 這裏就是jquery的一個獨特之處了,很是的巧妙
         jQuery.fn.init.prototype = jQuery.fn;

         // 【4】
         // 建立jQuery.extend方法
         jQuery.extend = jQuery.fn.extend = function() {、
              ...
         }
     

        // 【5】
        // 使用jQuery.extend擴展靜態方法
        jQuery.extend({});

        // 【6】
        // 爲window全局變量添加$對象,在給window全局添加變量的時候頗有可可能會致使變量命名衝突哦,咱們以後會學習到如何處理這種命名衝突的解決方法
        if ( typeof noGlobal === strundefined ) {     // var strundefined = typeof undefined
            window.jQuery = window.$ = jQuery;
            
            // $('')
            // 同 jQuery('')
        }


        return jQuery;
    }));
2、 jQuery的類數組對象

能夠這麼理解,jquery主要的任務就是獲取DOM操做DOMnode

jQuery的入口都是經過$()來的,經過傳入不一樣的參數,實現了9種重載方法。jquery

1. jQuery([selector,[context]])
2. jQuery(element)
3. jQuery(elementArray)
4. jQuery(object)
5. jQuery(jQuery object)
6. jQuery(html,[ownerDocument])
7. jQuery(html,[attributes])
8. jQuery()
9. jQuery(callback)

// 9種用法總體來講能夠分三大塊:選擇器、dom的處理、dom加載。
  • 在jquery內部使用了一種類數組對象的形式,這也是爲何咱們獲取到同一個class的許多DOM後,既可以調用jquery的方法,又能經過數組的方式來處理每個dom對象了(好比遍歷)。

例子嘍ajax

經過$(".Class")構建的對象結構以下所示:\數組

<div> 
    <span class="span">1</span>
    <span class="span">2</span>
    <span class="span">3</span>
</div>



console.log($('.span'));

// jQuery.fn.init(3) [span.span, span.span, span.span, prevObject: jQuery.fn.init(1), context: document, selector: ".span"]
// 0:span.span
// 1:span.span
// 2:span.span
// context:    document
// length: 3
// prevObject: jQuery.fn.init [document, context: document]
// selector:".span"
// __proto__:Object(0)
// 模擬一下
function Ajquery(selecter) {
    // 若是傳入的不是一個對象,則將其轉換爲對象
    if(!(selecter instanceof Ajquery)) {
        return new Ajquery(selecter);
    }
    var elem = document.getElementById(/[^#].*/.exec(selector)[0]); // 獲取id
    this[0] = elem;
    this.length = 1;
    this.context = document;
    this.selector = selector;
    this.get = function(num) {
        return this[num];
    }
    return this;
}


// 使用

$('#show2').append(Ajquery('#book').get(0));

// 所以 $('')獲取到的就是一個類數組對象

jQuery的無new構造原理promise

咱們在構造jQuery對象的時候,並無使用new來建立,但實際上是在jQuery方法的內部,咱們使用了new,這樣就保證了當前對象內部就又了一個this對象,而且吧全部的屬性和方法的鍵值對都映射到this上了,因此既能夠經過鏈式取值,也能夠經過索引取值。jquery除了實現了類數組結構, 方法的原型共享,還實現了靜態和實例的共享.瀏覽器

javascript就是函數式語言,函數能夠實現類,因此javascript不是一個嚴格的面向對象的語言。緩存

  • 平時的狀況
function ajquery(name){
    this.name = name;
}
ajquery.prototype = function(){
   say: function(){
        return this.name;
   } 
}

var a = new ajquery();

a.say();
  • 可是在jquery中卻不是這麼來的。jQuery沒有使用new運行符將jQuery顯示的實例化,仍是直接調用其函數
$().ready() 
$().noConflict()
  • 若是要實現不用new直接得到實例
var aQuery = function(selector, context) {
       return new aQuery(); // 直接new一下
}
aQuery.prototype = {
    name:function(){},
    age:function(){}
}
// 若是是上訴的樣子,直接new aQuery()則會致使死循環。
  • 如何獲得一個正確的實例呢,那麼能夠把jQuery類看成一個工廠方法來建立實例,把這個方法放到jQuery.prototye原型中,而後實例化這個方法,從而建立一個實例
// 下面就是jquery的寫法了
jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context, rootjQuery );
},

// 可是問題又來了,init中的this指向的是實例init的原型,就導師了jquery類的this分離了,
// 解決這個問題的方法是:

jQuery.fn.init.prototype = jQuery.fn;

以上就是jQuery無new構造的原理了

// 精簡分析
var ajquery = function(name) {
  return new ajquery.prototype.init(name);
}

ajquery.prototype = {
  init: function(name) {
    this.name = name;
    return this;
  },
  get: function() {
    return this.name;
  },
  name: 'zjj'
}

ajquery.prototype.init.prototype = ajquery.prototype;//這裏使得init內部的this跟ajquery類的this保持了一致。

console.log(ajquery('zmf').get()); // zmf

3、 ready和load事件

針對於文檔的加載

// 一
$(function() {

})
// 二
$(document).ready(function() {

})

// 三
$(document).load(function() {

})

在上面咱們看到了一個是ready一個是load,那麼這兩個有什麼區別呢?

// 咱們先來看一個寫DOM文檔的加載過程吧
1. html 解析
2. 加載外部引用腳本和外部樣式
3. 解析執行腳本
4. 構造DOM原型  // ready
5. 加載圖片等文件 
6. 頁面加載完畢 // load
document.addEventListener("DOMContentLoaded", function () {
    console.log('DOMContentLoaded回調')
}, false);

// 當初始的 HTML 文檔被徹底加載和解析完成以後,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架的完成加載。

window.addEventListener("load", function () {
    console.log('load事件回調')
}, false);

console.log('腳本解析一')

//測試加載
$(function () {
    console.log('腳本解析二')
})

console.log('腳本解析三')

// 觀察腳本加載的順序
// test.html:34 腳本解析一
// test.html:41 腳本解析三
// test.html:38 腳本解析二
// test.html:26 DOMContentLoaded回調
// test.html:30 load事件回調

看完上面的過程咱們不難看出ready是在文檔加載完畢也就是DOM建立完畢後執行的,而load則是在頁面加載完畢以後才執行的。
兩者惟一的區別就是中間加了一個圖片的加載,,可是圖片等外部文件的加載確實很慢的呢。

在平時種咱們爲了增長用戶的體驗效果,首先應該執行的是咱們的處理框架的加載。而不是圖片等外部文件的加載。咱們應該越早處理DOM越好,咱們不須要等到圖片資源都加載後纔去處理框架的加載,這樣就能增長用戶的體驗了。

// 源碼分析
jQuery.ready.promise = function( obj ) {
    if ( !readyList ) {
        readyList = jQuery.Deferred();
        if ( document.readyState === "complete" ) {
            // Handle it asynchronously to allow scripts the opportunity to delay ready
            setTimeout( jQuery.ready );
        } else {
            document.addEventListener( "DOMContentLoaded", completed, false );
            window.addEventListener( "load", completed, false );
        }
    }
    return readyList.promise( obj );
};

DOM文檔是否加載完畢處理方法

  • DOMContentLoaded

當HTML文檔內容加載完畢後觸發,並不會等待圖像、外部引用文件、樣式表等的徹底加載。

<script>
  document.addEventListener("DOMContentLoaded", function(event) {
      console.log("DOM fully loaded and parsed");
  });

  for(var i=0; i<1000000000; i++){
      // 這個同步腳本將延遲DOM的解析。
      // 因此DOMContentLoaded事件稍後將啓動。
  } 
</script>

該事件的瀏覽器支持狀況是在IE9及以上支持。

兼容不支持該事件的瀏覽器
  • readystatechange

在IE8中可以使用readystatechange來檢測DOM文檔是否加載完畢。

對於跟早的IE,能夠經過每隔一段時間執行一次document.documentElement.doScroll("left")來檢測這一狀態,由於這條代碼在DOM加載完畢以前執行時會拋出錯誤(throw an error)。

document.onreadystatechange = subSomething;//當頁面加載狀態改變的時候執行這個方法.

function subSomething() 
{ 
 if(document.readyState == "complete"){ //當頁面加載狀態爲徹底結束時進入 
              //你要作的操做。
    }
}

// 用這個能夠作一下等待網站圖片或者其餘東西加載完之後的操做,好比加載時咱們能夠調用加載動畫,當complete也就是加載完成時咱們讓加載動畫隱藏,這樣只是一個小例子。仍是很完美的。
針對IE的加載檢測

Diego Perini 在 2007 年的時候,報告了一種檢測 IE 是否加載完成的方式,使用 doScroll 方法調用,詳情可見http://javascript.nwbox.com/I...
原理就是對於 IE 在非 iframe 內時,只有不斷地經過可否執行 doScroll 判斷 DOM 是否加載完畢。在上述中間隔 50 毫秒嘗試去執行 doScroll,注意,因爲頁面沒有加載完成的時候,調用 doScroll 會致使異常,因此使用了 try -catch 來捕獲異常。
結論:因此總的來講當頁面 DOM 未加載完成時,調用 doScroll 方法時,會產生異常。那麼咱們反過來用,若是不異常,那麼就是頁面DOM加載完畢了。

// Ensure firing before onload, maybe late but safe also for iframes
document.attachEvent( "onreadystatechange", completed );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", completed );
// If IE and not a frame
// continually check to see if the document is ready
var top = false;
try {
    // 非iframe中
    top = window.frameElement == null && document.documentElement;
} catch(e) {}
if ( top && top.doScroll ) {
    (function doScrollCheck() {
        if ( !jQuery.isReady ) {
            try {
                // Use the trick by Diego Perini
                // http://javascript.nwbox.com/IEContentLoaded/
                top.doScroll("left");
            } catch(e) {
                // 每一個50ms執行一次
                return setTimeout( doScrollCheck, 50 );
            }
            // 分離全部dom就緒事件
            detach();

            // and execute any waiting functions
            jQuery.ready();
        }
    })();
}
3、 解決$的衝突
  • $太火熱,jQuery採用$做爲命名空間,難免會與別的庫框架或者插件相沖突。

解決方案–– noConflict函數

引入jQuery運行這個noConflict函數將變量$的控制權讓給第一個實現它的那個庫,確保jQuery不會與其餘庫的$對象發生衝突。
在運行這個函數後,就只能使用jQuery變量訪問jQuery對象。例如,在要用到$("aaron")的地方,就必須換成jQuery("aaron"),由於$的控制權已經讓出去了。

// jquery導入
jQuery.noConflict();
// 使用 jQuery
jQuery("aaron").show();
// 使用其餘庫的 $()

// 別的庫導入
$("aaron").style.display = ‘block’;

這個函數必須在你導入jQuery文件以後,而且在導入另外一個致使衝突的庫以前使用。固然也應當在其餘衝突的庫被使用以前,除非jQuery是最後一個導入的。

(function(window, undefined) {
    var
        // Map over jQuery in case of overwrite
        // 設置別名,經過兩個私有變量映射了 window 環境下的 jQuery 和 $ 兩個對象,以防止變量被強行覆蓋
        _jQuery = window.jQuery,
        _$ = window.$;
 
    jQuery.extend({
        // noConflict() 方法讓出變量 $ 的 jQuery 控制權,這樣其餘腳本就可使用它了
        // 經過全名替代簡寫的方式來使用 jQuery
        // deep -- 布爾值,指示是否容許完全將 jQuery 變量還原(移交 $ 引用的同時是否移交 jQuery 對象自己)
        noConflict: function(deep) {
            // 判斷全局 $ 變量是否等於 jQuery 變量
            // 若是等於,則從新還原全局變量 $ 爲 jQuery 運行以前的變量(存儲在內部變量 _$ 中)
            if (window.$ === jQuery) {
                // 此時 jQuery 別名 $ 失效
                window.$ = _$;
            }
            // 當開啓深度衝突處理而且全局變量 jQuery 等於內部 jQuery,則把全局 jQuery 還原成以前的情況
            if (deep && window.jQuery === jQuery) {
                // 若是 deep 爲 true,此時 jQuery 失效
                window.jQuery = _jQuery;
            }
 
            // 這裏返回的是 jQuery 庫內部的 jQuery 構造函數(new jQuery.fn.init())
            // 像使用 $ 同樣盡情使用它吧
            return jQuery;
        }
    })
}(window)

使用實例:

<script src="prototype.js"></script>//1.包含jQuery以外的庫(好比Prototype)
<script src="jquery.js"></script>//2.包含jQuery庫取得對$的使用權
<script>
    jQuery.noConflict();//3.調用noConflict()方法,讓出$,把控制權讓給最早包含的庫
</script>
<script src="myscript.js"></script>

讓出$控制權後,須要使用jQuery方法時,則不能用$來調用了,要用jQuery。或者經過定義新的名稱來代替$符號。

var jq=jQuery.noConflict();

另外還有一個技巧,能夠再.ready()方法中使用$。它的回調函數能夠接收一個參數,這個參數爲jQuery對象自己,能夠從新命名jQuery爲$,這樣也是不會形成衝突的。

jQuery.(document).ready(function($){
   //這裏能夠正常使用$ 
})
jQuery源碼學習系列

一篇不錯的解析博客

相關文章
相關標籤/搜索