jQuery源碼解析總結 版本2.0.3

第一天:~。~ jQuery源碼的總體架構、規定一些變量與函數 以及 對jQuery對象添加一些屬性和方法php

 

源碼含註釋共8830行,如下爲總體架構css

//總體架構(左側爲行數)
(function(window,undefined){ //閉包

(傳入window的意義在於:1.加快執行速度(沒必要靠做用域鏈查找window了) 2.便於壓縮(在內部能夠簡寫);)
(傳入undefined的意義在於防止某些瀏覽器 (IE7 IE8等) 能夠人爲修改 undefined的狀況出現;)
(21,94)  定義一些變量和函數jQuery = function(){}
  
(96,283)  給JQ對象添加一些屬性和方法

(285,347)  extend:JQ的繼承方法

(349,817)  jQuery.extend():擴展一些工具方法(靜態方法)  如$.trim()等

(877,2856)  Sizzle:複雜選擇器的實現 (略過)

(2880,3042)  callbacks:回調函數:對函數的統一管理

(3043,3183)  Deferred:延遲對象 : 對異步的統一管理

(3184,3295) :support功能檢測

(3308,3652)  data():數據緩存

(3653,3797)  queue :隊列管理

(3803,4299)  attr() prop() val() addClass()等對元素屬性的操做

(4300,5128)  on() trigger() 事件操做的相關方法

(5140,6057)  DOM操做: 添加 刪除 獲取 包裝 DOM篩選

(6058,6620)  css():樣式的操做

(6621,7854)  提交的數據和ajax()

(7855,8584)  animate():運動的方法

(8585,8792)  offset():位置與尺寸的方法

(8804,8821)  JQ支持模塊化的模式

(8826)  window.jQuery = window.$ = jQuery 對外提供接口
 }
)(window)

 

在瞭解了總體架構後,咱們來看一下:html

第一部分 .(21,94) 定義一些變量和函數html5

變量:node

rootJquery : 就是jQuery(document)  用jquery選擇到的document ,賦給這個變量方便壓縮(簡寫)和維護(理解);jquery

readyList : 與後面的DOM加載相關;es6

core_strundefined = typeof undefined : 字符串的undefined 處理一下IE6789使用xml的一些bug ,比較小衆的問題;web

location = window.locationajax

document = window.document正則表達式

docElem = document.documentElement //即html標籤

以上3個變量也是方便壓縮使用

_jQuery = window.jQuery  ;      _$=window.$ : 防與別的庫中的$或者jQuery衝突時使用;

class2type = {} : 類型判斷時使用。 如$.type() 內部存儲的方式大體爲{'[Object String]' : 'string',' [Object Array]' : 'array'}

core_deletedIds = [];

core_version = '2.0.3';

core_concat = core_deletedIds.concat;

core_push = core_deletedIds.push;

core_slice = core_deletedIds.slice;

core_indexOf = core_deletedIds.indexOf;

core_toString = class2type.toString;

core_hasOwn = class2type.hasOwnProperty;

core_trim =core_version.trim   以上7個變量便於壓縮使用

方法:

    jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context, rootjQuery );//直接初始化
    },

在函數內部返回了一個實例對象,能夠看出jQuery是一個面向對象的程序。那他是如何設計的?其實在jQuery源碼中有一句

jQuery.fn = jQuery.prototype 即jQuery.fn就是jQuery的原型,而上述代碼中jQuery.fn.init()纔是構造函數,那不是亂套了?

其實否則,jQuery的設計是經過jQuery.prototype.init.prototype = jQuery.prototype將jQuery的原型賦給jQuery.fn.init的原型。

以下(模擬):

function jQuery(){
   return new jQuery.prototype.init();
}
jQuery.prototype.init = function(){};
jQuery.prototype.css = function(){};
jQuery.prototype.init.prototype = jQuery.prototype;
//此處將 jQuery.prototype賦給jQuery.prototype.init.prototype造成引用關係
jQuery().css()//能夠正常使用

一些正則:

core_pnum:匹配數字,在後面的css方法中用到

core_rnotwhite:匹配非空

rquickExpr: 兩個子項 匹配<p>hello 或者#div1等

rsingleTag: 匹配單標籤 <li>  <li></li>等

rmsPrefix : 匹配 -ms- (ie)

rdashAlpha : 匹配 -webkit等  ,後面用於更換大小寫;

2個方法:

fcamelCase : 轉大寫;

completed : DOM加載成功後的回調(後面用到);

 

第二部分.(96,283) 給JQ對象添加一些(實例)屬性和方法:jQuery.fn = jQuery.prototype = {} 

jquery : core_version    版本字符串'2.0.3'

constructor : jQuery      將constructor重指向至jQuery  (修正指向)

先來看一個小例子: 當咱們使用jQuery的css方法時 如$('li').css('background','red')時, jQuery內部是如何處理的呢?

$('li').css('background',red) 在jQuery中的簡易處理
//$('li'): 在面向對象中 this能夠共享,因此將jQuery對象存成如下形式;
this={ 0:'li', 1:'li', 2:'li', length:3 } //.css('background',red) : 對於上面特殊的對象 (012下標,有length屬性),能夠進行for循環處理 for(let i=0;i<this.length;i++){ this[i].style.background = 'red' }

 

init方法:初始化參數管理:

當咱們給jQuery傳入參數的時候分爲幾種狀況,好比:

1.$("") , $(null) , $(undefined) , $(false) : 直接return this ;

2.參數爲字符串時 如$('#div1') , $('.box') , $('div') , $('#div div.box')  或者建立標籤$('<li>') $('<li>1</li><li>2</li>')

3.參數爲dom元素時  如$(this)  $(document);

4.參數爲函數時 :$(function(){ })

jQuery對上面的狀況先進行簡易的分類處理。 利用名爲match的數組分配各類狀況。(詳細見源碼)

 

init方法中調用的工具方法(後面會有這些工具方法的實現):

  jQuery.parseHTML : 將字符串變成節點數組,如:'<li>1</li><li>2</li>'   to :   ['li','li'] 

  jQuery.merge    :  內部使用時能夠將數組合併成類數組  ['li','li']     to :   this={0:'li',1:'li',length:2}

  jQuery.isPlainObject :   判斷是不是對象字面量{}

  jQuery.makeArray :[],傳第二個參數(this)時,能夠在內部轉爲類數組this

 

tips: match = rquickExpr.exec(selector)    :    .exec()返回一個數組,此數組的第 0 個元素是與正則表達式相匹配的文本,第 1 個元素是與 正則第 1 個子表達式相匹配的文本,第 2 個元素是與正則的第 2 個子表達式相匹配的文本,沒有則爲null,以此類推。

selector : 存儲選擇的字符串

length : this對象的長度

toArray() : 轉數組 :相似makeArray(工具方法)  利用Array.prototype.slice.call(obj)

get() : 轉原生集合                  return num == null ?this.toArray() :( num < 0 ? this[ this.length + num ] : this[ num ] );

pushStack() : JQ對象的入棧處理。(先進後出),在源碼中使用較多,例如$('div').pushStack('$('span')).css()操做的是span 而$('div').pushStack('$('span')).end().css()操做的是div

 pushStack: function( elems ) {
              var ret = jQuery.merge( this.constructor(), elems );
              ret.prevObject = this;  //將棧更深的一級存起來 可使用end()調用
              ret.context = this.context;
              return ret;
           },

  end:     function() {
              return this.prevObject || this.constructor(null);
           },

 

each() : 遍歷集合,內部調用工具方法$.each()

ready() : DOM加載的接口

slice() : 集合的截取:利用pushStack 能夠用end()回溯。

  return this.pushStack( core_slice.apply( this, arguments ) );

first() : 集合的第一項 ,即eq(0)

last() : 集合的最後一項, 即eq(-1)

eq() : 集合某一項,內部調用pushStack

map() :映射,內部調用$.map()工具方法和pushStack

end() : pushStack的回溯方法;

push 、 sort 、  splice爲jQ內部使用的方法。

---------------------------------------------------------------------------------------------------------------------------------------------------

 次日:~。~ jQuery的繼承方法 以及 對jQuery添加一些工具方法

(285,347) extend:JQ的繼承方法:

先來講說jQuery的extend的使用方式:

當參數只有一個對象字面量時,這時是擴展插件的狀況,好比:

$.extend({
   aaa:function(){
      alert(1);
  }
})       此時爲擴展工具方法,由$.aaa()調用;如果$.fn.extend則爲擴展實例方法,由$().aaa調用

 

而當參數爲多個時,這時是後面的對象擴展到第一個對象的方式,好比:

var a = {};
$.extend(a, { name:'hello'},{age:30})

