第一天:~。~ 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 &©是對象或者數組){//深拷貝
//先作一下防止同名屬性覆蓋的處理 ( 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數據緩存的方法
讓咱們在控制檯打印一下elemData(data_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內部調用的是dispatch,dispatch作了三件事:
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: 它又分爲mouseHooks和keyHooks
先來看看鼠標兼容問題 :
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值