也能夠作淺拷貝和深拷貝(jQuery默認淺拷貝),好比:

var a = {};
var b ={name:{age:18}};
$.extend(a,b)
a.name.age = 28;//此時默認淺拷貝模式,對象之間引用關係,致使修改a.name影響到b.name
alert(b.name)   //28
var a = {};
var b ={name:{age:18}};
$.extend(true,a,b)  //增長參數true
a.name.age = 28;//此時爲深拷貝模式,修改a.name不影響b.name
alert(b.name)   //18

JQ使用拷貝繼承:在深拷貝時使用了遞歸,下面看下源碼大概作了哪些事:以下:

先定義一些變量
 var options, name, src, copy, copyIsArray, clone,
           target = arguments[0] || {},
           i = 1,
           length = arguments.length,
           deep = false;
if(typeof target === "boolean){
           deep = target;
            target = arguments[1] || {};
            i = 2;
} // 看看是否是深拷貝的狀況,即第一個參數是否是boolean

if( typeof target !== "object" && !jQuery.isFunction(target)){
        target = {};
}  //看參數正確不,若不正確變爲空對象{} if(length === i){
      target = this;
            --i;
}  //看是否是插件的狀況,即只有一個對象字面量的參數

for(){//若是傳入多個對象則開啓for循環
   for(name in options){
        src = target[ name ];  //目標對象
        copy = options[ name ]; //拷貝對象
        if( target === copy ){// 防止循環引用,即$.extend({a,{name:a}})相似的狀況 continue;
        }if(deep &&  copy &&copy是對象或者數組){//深拷貝
//先作一下防止同名屬性覆蓋的處理 ( clone = src && jQuery.isPlainObject(src) ? src : {};) target[ name ] = jQuery.extend( deep, clone, copy );//使用遞歸分解執行 } else if(){//淺拷貝 target[ name ] = copy;直接賦值 } } return target; }

(349,817) jQuery.extend():擴展一些工具方法(靜態方法):具體以下:

expando : 生成惟一的JQ字符串(內部),在後面數據緩存,事件操做,ajax等處使用;

noConflict() : 防止衝突;(先將別的庫中的$=123 賦值給_$  而後再將_$賦值給$  捨棄$,使用新的變量名)

let $ = 123
<script src='jquery203.js'></script>
let new$ = $.noConflict(); new$(function(){ alert($) //123 })

isReady : DOM是否加載完畢(內部); 

readyWait : 等待多少文件的計數器(內部);

holdReady() : 推遲DOM觸發; 好比:

$.holdReady(true);
$.getScript('a.js',function(){
   $.holdReady(false)
}
$(function(){
   alert(2)
});//這麼操做就能夠先加載完外部js文件再彈出2

內部實現爲:

 holdReady: function( hold ) {
            if ( hold ) {
                jQuery.readyWait++;
            } else {
                jQuery.ready( true );
            }
        }

ready() : 準備DOM觸發;

先來集中說說DOM相關的方法:

window.onload()和$(function(){})的區別是什麼?: 在於後者在dom加載完(不用文件加載完)便可執行(依賴DOMContentLoaded:即dom加載完觸發的事件),而window.onload須要等文件圖片等加載完畢纔會執行;

而$(function(){})實際上是$(document).ready(function)的一種簡寫方式,內部仍是調用的後者;

經過源碼能夠看出$().ready()內部調用的是jQuery.ready.promise().done(fn) 延遲對象,而延遲對象內部不管是if仍是else都調用了$.ready()這個工具方法,而$.ready()中有一句readyList.resolveWith(document,[jQuery])表示已完成,能夠調用上述延遲對象的fn函數。下面詳細看看這段比較繞人的異步操做源碼:

    jQuery.ready.promise = function( obj ) { //$().ready()中調用的方法
        if ( !readyList ) {//源碼開頭定義的一個變量,一開始爲undefined

            readyList = jQuery.Deferred(); //延遲對象,後面會說
        
            if ( document.readyState === "complete" ) {
             
                setTimeout( jQuery.ready ); //setTimeout做用是防ie的一個bug

            } else {//若dom未加載完成,則監聽事件
          
                document.addEventListener( "DOMContentLoaded", completed, false );//90行的回調
           
                window.addEventListener( "load", completed, false );//寫2個防止緩存
            }
        }
        return readyList.promise( obj );
    };

下面看看completed回調的源碼:(能夠看出標紅的地方不管dom有沒有加載完畢,最終都是調用工具方法$.ready())

    completed = function() {
            document.removeEventListener( "DOMContentLoaded", completed, false );//取消事件監聽,因此以前的監聽不會觸發2次
            window.removeEventListener( "load", completed, false );//取消事件監聽,因此以前的監聽不會觸發2次
            jQuery.ready();
        };

下面來看看重點的$.ready()工具方法的源碼:

 ready: function( wait ) {//傳參時內部調用holdReady

            if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
                return;
            }

            jQuery.isReady = true;

            if ( wait !== true && --jQuery.readyWait > 0 ) {
                return;
            }//以上是處理holdReady的情景
   
            readyList.resolveWith( document, [ jQuery ] );
//能夠傳參 如 $(function(arg){}) arg就是jQuery document是指向 if ( jQuery.fn.trigger ) {
//
第三種頁面加載的書寫方式(主動觸發)如:$(document).on('ready',function(){}) jQuery( document ).trigger("ready").off("ready"); } }

isFunction() : 是不是函數,經過$.type()實現,後面會說;

isArray() : 是不是數組,內部實現使用原生的Array.isArray();

isWindow() :是不是window;

isNumeric() : 是不是數字,對NaN進行處理,判斷爲false,原生typeof判斷 NaN時爲true;

type() : 判斷數據類型;( 利用  { }.toString.call([]) == '[object Array]'  )

   jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
        class2type[ "[object " + name + "]" ] = name.toLowerCase();
    });

isPlainObject() :  是否爲對象自面量;{ }或者new Object()  注意:window和DOM節點在$.type()判斷時返回object但他們不是對象字面量。

isEmptyObject() : 是否爲空的對象;{ }  ,  [ ]  , new Aaa()

  isEmptyObject: function( obj ) {
            var name;
            for ( name in obj ) {
                return false;
            }
            return true;
        }//利用for-in 判斷

error() : 拋出異常;

parseHTML(str,document,true) : 解析節點;將字符串變成節點數組,如:'<li>1</li><li>2</li>'   to :   ['li','li'] ,第三個參數爲是否解析script標籤。

parseJSON() : 解析JSON;

parseXML() : 解析XML;

noop() : 空函數(容錯處理);

globalEval() : 全局解析JS代碼 ,將局部變量解析成全局變量

camelCase() :轉駝峯(內部);

nodeName(): 是否爲指定節點名(內部);如$.nodeName(document.body,'body')  返回true

each() : 遍歷,jQuery中的each()能夠遍歷數組,對象,類數組等,仍是很強大的。如今來詳細看看源碼如何實現:

 each: function( obj, callback, args ) {
            var value,
                i = 0,
                length = obj.length,
                isArray = isArraylike( obj );

            if ( args ) {//內部使用時傳入第三個參數args
                 //和外部使用的區別是 value = callback.apply( obj[ i ], args );由於args是不定參
                }
            } else {//正常使用時
                if ( isArray ) {
                    for ( ; i < length; i++ ) {
                        value = callback.call( obj[ i ], i, obj[ i ] );//第2,3個參數就是回調裏的第1 2 個參數
                        if ( value === false ) {
                            break;
                        }
                    }
                } else {
                    for ( i in obj ) {
                        value = callback.call( obj[ i ], i, obj[ i ] );
                        if ( value === false ) {
                            break;
                        }
                    }
                }
            }

            return obj;
        },

trim() : 去先後空格,源碼實現使用的原生es5的trim方法;

makeArray() :類數組轉真數組;(傳兩個參數時內部使用,能夠轉成特殊的對象形式,第二個參數須要有length屬性),

內部調用了$.merge();

inArray() :數組版indexOf(),內部調用原生indexOf;

merge() : 對外使用時合併數組,對內使用時合併成特殊的{}類數組;

     merge: function( first, second ) {
            var l = second.length,
                i = first.length,
                j = 0;

            if ( typeof l === "number" ) {//第二個參數有length屬性時
                for ( ; j < l; j++ ) {
                    first[ i++ ] = second[ j ];
                }
            } else {//第二個參數沒有length,好比{0:'a',1:'b'}
                while ( second[j] !== undefined ) {
                    first[ i++ ] = second[ j++ ];
                }
            }

            first.length = i;//從新設置length

            return first;
        },

grep(): 過濾新數組;相似es5的filter          使用方法:arr= $.grep(arr,function(n,i){ return n>2})

 grep: function( elems, callback, inv ) {//inv爲true時取相反的狀況 var retVal,
                ret = [],
                i = 0,
                length = elems.length;
                inv = !!inv;   //轉換類型 
            for ( ; i < length; i++ ) {
                retVal = !!callback( elems[ i ], i );
                if ( inv !== retVal ) {
                    ret.push( elems[ i ] );//若符合條件就push到結果數組裏
                }
            }
            return ret;
        },

map() : 映射新數組;

 map: function( elems, callback, arg ) {
            var value,
                i = 0,
                length = elems.length,
                isArray = isArraylike( elems ),
                ret = [];
 
            if ( isArray ) {
                for ( ; i < length; i++ ) {
                    value = callback( elems[ i ], i, arg );

                    if ( value != null ) {//將回調處理的結果添加至空數組中
                        ret[ ret.length ] = value;
                    }
                }

            } else {
                for ( i in elems ) {
                    value = callback( elems[ i ], i, arg );

                    if ( value != null ) {
                        ret[ ret.length ] = value;
                    }
                }
            }

            // 防止嵌套數組
            return core_concat.apply( [], ret );
        },

guid : 1  惟一標識符(內部),能夠累加,用於後面的事件操做;

proxy() :改this指向,相似es5的bind,能夠傳參,內部調用的是apply;

   proxy.guid = fn.guid = fn.guid || jQuery.guid++;  //最後給事件綁定設置惟一標識符,能夠準確移除事件。

access(): 多功能值操做(內部);供attr)  css()等使用。舉例,當咱們使用css方法時:

$('div1').css('width')  //獲取寬度
$('div1').css('width','100px')  //設置寬度
$('div1').css({width:'100px',height:'100px'})  //同時設置高度和寬度

像這樣經過參數個數或形式的不一樣,進行不一樣的操做。除了css() 還有attr() prop() val() width()等,爲了簡化代碼,將處理不一樣參數的功能統一的提取出來,生成這麼一個工具方法,供後面使用。咱們來看看它的實現:(比較繞)

access: function( elems, fn, key, value, chainable, emptyGet, raw ) {//參數fn用於區分不一樣的功能如attr css
           var i = 0,
               length = elems.length,
               bulk = key == null;       //bulk爲false時表示有key值(width)
            // 設置多組值好比$('div1').css({width:'100px',height:'100px'})
           if ( jQuery.type( key ) === "object" ) {
               chainable = true;      // true爲set  false爲get
               for ( i in key ) {//遞歸分解
                   jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
               }

               // 設置一個值時如$('div1').css('width','100px')
            } else if ( value !== undefined ) {
               chainable = true;
               if ( !jQuery.isFunction( value ) ) {
                   raw = true;     //value不是函數時爲true
               }
               if ( bulk ) {//沒有key值時,用的很少
                  ...            
               }
               if ( fn ) {//有key值時,走回調
                  for ( ; i < length; i++ ) {
                    fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
                  }
                }
            }

            return chainable ?
                elems :
                // 獲取值時
                bulk ? 
                    fn.call( elems ) :           // 沒有key,走回調
                    length ? fn( elems[0], key ) : emptyGet;  // 有key
        },

now() : 當前時間;Date.now()

swap() : css交換,使jQuery能夠得到隱藏元素的值(display:none)

原理:
讓display:none變爲
display:block visibility:none  position:absolute 
取得值後變回display:none

jQuery.ready.promise():延遲對象

 isArraylike():是不是數組/類數組/帶length的對象

   function isArraylike( obj ) {
        var length = obj.length,
            type = jQuery.type( obj );

        if ( jQuery.isWindow( obj ) ) {//不是window,防止window下添加屬性的狀況
            return false;
        }

        if ( obj.nodeType === 1 && length ) {//元素節點類數組
            return true;
        }

        return type === "array" || type !== "function" &&
            ( length === 0 ||     //arguments的狀況
            typeof length === "number" && length > 0 && ( length - 1 ) in obj );
    }

 

-------------------------------------------------------------------------------------------------------------------------------------------

  第三天:~。~ Callbacks回調函數和Deferred異步統一管理 

(2880,3042) Callbacks:回調函數:對函數(能夠是不一樣做用域的)的統一管理,它的基本使用狀況以下:

function aaa(){
     alert(1)
}
function bbb(){
     alert(2)
}
let cb = $.Callbacks();
cb.add(aaa);   //  源碼中經過將aaa,bbb 添加入一個名爲list的數組統一管理
cb.add(bbb);
cb.fire()    //觸發執行        源碼中利用for循環觸發

而它的做用主要用於對不一樣做用域的函數的統一管理:

let cb = $.Callbacks();
function aaa(){
     alert(1)
}
cb.add(aaa);  
(function (){    //aaa,和bbb的做用域不一樣
  function bbb(){
       alert(2)
  }
   cb.add(bbb);
})

cb.fire()  //不一樣做用域的函數也能夠統一管理

Callbacks擁有4個配置選項(options) 如 let cb = $.Callbacks('once'),能夠組合使用 用空格隔開 如('once memory')

once:(fire只會觸發一次,源碼中讓for循環只執行一次) 

memory:(fire以後的add添加的函數也能夠執行,源碼中做用在add上)

unique : (去重,同名函數不能添加至list,源碼中做用在add上)

stopOnFalse :(若是函數中return  false  則不添加至list ,源碼中做用於for循環)

配置相關的源碼以下:

  //先設置一個配置緩存
  var optionsCache = {};//內容如{'once memory': {once:true,memory:true} }

// 設置一個開啓配置的全局方法,後面用
    function createOptions( options ) {
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true; //將配置加入空對象中
        });
        return object;//{once:true,memory:true}
    }

 jQuery.Callbacks = function( options ) {
       //判斷有沒有傳入options
        options = typeof options === "string" ?  //{once:true,memory:true}
            ( optionsCache[ options ] || createOptions( options ) ) ://從緩存拿或者用方法新建
            jQuery.extend( {}, options );//防止options是undefined
...}

方法:

私有函數fire :cb.fire()底層的實現依賴於私有函數fire

 fire = function( data ) {
        memory = options.memory && data;  // 給memory賦值,add方法中就能夠取到memory
        fired = true;
        firingIndex = firingStart || 0;
        firingStart = 0;
        firingLength = list.length;
        firing = true;       //用於處理在回調中使用fire()的狀況,for循環後就變爲false
        for ( ; list && firingIndex < firingLength; firingIndex++ ) {
//data[0]是執行環境 data[1]是fireWith傳過來的參數args
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { memory = false; break; } } firing = false; if ( list ) { if ( stack ) { // stack = !options.once && [], if ( stack.length ) { fire( stack.shift() ); } } else if ( memory ) { list = []; } else { self.disable(); } } },

add 將函數push到list中:

                add: function() {
                    if ( list ) {
                        var start = list.length;
                        (function add( args ) {//主要針對cb.add(fn1,fn2)
                            jQuery.each( args, function( _, arg ) {
                                var type = jQuery.type( arg );
                                if ( type === "function" ) {
                                    if ( !options.unique || !self.has( arg ) ) {
                             list.push( arg );
                                    }
                                } else if ( arg && arg.length && type !== "string" ) {
                                    //  針對cb.add([fn1,fn2])
                                    add( arg );
                                }
                            });
                        })( arguments );
                        if ( firing ) {
                            firingLength = list.length;
                        } else if ( memory ) {
// 當調用fire()後fire的工具方法源碼中會給memory賦值,此時在fire()後面的add方法就會走這個else if,從新調用fire firingStart
= start; fire( memory ); } } return this; },

 

remove 相對於add,從list數組中去除成員list.splice(index,1)

 

  remove: function() {
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );  
                                if ( firing ) {
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    if ( index <= firingIndex ) {
                                        firingIndex--;
                                    }
                                }
                            }
                        });
                    }
                    return this;
                },

 

has :查看list中是否包含傳入的fn

 

      has: function( fn ) {//查看list中是否包含傳入的fn
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },

 

empty :  list = [ ]    firingLength = 0;

disable :禁止全部操做   list = stack = memory = undefined

disabled  :return !list;  

lock : 只鎖住後續的fire, 只將stack = undefined

locked  return !stack; 

fireWith  調用fire私有函數

 

     fireWith: function( context, args ) {
                    if ( list && ( !fired || stack ) ) {
                        args = args || [];
                        args = [ context, args.slice ? args.slice() : args ];
                        if ( firing ) {
                            stack.push( args );
                        } else {
                            fire( args );//cb.fire('hello')hello能夠傳遞給私有fire函數
                        }
                    }
                    return this;
                }

 

fire  調用self.fireWith(this,arguments)

fired return !!fired

 

(3043-3183)Deferred:對異步的統一管理,基於$.Callbacks()設計,能夠看下二者簡單的對比:

let cb = $.Callbacks()
setTimeout(function(){
   alert(1);
   cb.fire() 
},1000)

cb.add(function(){
  alert(2)
})
-------------------------------------------------------
let dfd= $.deferred()
setTimeout(function(){
   alert(1);
   dfd.resolve() 
},1000)

dfd.done(function(){
  alert(2)
})

dfd.resolve()    dfd.reject()   dfd.notify()  《==》 cb.fire()

dfd.done()   dfd.fail()  dfd.progress() 《==》 cd.add()

$.ajax()內置了延遲對象,以下:

//普通寫法
$.ajax({
  url: 'xxx.php',
  success : function(){},
  error :  function(){}
})
//延遲對象寫法  自動觸發resolve/reject
$.ajax('xxx.php').done(function(){}).fail(function(){})

延遲對象的一個簡單需求分析:加載網頁時,導航運動到一半時,網頁內容纔開始運動;而網頁加載完後,再返回導航,

這時網頁內容馬上運動,而不是等待導航運動到一半再運動,這就比較適合使用延遲對象(resolve)原理是使用了$.Callbacks的memory配置

下面看看源碼:

開始先定義一個映射數組:

var tuples = [ //映射數組 其中once表示狀態只變化一次,memory表示記憶,只要resolve就馬上觸發done的函數
                  [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                  [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                  [ "notify", "progress", jQuery.Callbacks("memory") ]
              ],

而後新建一個promise對象,(添加[ done | fail | progress]方法即$.Callbacks()裏的 list.add )  並無resolve reject notify的方法,因此外部沒法經過resolve()  reject()修改狀態;來看一眼promise對象

 promise = {
                    state: function() {
                        return state;
                    },
                    always: function() {//不管resolve仍是reject都會觸發
                       deferred.done(arguments).fail(arguments);
return this; }, then: function( /* fnDone, fnFail, fnProgress */ ) { ... }, promise: function( obj ) {
 //當有參數時(deferred),將promise繼承給deferred,沒有參數時返回promise對象!
return obj != null ? jQuery.extend( obj, promise ) : promise; } },

而後再新建一個deferred對象,添加[ resolve | reject | notify]方法,而後經過後面代碼promise.promise(deferred)即調用上述紅色代碼,將promise繼承給deferred,當咱們平時使用延遲對象時,想要在外部不能隨便修改狀態。會調用dfd.promise(),因爲沒有參數,此時返回的就是promise對象,而promise對象中沒有deferred對象中的[ resolve | reject | notify]方法,因此沒法在外部修改狀態。

延遲對象的輔助方法when(): 內部return deferred.promise()即外部不能更改狀態,相似es6的promise.all,例如:

$.when(aaa(),bbb()).done(function(){ //aaa(),bbb()必須return dfd
    alert('成功')          //aaa,bbb都resolve才執行
}).fail(function(){
    alert('失敗')          //aaa,bbb只要一個reject就執行
})

源碼中經過計數器remaining(即arguments.length)作判斷,來處理各類參數狀況

 

-------------------------------------------------------------------------------------------------------------------------------------------

  第四天:~。~ support功能檢測和data數據緩存

(3184,3295)support :功能檢測

 jQuery解決兼容問題經過support檢測,經過hooks解決

1.input.type='checkbox'時,老版本webkit中input.value= ' '  而其餘爲'on',

2.option 的下拉select的第一項的secleted(是否選中) IE中默認false 其餘默認true;

3.當input.checked = true時, input.cloneNode(true).check在IE9 IE10中不能正確克隆到。

4.當設置input.value = 't'    input.type ='radio' 時判斷input.value有沒有被修改爲't' (在IE9 10 11中沒有變成't' 而是'on')

5.IE下的onfocusin 和onfocusout  具備冒泡操做,其餘瀏覽器不支持

6. background相關樣式操做克隆時,IE9 IE10  在修改克隆的背景樣式時會影響到原來元素的背景樣式

7.下列功能檢測在dom加載完後執行:

a.box-sizing:   首先要了解下box-sizing :content-box  和box-sizing: border-box的區別:好比在設置width爲100px時,

content-box模式時,這100px並不包含border padding margin。 而border-box模式時:100px是全部寬度的和。

例如,假如您須要並排放置兩個帶邊框的框,可經過將 box-sizing 設置爲 "border-box"。這可令瀏覽器呈現出帶有指定寬度和高度的框,並把邊框和內邊距放入框中。

b.當top值設置爲1%時,只有safari的getComputedStyle不會轉爲像素值。

c.在box-sizing:border-box中 ,只有IE在設置width後,getComputedStyle得到的width值須要減去padding

 

(3308,3652) data():數據緩存:

 咱們先來看一下data() prop()  attr()有什麼區別:

$('#div1').attr('name','hehe'):  內部實現爲document.getElementById('div1').setAttribute('name','hehe')    獲取是alert(document.getElementById('div1').getAttribute('name'));

$('#div1).prop('name','hehe') : 內部實現爲document.getElementById('div1') ['name'] ='hehe' ,獲取是alert(document.getElementById('div1') ['name']);

$('#div1').data('name','hehe') :更適合於處理大量數據,並且能夠避免內存泄漏,內存泄漏的緣由之一就是:

當DOM元素與對象互相引用時,大部分瀏覽器會出現內存泄漏,如:

let div1 =document.getElementById('div1');
let obj = {};
div1.name = obj;
obj.age = div1            //垃圾回收機制沒法銷燬變量,致使內存泄漏

而data()是如何作到避免內存泄漏的呢?緣由是data()用了一箇中介對象cache={ },簡易說明以下:

$(#div1).data('name',obj);//至關於<div id ='div1' xxx='1'></div>
$('body').data('age',obj) //至關於<body xxx='2'></body>
var cache ={ 
//中介cache中存的是須要添加的數據,因爲自定義屬性xxx='1'不是屬性只是一個索引,因此不會引發內存泄漏 1:{name:obj}, 2:{age:obj} }

下面來研究下源碼:

 function Data() {
//首先將cache的屬性'0' 設置爲只能get不能set,並且只返回一個空對象。屬性'0'是公用的,
然後面的屬性‘1’’2‘‘3’...等是私有的,並且能夠set
        Object.defineProperty( this.cache = {}, 0, {
            get: function() {
                return {};
            }
        });
        //惟一的標識  就是上述的<div xxx='1'>中的xxx
        this.expando = jQuery.expando + Math.random();
    }        
    Data.uid = 1;//即cache中的1 2 3 4...能夠累加,
    Data.accepts = function( owner ) {
        //節點中只接受元素節點和document節點(即不接受文本節點等),和其餘類型,
        return owner.nodeType ?
            owner.nodeType === 1 || owner.nodeType === 9 : true;
    };

下面來看看構造函數data的原型上的方法:

key: function( owner ) { //用於分配屬性名,讓元素與cache關聯起來
         //若是不是可接受的參數,則返回0做爲cache的屬性'0'
            if ( !Data.accepts( owner ) ) {
                return 0;
            }
            var descriptor = {},
                //至關於如unlock = div[xxx]即 1
                unlock = owner[ this.expando ];

            if ( !unlock ) {//若不存在,就建立一個
                unlock = Data.uid++;
                try {
                    descriptor[ this.expando ] = { value: unlock };
                    Object.defineProperties( owner, descriptor );
                } catch ( e ) {//安卓4如下的兼容寫法
                    descriptor[ this.expando ] = unlock;
                    jQuery.extend( owner, descriptor );
                }
            }
            if ( !this.cache[ unlock ] ) {
                this.cache[ unlock ] = {};
            }
            return unlock;
        }
set: function( owner, data, value ) {
            var prop,
                unlock = this.key( owner ),
                cache = this.cache[ unlock ];
           // 處理: [ owner, key, value ] args
            if ( typeof data === "string" ) {
                cache[ data ] = value;
          //處理: [ owner, { properties } ] args
            } else {
                    for ( prop in data ) {
                        cache[ prop ] = data[ prop ];
                    }
                }
            }
            return cache;
        }
 get: function( owner, key ) {
            var cache = this.cache[ this.key( owner ) ];
            return key === undefined ?
                cache : cache[ key ];
        }
access: function( owner, key, value ) {//set和get的整合,經過參數不一樣選擇不一樣操做
            var stored;
            if ( key === undefined ||
                ((key && typeof key === "string") && value === undefined) ) {
                stored = this.get( owner, key );
                return stored !== undefined ?
                    stored : this.get( owner, jQuery.camelCase(key) );
            }
            this.set( owner, key, value );
            return value !== undefined ? value : key;
        }
 remove: function( owner, key ) {
            var i, name, camel,
                unlock = this.key( owner ),
                cache = this.cache[ unlock ];
            if ( key === undefined ) {
                this.cache[ unlock ] = {};
            } else {
                if ( jQuery.isArray( key ) ) { 
                    name = key.concat( key.map( jQuery.camelCase ) );
                } else {
                    camel = jQuery.camelCase( key );
                    if ( key in cache ) {
                        name = [ key, camel ];
                    } else {
                        name = camel;
                        name = name in cache ?
                            [ name ] : ( name.match( core_rnotwhite ) || [] );
                    }
                }
                i = name.length;
                while ( i-- ) {
                    delete cache[ name[ i ] ];
                }
            }
        }
 hasData: function( owner ) {
            return !jQuery.isEmptyObject(
                this.cache[ owner[ this.expando ] ] || {}
            );
        }
  discard: function( owner ) {//刪除cache中的1(2,3...)的總體
            if ( owner[ this.expando ] ) {
                delete this.cache[ owner[ this.expando ] ];
            }
        }

下面來看看data的2個實例方法data()和removeData(),看以前先了解下jQuery設計的一個小思想:

就是對一組元素進行設置時是設置每個元素,而獲取時只獲取第一個元素。data也是如此:那麼來看下源碼:

 data: function( key, value ) {
            var attrs, name,
                elem = this[ 0 ],//jq對象的第一項,用於get
                i = 0,
                data = null;
         //$('#div1') .data()不傳參時獲取全部屬性
            if ( key === undefined ) {
                if ( this.length ) {
                    data = data_user.get( elem );
              ...//此處有段代碼針對html5的data-自定義屬性
                return data;
            }

            // Sets multiple values 針對$('#div1').data({'a':1,'b':2})
            if ( typeof key === "object" ) {
                return this.each(function() {
                    data_user.set( this, key );
                });
            }

            return jQuery.access( this, function( value ) {
                ...//此處有段代碼針對html5的data-自定義屬性
                
       this.each(function(){...})// set
}); }, null, value, arguments.length > 1, null, true ); } 
  removeData: function( key ) {//調用原型上的remove方法
            return this.each(function() {
                data_user.remove( this, key );
            });
        }

 

(3653,3797)queue隊列管理:先進先出的順序管理,它的基本使用方式爲:

function aaa(){
  alert(1);
}
function bbb(){
  alert(2);
}
$.queue(document,'q1',aaa);
$.queue(document,'q1',bbb);       //將aaa  bbb添加到名字爲q1的數組裏
console.log($.queue(document, 'q1') //[aaa(),bbb()]
$.dequeue(document,'q1')//出隊,彈出aaa   再調用一次纔會彈出bbb
實例方法爲$(document).queue('q1',aaa)

queue和deferred的區別是queue更適合處理多步的異步操做,主要用於animation(內部隊列名爲' fx' )方法,讓原生方法中的setInterval能夠按順序執行;好比:

$('#div1').animate({width:300},2000).queue(function(next){//next就至關於dequeue
    let This = this;
    let timer = setInterval(function(){
 This.style.height = This.offsetHeight + 1 +'px';
      if(This.offsetHeight == 200){
          next()
          clearInterval(timer)
      }
  }
},30)

下面來看看queue的工具方法的源碼

 queue: function( elem, type, data ) {
            var queue;
            if ( elem ) {
                type = ( type || "fx" ) + "queue";
                queue = data_priv.get( elem, type );                if ( data ) {
                    if ( !queue || jQuery.isArray( data ) ) {//若不存在queue就設置一個
                        queue = data_priv.access( elem, type, jQuery.makeArray(data) );
                    } else {
                        queue.push( data );//若是存在就push
                    }
                }
                return queue || [];
            }
        },
   dequeue: function( elem, type ) {
            type = type || "fx";
            var queue = jQuery.queue( elem, type ),
                startLength = queue.length,
                fn = queue.shift(),
                hooks = jQuery._queueHooks( elem, type ),
                next = function() {
                    jQuery.dequeue( elem, type );
                };

            // 處理animate的狀況
            if ( fn === "inprogress" ) {
                fn = queue.shift();
                startLength--;
            }
            if ( fn ) {
                if ( type === "fx" ) {
                    queue.unshift( "inprogress" );
                }
                delete hooks.stop;
                fn.call( elem, next, hooks );//源碼中就是$.dequeue(),做爲參數傳給fn
            }
            if ( !startLength && hooks ) {
                hooks.empty.fire();   //調用_queueHooks中的empty方法清除不須要的對列名
            }
        }

_queueHooks  :當dequeue後,清除data中留下的用不上的隊列名。

 下面看下queue的實例方法源碼:

queue: 入隊

dequeue:出隊 ,內部調用工具方法$.dequeue

delay: 給隊列加延遲執行時間 好比: animate().delay(2000).animate()

delay: function( time, type ) {
            time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;//源碼最後的字符的速度
            type = type || "fx";
            return this.queue( type, function( next, hooks ) {
                var timeout = setTimeout( next, time );//next就是dequeue
                hooks.stop = function() {
                    clearTimeout( timeout );
                };
            });
        }

clearQueue: return this.queue( type || ' fx ', [ ] ) 即把queue變爲空數組。

promise: 當整個隊列都結束後調用回調,通常不用。

 

 

-------------------------------------------------------------------------------------------------------------------------------------------

  第五天:~。~attr prop val addClass等對元素的操做

 在上面咱們說過 attr()和prop()內部的實現 一個是原生的setAttribute()/getAttrbute(),另外一個是[ ] /.   那麼二者在實用方面有什麼區別呢?

1.attr()能夠設置和獲取自定義屬性,而prop()不能夠;如<div hehe='haha'>

2 .當獲取a標籤的href值時,attr()獲取的是原來的值,而prop()獲取的是帶有本機地址的值;

3.對於元素自帶的屬性,好比id  用removeProp()沒法刪除,而用removeAttr()能夠刪除;

下面先來看下attr prop等的實例方法:

 

 attr: function( name, value ) {//走$.attr()工具方法的回調,經過arguments.length判斷獲取仍是設置 return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
        },

        removeAttr: function( name ) {//走$.removeAttr()的工具方法
            return this.each(function() {
                jQuery.removeAttr( this, name );
            });
        },

        prop: function( name, value ) {/走$.prop()的工具方法
            return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
        },

        removeProp: function( name ) {//只有實例方法 return this.each(function() {
                delete this[ jQuery.propFix[ name ] || name ];
            });
        }

 

下面來看下attr reoveAttr的工具方法:

attr: function( elem, name, value ) {
            var hooks, ret,
                nType = elem.nodeType;
            if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
                return;//節點類型爲文本,註釋,屬性時返回
            }
            if ( typeof elem.getAttribute === core_strundefined ) {
                return jQuery.prop( elem, name, value );//若沒有getAttribute則用prop兼容一下
            }
            if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
         ...處理一個attr('check',true)的兼容問題,處理後調用的是attr('check','check')
            }
            if ( value !== undefined ) {
                if ( value === null ) { //針對$('#div1').attr('hehe',null),把hehe這個屬性刪除
                    jQuery.removeAttr( elem, name );
                } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
                    return ret;  //hook的兼容寫法
                } else {
                    elem.setAttribute( name, value + "" );
                    return value;
                }
            } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
                return ret;
            } else {
                ret = jQuery.find.attr( elem, name );//sizzle中的方法,調用的getAttribute
                return ret == null ?
                    undefined :
                    ret;
            }
        }
  removeAttr: function( elem, value ) {
            var name, propName,
                i = 0,
                attrNames = value && value.match( core_rnotwhite );
//針對removeAttr('title id name'),match後返回一個分割後的數組['title','id','name']
if ( attrNames && elem.nodeType === 1 ) { while ( (name = attrNames[i++]) ) { propName = jQuery.propFix[ name ] || name; //propFix用來處理class關鍵字,修正爲className for關鍵字修正爲htmlFor if ( jQuery.expr.match.bool.test( name ) ) { elem[ propName ] = false;//用於處理checked被刪除後,checked屬性也變爲false } elem.removeAttribute( name );//調用原生的removeAtrribute() } } }

prop的工具方法相似attr ,裏面有一段propHooks,針對tabIndex在IE下的兼容,tableIndex的做用是改變光標切換的順序。

好比:

<input type='text' tableIndex = '2'>
<input type='text' tableIndex = '1'>
//此時用tab鍵切換時先切換到第二個再第一個,在IE中不少其餘元素也擁有tableIndex的屬性,會形成一些問題,jQ作了處理。

來看下addClass的實現:

addClass: function( value ) {
            var classes, elem, cur, clazz, j,
                i = 0,
                len = this.length,
                proceed = typeof value === "string" && value;
            if ( jQuery.isFunction( value ) ) {//若是寫回調的話,通常不用
                return this.each(function( j ) {
                    jQuery( this ).addClass( value.call( this, j, this.className ) );
                });
            }
            if ( proceed ) {
                classes = ( value || "" ).match( core_rnotwhite ) || [];//分割一下
                for ( ; i < len; i++ ) {
                    elem = this[ i ];
                    cur = elem.nodeType === 1 && ( elem.className ?
                                ( " " + elem.className + " " ).replace( rclass, " " ) :
                                " "  //rclass是/[\t\r\n\f]/g,將這些轉爲空格。
                        );
                    if ( cur ) {//若是元素中已經有className的時候,進行處理
                        j = 0;
                        while ( (clazz = classes[j++]) ) {
                            if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
                                cur += clazz + " ";
                            }
                        }
                        elem.className = jQuery.trim( cur );
                    }
                }
            }
            return this;
        }

removeClass:相似addClass,不一樣點在於

             if ( cur ) {
                        j = 0;
                        while ( (clazz = classes[j++]) ) {
                            while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
                                cur = cur.replace( " " + clazz + " ", " " );
                            }
                        }
                        elem.className = value ? jQuery.trim( cur ) : "";
                    }

toggleClass:能夠接受第二個參數boolean 如toggleClass('box1 box2',true)表明執行addClass, false表明執行removeClass.

//主要代碼爲  ...
            return this.each(function() {
                if ( type === "string" ) {
                    var className,
                        i = 0,
                        self = jQuery( this ),
                        classNames = value.match( core_rnotwhite ) || [];
                    while ( (className = classNames[ i++ ]) ) {
                        if ( self.hasClass( className ) ) {
                 self.removeClass( className );
                        } else {
                         self.addClass( className );
                        }
                    }
                } else if ( type === core_strundefined || type === "boolean" ) {
                   //通常不用即toggleClass(true)
            });

hasClass:

hasClass: function( selector ) {
            var className = " " + selector + " ",
                i = 0,
                l = this.length;
            for ( ; i < l; i++ ) {
                if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
                    return true;
                }
            }
            return false;
        }

val: 針對value的操做,源碼中首先要經過工具方法valHooks作一下兼容處理,好比<select>標籤裏若是返回type值爲

select-one 若是<select multiple>則type值返回select-multiple 處理方法經過若是找不到select則經過nodeName尋找select。

 

 

 

-------------------------------------------------------------------------------------------------------------------------------------------

  第六天:~。~event事件操做  on() trigger():

事件源碼較多較複雜,先整理一下思路:

從上圖看出:平時咱們使用的.click()  .hover等內部調用的是實例方法on() trigger()  off() ,而這些實例方法內部調用的是更底層的add()綁定事件   remove()解綁事件   trigger()主動觸發事件方法;

因爲實例方法on()的特殊'地位',咱們先來關心下on的使用和實現:

//平時使用的狀況,兩個參數:一個事件名,一個回調
$('#div1').on('click',function(){alert(1)}); on: function( types, selector, data, fn, /*INTERNAL*/ one ) {}//第5個參數內部使用 //on()接收5個參數,其中selector用於事件委託,data用於傳參,例如: $('#div').on('click',{name:'hehe'},function(ev){//{name:'hehe'}就是參數data alert(ev.data.name) //'hehe' } $('ul').delegate('li','click',function(){ $(this).css('background','red'); }//經過事件委託,在ul上綁定事件,經過冒泡機制點擊li時會觸發ul的事件,知道源碼後用on徹底能夠完成delegate的職責,以下:
$('ul').on('click','li',function(){//參數順序有些變化
$(this).css('background','red');
}

 看下on在源碼裏作了什麼:

 on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
            var origFn, type;
            if ( typeof types === "object" ) {//處理第一個參數是{}的形式
                if ( typeof selector !== "string" ) {
                    // ( types-Object, data )
                    data = data || selector;
                    selector = undefined;
                }
                for ( type in types ) {//for in  分解遞歸
                    this.on( type, selector, data, types[ type ], one );
                }
                return this;
            }
            if ( data == null && fn == null ) {//對參數進行順序的處理
                fn = selector;
                data = selector = undefined;
            } else if ( fn == null ) {
                if ( typeof selector === "string" ) {
                    fn = data;
                    data = undefined;
                } else {
                    fn = data;
                    data = selector;
                    selector = undefined;
                }
            }
            if ( fn === false ) {
                fn = returnFalse;
            } else if ( !fn ) {
                return this;
            }
            if ( one === 1 ) {//即$('div').one('click',function(){})只觸發一次,下面有one的源碼
                origFn = fn; //將fn 存一下
                fn = function( event ) {
                    jQuery().off( event ); //取消$(this)的事件
                    return origFn.apply( this, arguments ); //調用存起來的函數
                };
                // 分配一個特定的guid,普通函數和事件函數均可以識別到
                fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
            }
            return this.each( function() {//調用底層add方法
                jQuery.event.add( this, types, fn, data, selector );
            });
        }
 one: function( types, selector, data, fn ) {
            return this.on( types, selector, data, fn, 1 );
        }
 off: function( types, selector, fn ) {
          var handleObj, type;
          if ( types && types.preventDefault && types.handleObj ) {
              handleObj = types.handleObj;
              jQuery( types.delegateTarget ).off(
              handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
                  handleObj.selector,
                  handleObj.handler
              );
                return this;
            }
            if ( typeof types === "object" ) {//同on 處理第一個參數是{}
                for ( type in types ) {
                    this.off( type, selector, types[ type ] );
                }
                return this;
            }//同on 處理參數順序
            if ( selector === false || typeof selector === "function" ) {
                fn = selector;
                selector = undefined;
            }
            if ( fn === false ) {
                fn = returnFalse;
            }
            return this.each(function() {//調用底層的remove方法
                jQuery.event.remove( this, types, fn, selector );
            });
        }

delegate和undelegate內部都是調用on()和off()

bind和 unbind內部也調用on()和off();

下面看下trigger和triggerHandler的區別:後者不會觸發事件的默認行爲。好比:

$('input').focus(function(){$(this).css('background,'red')});
$('input').trigger('focus)//光標會在input中閃爍(默認行爲會觸發),背景也變紅
$('input').triggerHandle('focus) //不會觸發事件默認行爲,只將背景色變紅

二者的實現很簡單,都是調用了底層的trigger方法,經過第四個參數來斷定行爲:

   trigger: function( type, data ) {
            return this.each(function() {//調用底層trigger
                jQuery.event.trigger( type, data, this );
            });
        },
  triggerHandler: function( type, data ) {
           var elem = this[0];
           if ( elem ) {
               return jQuery.event.trigger( type, data, elem, true );
           }
        }
    })

下面重點來看看底層的add()    remove()   trigger()方法:因爲源碼代碼量較大,先看下很是簡化的版本如(紅色部分爲trigger):

var div1 =document.getElementById('div1');
window.onload=function () {
    var aaa= function () {
        alert(111)
    };
   add(div1,'show',aaa);//自定義事件,經過trigger觸發;
  // remove(div1,'show',aaa);
   trigger(div1,'show')
};
function add(obj,types,fn) {//在obj上掛載一個自定義屬性,並添加數組如div1.listeners['show']造成映射關係
    obj.listeners =obj.listeners || {};
   obj.listeners[types] = obj.listeners[types]||[];
    obj.listeners[types].push(fn);//將函數aaa等push到數組中,在後面依次執行。
    obj.addEventListener(types,fn,false)
}
function remove(obj,types,fn) {
    obj.removeEventListener(types,fn,false);
    delete obj.listeners[types];//刪除數組
}
function trigger(obj,types) {
    var arr = obj.listeners[types] ||[];
    for(var i=0;i<arr.length;i++){ arr[i]();//執行函數
    }
}

 在jQuery源碼中,並非直接在obj上掛載自定義屬性,由於可能會引發內存泄漏,而是使用了data數據緩存的方法

讓咱們在控制檯打印一下elemDatadata_priv.get( elem ))看一下里面大概的內容:

$(function(){
  $('#div1').on('click',function(a){alert(1)

  $('#div1').on('click',function(b){alert(2)}
  $('#div1').on('click','span',function(c){alert(3)}
  $('#div1').on('mouseover',function(d){alert(4)}
}
elemData={ events:{ 'click':[ //還有delegateCount和length2個屬性,第一個屬性是委託的個數。
      {},//委託的 排在前方,不管guid的值
      {
        data:undefined,
        guid:3, //惟一標識符
        handler:function(c){},//本身綁定的函數
        namespace:'', //命名空間,好比click.aaa 方便分類操做;
        needContext:false,//委託相關,處理span:first這種僞類狀況 若是沒有委託就委undefined
        origType:'click',//原始的事件類型,如mouseenter
        selector:'span', //事件委託
        type:'click'//須要兼容後的事件類型,好比mouseenter=>mouseover
}, {}, ],
'mouseover':[ {} ] } handle:function(e){} }

如今理一下流程,當on()的時候調用的add() 其實add內部調用的是dispatchdispatch作了三件事:

jQuery.event.fix    //處理event兼容
jQuery.event.special  //處理特殊事件的兼容
jQuery.event.handlers  //處理事件的順序問題

如今來看看jQuery源碼裏jQuery.event裏各個方法的功能:

jQuery.event={
    global:
    add:綁定事件
    remove:解綁事件
    trigger:主動觸發
    dispatch:分發事件的具體操做
    handlers:函數執行順序的操做
    props : JQ中共享原生JS的event屬性
    fixHooks :收集event兼容的結合
    keyHooks:鍵盤的event兼容
    mouseHooks:鼠標的event兼容
    fix:event對象的兼容處理
    special:特殊事件的兼容處理
    simulate:focusin的模擬操做(利用trigger)
}

 下面來看看兼容處理的方法fix: 它又分爲mouseHookskeyHooks

先來看看鼠標兼容問題 :

1.clientY和pageY  : pageY是鼠標點到頁面頂的距離,clientY是鼠標點到可視區的距離,並且pageY有瀏覽器兼容問題,

jQuery的作法是,把clientY+scrollY拼接成pageY解決兼容問題;

2 event.which 和event.button的兼容處理(兼容成 左鍵1中鍵2右鍵3)注意IE中最後用mousedown來得到event.which而不是用click,

若是使用click  IE可能會直接彈出右鍵菜單而非得到事件對象的值;

 再來看看鍵盤兼容問題:

     if ( event.which == null ) {//當keypress事件時,keycode可能取不到值 event.which = original.charCode != null ? original.charCode : original.keyCode;
        }

阻止冒泡:ev.stopPropagation()  和ev.stopImmediatePropagation()的區別是後者自身相同事件也會被阻止;

 

下面看下jQuery.event.special(dispatch作的第二件事)

load       //noBubble 處理img等 onload會冒泡的問題
focus     //focus不該冒泡,而當委託時,變爲focusin並作兼容
blur
click      //若是是checkbox的click則選中  若是是a標籤則不跳轉
beforeunload//火狐20+裏兼容添加returnValue,不然關閉網頁不彈框
mouseenter
mouseleave
focusin //內部用trigger模擬自定義事件,達到兼容目的
focusout

mouseover和mouseout兼容性好,可是有缺陷(好比嵌套元素,會反覆觸發事件);而mouseenter和mouseleave沒缺陷,兼容性差些,jQuery利用mouseover和mouseout模擬mouseenter和mouseleave來解決問題;

下面看下jQuery.event.handle(dispatch第三件事):1委託優先  2委託層級越深的越優先

-------------------------------------------------------------------------------------------------------------------------------------------

  第七天:~。~dom操做:

先來看下幾個經常使用方法.filter()   .not()   .has()  :都是對自身進行操做,而find是對找到的子項進行操做;

$('div').filter('.box').css(...)  //過濾出自身有.box的元素
$('div').not('.box').css(...) //和filter相反
$('div').has('.box').css(...) //過濾出子項有.box的元素

看下filter和not的源碼:能夠看出調用的是之前說過的入棧pushStack操做,

      not: function( selector ) {//經過winnow的第三個參數判斷行爲 return this.pushStack( winnow(this, selector || [], true) );
}, filter: function( selector ) {
return this.pushStack( winnow(this, selector || [], false) ); }

而這個winnow是什麼呢?它是一個過濾的方法,將過濾後的數組推入到pushStack的棧中

    function winnow( elements, qualifier, not ) {
        if ( jQuery.isFunction( qualifier ) ) {
            return jQuery.grep( elements, function( elem, i ) {//回調的狀況
                return !!qualifier.call( elem, i, elem ) !== not;
            });
        }
        if ( qualifier.nodeType ) {//getElementsByClassName()的狀況
            return jQuery.grep( elements, function( elem ) {
                return ( elem === qualifier ) !== not;
            });
        }
        if ( typeof qualifier === "string" ) {//篩選條件如'.box'
            if ( isSimple.test( qualifier ) ) {
                return jQuery.filter( qualifier, elements, not );
            }
            qualifier = jQuery.filter( qualifier, elements );
        }
        return jQuery.grep( elements, function( elem ) {
            return ( core_indexOf.call( qualifier, elem ) >= 0 ) !== not;
        });
    }

find():  基本使用$('ul').find('li')  :即找到ul下的li並對li進行後續操做,內部調用sizzle;

is(): 基本使用爲$('div').is('.box') :即判斷div中有沒有.box的屬性,有返回true,沒有返回false;內部調用winnow;

index():基本使用爲$('#div1').index() :即判斷#div1在兄弟節點中的位置(0.1.2.3...),能夠傳遞參數index('span')縮小查找範圍;

closest():基本使用爲$('#div1').closest('.box'):即對#div1的祖先節點(包含自身)中具備條件.box的最近的節點進行操做,能夠傳第二個參數限制查找範圍;

add():基本使用爲$('#div1').add('span')即將span添加到#div1的集合裏造成一個總體,而後對總體進行操做

addBack():基本使用爲$('#div1').find('span').css(...).addBack().css()即不只能夠回溯也對當前的嚴肅操做(同時操做),類比end()

下面集中看下系列源碼:

jQuery.each({
        parent: function( elem ) {//父節點,傳參限定條件
            var parent = elem.parentNode;
            return parent && parent.nodeType !== 11 ? parent : null;
        },
        parents: function( elem ) {//全部祖先節點,傳參限定條件如'div'
            return jQuery.dir( elem, "parentNode" );
        },
        parentsUntil: function( elem, i, until ) {//parentsUntil('body')截止到body不包含body,第二個參數也能夠限定條件
            return jQuery.dir( elem, "parentNode", until );
        },
        next: function( elem ) {//下一個兄弟節點
            return sibling( elem, "nextSibling" );
        },
        prev: function( elem ) {//上一個兄弟節點
            return sibling( elem, "previousSibling" );
        },
        nextAll: function( elem ) {//下面全部兄弟節點
            return jQuery.dir( elem, "nextSibling" );
        },
        prevAll: function( elem ) {//上面全部兄弟節點
            return jQuery.dir( elem, "previousSibling" );
        },
        nextUntil: function( elem, i, until ) {//截止到xxx
            return jQuery.dir( elem, "nextSibling", until );
        },
        prevUntil: function( elem, i, until ) {//截止到xxx
            return jQuery.dir( elem, "previousSibling", until );
        },
        siblings: function( elem ) {//選擇全部兄弟節點(不包括自身)
            return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
        },
        children: function( elem ) {//選擇全部子節點 return jQuery.sibling( elem.firstChild );
        },
        contents: function( elem ) {//能夠包含空白節點,文本節點等 原生childNodes就是獲取全部節點 return elem.contentDocument || jQuery.merge( [], elem.childNodes );
        }
    }, function( name, fn ) {
        jQuery.fn[ name ] = function( until, selector ) {
            var matched = jQuery.map( this, fn, until );
            if ( name.slice( -5 ) !== "Until" ) {
                selector = until;
            }
            if ( selector && typeof selector === "string" ) {
                matched = jQuery.filter( selector, matched );
            }
            if ( this.length > 1 ) {
                if ( !guaranteedUnique[ name ] ) {
                    jQuery.unique( matched );//去重
                }
                if ( rparentsprev.test( name ) ) {
                    matched.reverse();//修正排序
                }
            }
            return this.pushStack( matched );
        };
    })

jQuery.dir()出場較高:

dir: function( elem, dir, until ) {
            var matched = [],
                truncate = until !== undefined;
           while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { //節點不能爲document
                if ( elem.nodeType === 1 ) {
                    if ( truncate && jQuery( elem ).is( until ) ) { //若是有until,則不push
                        break;
                    }
                    matched.push( elem );
                }
            }
            return matched;//將結果存入數組
        }

下面繼續:

remove():基本使用爲$('div').remove('.box') 參數可傳 爲篩選條件,返回值就是刪除的元素,刪的乾淨包括綁定的事件等;

detach():基本使用爲$('div').detach('.box') 參數可傳 爲篩選條件,保留綁定事件等;內部就是調用的remove()第二個參數傳true

 remove: function( selector, keepData ) {
            var elem,
                elems = selector ? jQuery.filter( selector, this ) : this,
                i = 0;
            for ( ; (elem = elems[i]) != null; i++ ) {
                if ( !keepData && elem.nodeType === 1 ) {//若keepData存在則爲detach
                    jQuery.cleanData( getAll( elem ) );//cleanData清空數據
                }
                if ( elem.parentNode ) {
                    if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
                        setGlobalEval( getAll( elem, "script" ) );
                    }
                    elem.parentNode.removeChild( elem );
                }
            }
            return this;
        }

empty:基本使用爲$('div').empty() 至關於innerHTML='';

html(): 基本使用爲$('div').html()  ,和原生innerHTML的區別是html('<script>alert(1)</script>')會執行js代碼,而原生不執行

text():基本使用爲$('div').text(),前者解析htm標籤;

clone():基本使用爲$('div').clone(true) 第一個參數表示是否克隆綁定的事件等, 第二個參數表示子元素是否進行事件操做;

內部使用原生的cloneNode()實現,原生的cloneNode在IE9  IE10中並不能克隆checked的狀態,jQuery兼容了這個bug

before():基本使用爲$('div').before($('span')) 即把span插到div前面,相似功能insertBefore()

after():基本使用爲$('div').after($('span')) 即把span插到div後面,與before()內部都是調用原生insertBefore();相似功能insertAfter()

append():基本使用爲$('div').append($('span')) 即把span添加到div內部的最後,內部調用原生appendChild(),相似功能appendTo()

prepend():基本使用爲$('div').prepend($('span'))即把span添加到div內部的最開始,內部調用原生insertBefore(),相似功能prependTo()

replaceWith  replaceAll :基本使用爲$("p").replaceWith("<b>Hello world!</b>")即替換

wrap():基本使用爲$('span').wrap('<div>')即把每一個span外面都包上一個div

wrapAll():基本使用爲$('span').wrapAll('<div>')即把span總體的外面包上一個div

wrapInner():基本使用爲$('span').wrapAll('<div>')即把每一個span內部子節點的外面包上一個div

unwrap():基本使用爲$('span').unwrap(),即除了body外刪除其父級(包裝)

 

-------------------------------------------------------------------------------------------------------------------------------------------

  第八天:~。~css相關操做與Ajax:

先來看下框架:

一些變量
function vendorPropName(){} //添加瀏覽器前綴,如o ms webkit moz
function isHidden(){}
function getStyles(){}
function showHide(){}
jQuery.fn.extend({
 css //獲取時調用$.css() 設置時調用$.style() show //display block hide //display none toggle
})
jQuery.extend({
 cssHooks cssNumber cssProps //將float兼容成cssFloat style //內部調用原生style css //內部調用curCss,原生getComputedStyle()
})
curCSS=function(){}//處理了一個IE9的filter獲取不到的兼容問題等
function setPositiveNumber(){}
function argumentWidthOrHeight(){}
function getWidthOrHeight(){}
function css_defaultDisplay(){}//動態獲取隱藏元素的display值(經過nodeName而後createElement獲得後再remove)
function actualDisplay(){}
一些cssHooks

先來看看原生的獲取樣式方法:行間樣式的獲取能夠用.style (能夠獲取複合樣式如background)   而獲取其餘樣式用window.getComputedStyle('元素','僞類')(不能獲取複合樣式)

 width():100 時,當padding爲10px, border爲1px,  margin爲5px時,

innerWidth()  : 120    即width+padding

outerWidth() : 122  即width+padding+border         outerWidth(true) :   132 即width+padding+border+margin

當width(200)時:width=200

innerWidth(200):width = 200 - padding

outerWidth(200):width = 200 - padding-border

outerWidth(200,true):width = 200 - padding-border-margin

-----------------------------------------------------------------------------------------------------------------------------------

ajax():

$.param({'aaa':1,'bbb':2})   ==> 結果是aaa=1&bbb=2    ,若是有漢字或特殊符號則須要編碼,

須要注意的是$.param( [ {'name':'1','value':'2' } ] ) ==>結果是1=2 ,針對的是form表單;

tips:當空格在表單中傳到後臺時會轉爲‘+’ 而不是encodeURIComponent轉的%20

下面先看下ajax的源碼框架:

 function addToPrefiltersOrTransports(){}  
 function inspectPrefiltersOrTransports(){} //兩個函數利用柯里化組成dataType:fn的形式並調用fn
 function ajaxExtend(){}
 jQuery.fn.load = function(){}
 jQuery.extend({
    ajaxSettings    //默認參數
    ajaxSetup       //配置操縱覆蓋默認參數
    ajaxPrefilter   //網址url的預先處理
    ajaxTransport   //分發處理器(好比是否須要動態建立srcipt)
    ajax
    getJSON
    getScript
})
 jQuery.each( [ "get", "post" ], function(){}
 function ajaxHandleResponses(){}
function ajaxConvert(){}   //類型轉換器,識別dataType
其餘一些方法

5個接口$().load()    $.get()  $.post()  $.getJSON()   $.getScript()  內部調用的都是底層的$.ajax()方法

$().load():基本使用爲$(‘div’).load(' 1data.html' , {'name':'hello'} , function(a,b,c){})其中回調的參數裏,第一個是返回的內容,第二個是狀態,第三個是jQuery版的xhr(XMLHttpRequest)   。紅色部分爲傳參;

$.get() :基本使用爲$.get('1data.html' ,{} ,function(){},'json')

$.post():基本使用爲$.post('1data.html' ,{} ,function(){},'json')

$.getJSON(): 基本使用爲$.getJSON(‘1data.php’,function(){}),只會獲取JSON類型數據,支持JSONP,如$.getJSON('2data.php?callback=?',function(a){console.log(a})  a便是返回的{}數據

$.getScirpt(): 基本使用爲$.getScirpt(‘1data.php’,function(){}),只會獲取scirpt類型數據,通常用於按需加載JS;

全局事件:ajax擁有 ajaxStart  ajaxStop  ajaxComplete  ajaxError  ajaxSuccess  ajaxSend這6個全局事件,每一個ajax調用時都會觸發這些事件。(內部調用on,實際上是一種自定義事件,用trigger主動觸發)

 

addToPrefiltersOrTransports()  inspectPrefiltersOrTransports(){}這2個函數利用了柯里化,那咱們來看看什麼是柯里化:

//一個簡單的柯里化 分解參數
function show(n1){
return function(n2){ return n1+n2 } } var num1=show(3) var num2=show(4) console.log(num1(5)) //8
 

------------------------------------------------------------------------------------------------------------------

animate: 先來看下總體架構

tweeners={} //css樣式對應處理
function createFxNow(){}
function createTween(){}
function Animation(){} //核心方法
function propFilter(){} //屬性過濾操做
jQuery.Animation = jQuery.extend(Animation,{
    tweener
    prefilter
})
function defaultPrefilter(){}
function Tween(){}
Tween.prototype={
  init
  cur
  run
}
Tween.propHooks={}
jQuery.each(['toggle','show','hide'],function(){})
jQuery.fn.extend({
  fadeTo
  animate
  stop
  finish
})
function genFx(){}
jQuery.each({
  slideDown
  slideUp
  slideToggle
  fadeIn
  fadeOut
  fadeToggle
},function(){})
jQuery.speed =function(){}
jQuery.easing={
  linear
  swing
}
jQuery.timers=[]
jQuery.fx.tick=function(){}
jQuery.fx.interval=13;
jQuery.fx.start =function(){}
jQuery.fx.stop =function(){}
jQuery.fx.speeds = {}

來看看一些api的簡單實用

$(div).hide(1000)   //改變width height opacity      show()和toggle()同

$(div).sildeUp(1000)  //向上捲曲   

$(div).sildeDown(1000)  //向下展開  

slideToggle()

$(div).fadeOut(1000)  //淡出  改變opacity      fadeIn()和fadeToggle()同

$(div).animate({width:400},1000,'swing',function(){ }) //比較底層的api

$('div').fadeTo(1000,0.5)

 

offset()    獲取元素距離屏幕的距離 .top  .left  ,無視祖先節點的定位  若是傳參就設置

position()   相對於父級的距離 且不計算margin值

相關文章
相關標籤/搜索