今天我們帶來jquery源碼分析第二期收班,可能是最后一期喲,具體看以后的發(fā)現(xiàn)吧,話不多說直接上代碼。
1.節(jié)點查詢
先看一個函數(shù)
jquery.extend({
map: function( elems, callback, arg ) {
var length, value,
i = 0,
ret = [];
//判斷是否是類數(shù)組對象,dom節(jié)點是一種類數(shù)組對象
if ( isArrayLike( elems ) ) {
length = elems.length;
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
} else {
for ( i in elems ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
}
//把數(shù)組扁平化
return concat.apply( [], ret );
}
})
數(shù)組扁平化:
let aa = [[1,2,3,4],[2,3,4,5]]
let bb = [].concat(aa)
bb: [1,2,3,4,2,3,4,5]
前面是預(yù)熱,接下來看這個好吧
jQuery.each( {
parent: function( elem ) {
var parent = elem.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
},
parents: function( elem ) {
return dir( elem, "parentNode" );
},
parentsUntil: function( elem, i, until ) {
return dir( elem, "parentNode", until );
},
next: function( elem ) {
return sibling( elem, "nextSibling" );
},
prev: function( elem ) {
return sibling( elem, "previousSibling" );
},
nextAll: function( elem ) {
return dir( elem, "nextSibling" );
},
nextUntil: function( elem, i, until ) {
return dir( elem, "nextSibling", until );
},
siblings: function( elem ) {
return siblings( ( elem.parentNode || {} ).firstChild, elem );
},
children: function( elem ) {
return siblings( elem.firstChild );
}
}
// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
// Treat the template element as a regular one in browsers that
// don't support it.
if ( nodeName( elem, "template" ) ) {
elem = elem.content || elem;
}
return 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 ) {
// Remove duplicates
if ( !guaranteedUnique[ name ] ) {
jQuery.uniqueSort( matched );
}
// Reverse order for parents* and prev-derivatives
if ( rparentsprev.test( name ) ) {
matched.reverse();
}
}
return this.pushStack( matched );
};
} );
以上代碼是什么意思呢砰蠢,我來稀釋一下赵誓。。。
jQuery.fn.parents = function(utill,selector){}
jQuery.fn.parentsUtil = function(utill,selector){}
jQuery.fn.nextUtil = function(utill,selector){}
等等
var matched = jQuery.map( this, fn, until );
這個時候用了map函數(shù)椿肩,假設(shè)此時我們調(diào)用$('#qwe').nextUtil('.aa','.bb'),那么情況發(fā)展如下:
1. jQuery.map($('#qwe'),nextUtil,'.aa')
2. $('#qwe')是類數(shù)組元素茬末,只有一個。會執(zhí)行nextUtil($('#qwe'),0,'.aa')
3.nextUtil返回的是 dir($('#qwe'),nextSbling,'.aa')
4.我們來看看dir函數(shù)
var dir = function( elem, dir, until ) {
var matched = [],
truncate = until !== undefined;
// 這里是是查找#qwe的同輩 .aa節(jié)點
while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
if ( elem.nodeType === 1 ) {
// 如果碰到了util也就是元素自身,就break
if ( truncate && jQuery( elem ).is( until ) ) {
break;
}
matched.push( elem );
}
}
return matched;
};
注意上面是找出了所有#qwe的同輩.aa元素,但我們想在他碰到.bb就停止萄唇,所以有這樣一段代碼:
if ( selector && typeof selector === "string" ) {
matched = jQuery.filter( selector, matched );
}
我們上面是特殊例子,若是$('.qwe')在頁面中有多個,則會走完整個for循環(huán)。
我么來看看 jQuery.filter( selector, matched )是如何寫的
jQuery.filter = function( expr, elems, not ) {
var elem = elems[ 0 ];
// 過濾不符合expr的
if ( not ) {
expr = ":not(" + expr + ")";
}
if ( elems.length === 1 && elem.nodeType === 1 ) {
return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
}
return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
return elem.nodeType === 1;
} ) );
};
grep: function( elems, callback, invert ) {
var callbackInverse,
matches = [],
i = 0,
length = elems.length,
callbackExpect = !invert;
for ( ; i < length; i++ ) {
callbackInverse = !callback( elems[ i ], i );
if ( callbackInverse !== callbackExpect ) {
matches.push( elems[ i ] );
}
}
return matches;
}
grep是一個過濾函數(shù),invert如果為false,則與callback返回的之相反的元素進(jìn)去數(shù)組。
jQuery.find.matches大家可以到源碼去查看一下,這里不具體講了移国。
我們來看看jquery里面很重要的一個函數(shù)pushStack
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
// Return the newly-formed element set
return ret;
}
我們發(fā)現(xiàn)在上面任意一個節(jié)點查詢的函數(shù)最后都會有一個,this.pushStack(matched),把查找出來的元素放進(jìn)去。
---html代碼---
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="aa"></div>
<div class="bb"></div>
<div class="bb ww"></div>
<div class="bb"></div>
<div class="cc"></div>
<script src="jquery-3.2.1.js"></script>
<script>
console.log($('.aa').nextUntil('.cc'))
</script>
</body>
</html>
相信大家看到了打印結(jié)果就很明白了颅和,我們做完節(jié)點操作后都會的到一個新的dom對象,我們想回到之前操作的元素可以怎么辦呢响鹃?
考慮下面代碼:
$('.aa').nextUntil('.cc').find('.ww') 此時我們dom對象停在了$('.ww'),如果我們想回到$('.aa')怎么辦
end: function() {
return this.prevObject || this.constructor();
},
源碼里面有這樣一段,返回上次操作的dom元素,使用end方法。
$.fn.grandparent = function() {
var els = this.parent().parent();
return this.pushStack(els.get());
};
$('.child').grandparent().end() 進(jìn)行了2次查找,但一次end就可以返回最初元素秦踪。
關(guān)于節(jié)點我們再來介紹一個addback方法,結(jié)果顯然易見景馁。
<html>
<head>
<style>
p, div { margin:5px; padding:5px; }
.border { border: 2px solid red; }
.background { background:yellow; }
.left, .right { width: 45%; float: left;}
.right { margin-left:3%; }
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
</head>
<body>
<div class="left">
<p><strong>Before <code>addBack()</code></strong></p>
<div class="before-addback">
<p>First Paragraph</p>
<p>Second Paragraph</p>
</div>
</div>
<div class="right">
<p><strong>After <code>addBack()</code></strong></p>
<div class="after-addback">
<p>First Paragraph</p>
<p>Second Paragraph</p>
</div>
</div>
<script>
$("div.left, div.right").find("div, div > p").addClass("border");
// First Example
$("div.before-addback").find("p").addClass("background");
// Second Example
$("div.after-addback").find("p").addBack().addClass("background");
</script>
</body>
</html>
2. 元素操作
相信下面一段代碼大家會經(jīng)常用到,控制顯示和隱藏
jQuery.fn.extend( {
show: function() {
return showHide( this, true );
},
hide: function() {
return showHide( this );
},
toggle: function( state ) {
if ( typeof state === "boolean" ) {
return state ? this.show() : this.hide();
}
return this.each( function() {
if ( isHiddenWithinTree( this ) ) {
jQuery( this ).show();
} else {
jQuery( this ).hide();
}
} );
}
} );
原本簡單的功能其實在源碼里面也有復(fù)雜的判斷,來看showHide函數(shù)
function showHide( elements, show ) {
var display, elem,
values = [],
index = 0,
length = elements.length;
// Determine new display value for elements that need to change
for ( ; index < length; index++ ) {
elem = elements[ index ];
if ( !elem.style ) {
continue;
}
display = elem.style.display;
if ( show ) {
// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
// check is required in this first loop unless we have a nonempty display value (either
// inline or about-to-be-restored)
if ( display === "none" ) {
values[ index ] = dataPriv.get( elem, "display" ) || null;
if ( !values[ index ] ) {
elem.style.display = "";
}
}
if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
values[ index ] = getDefaultDisplay( elem );
}
} else {
if ( display !== "none" ) {
values[ index ] = "none";
// Remember what we're overwriting
dataPriv.set( elem, "display", display );
}
}
}
// Set the display of the elements in a second loop to avoid constant reflow
for ( index = 0; index < length; index++ ) {
if ( values[ index ] != null ) {
elements[ index ].style.display = values[ index ];
}
}
return elements;
}
showHide如果跟的參數(shù)是true蹄胰,就代表是顯示,dataPriv.set( elem, "display", display )是用來干嘛的呢妻往,記錄元素上一次的display屬性
有一個data的原型函數(shù)如下:
function Data() {
this.expando = jQuery.expando + Data.uid++;
}
Data.uid = 1;
Data.prototype = {
cache: function( owner ) {
var value = owner[ this.expando ];
if ( !value ) {
value = {};
if ( acceptData( owner ) ) {
if ( owner.nodeType ) {
owner[ this.expando ] = value;
} else {
Object.defineProperty( owner, this.expando, {
value: value,
configurable: true
} );
}
}
}
return value;
},
set: function( owner, data, value ) {
var prop,
cache = this.cache( owner );
// Handle: [ owner, key, value ] args
// Always use camelCase key (gh-2257)
if ( typeof data === "string" ) {
cache[ jQuery.camelCase( data ) ] = value;
// Handle: [ owner, { properties } ] args
} else {
// Copy the properties one-by-one to the cache object
for ( prop in data ) {
cache[ jQuery.camelCase( prop ) ] = data[ prop ];
}
}
return cache;
},
get: function( owner, key ) {
return key === undefined ?
this.cache( owner ) :
owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
}
其實就是一段數(shù)據(jù)儲存功能的函數(shù)昨稼,都有cache而已。咱們接著往下看
jQuery.fn.extend( {
detach: function( selector ) {
return remove( this, selector, true );
},
remove: function( selector ) {
return remove( this, selector );
},
text: function( value ) {
return access( this, function( value ) {
return value === undefined ?
jQuery.text( this ) :
this.empty().each( function() {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
this.textContent = value;
}
} );
}, null, value, arguments.length );
},
append: function() {
return domManip( this, arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.appendChild( elem );
}
} );
},
prepend: function() {
return domManip( this, arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.insertBefore( elem, target.firstChild );
}
} );
},
before: function() {
return domManip( this, arguments, function( elem ) {
if ( this.parentNode ) {
this.parentNode.insertBefore( elem, this );
}
} );
},
after: function() {
return domManip( this, arguments, function( elem ) {
if ( this.parentNode ) {
this.parentNode.insertBefore( elem, this.nextSibling );
}
} );
},
empty: function() {
var elem,
i = 0;
for ( ; ( elem = this[ i ] ) != null; i++ ) {
if ( elem.nodeType === 1 ) {
// Prevent memory leaks
jQuery.cleanData( getAll( elem, false ) );
// Remove any remaining nodes
elem.textContent = "";
}
}
return this;
},
我們看到了一堆耳熟能詳?shù)膉query方法,我們來看看是如何定義的烤芦。
detach: function( selector ) {
return remove( this, selector, true );
},
刪除一個元素并將他的返回值保留,以后可能會再加上去。
function remove( elem, selector, keepData ) {
var node,
nodes = selector ? jQuery.filter( selector, elem ) : elem,
i = 0;
for ( ; ( node = nodes[ i ] ) != null; i++ ) {
if ( !keepData && node.nodeType === 1 ) {
jQuery.cleanData( getAll( node ) );
}
if ( node.parentNode ) {
if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
setGlobalEval( getAll( node, "script" ) );
}
node.parentNode.removeChild( node );
}
}
return elem;
}
detach() 會保留所有綁定的事件、附加的數(shù)據(jù)谬泌,這一點與 remove() 不同。
其實無論remove還是detach都能夠保留原始元素宴卖,如果是remove會調(diào)用cleanData方法,來看一下吧
cleanData: function( elems ) {
var data, elem, type,
special = jQuery.event.special,
i = 0;
for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
if ( acceptData( elem ) ) {
if ( ( data = elem[ dataPriv.expando ] ) ) {
if ( data.events ) {
for ( type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );
// This is a shortcut to avoid jQuery.event.remove's overhead
} else {
jQuery.removeEvent( elem, type, data.handle );
}
}
}
elem[ dataPriv.expando ] = undefined;
}
if ( elem[ dataUser.expando ] ) {
// Support: Chrome <=35 - 45+
// Assign undefined instead of using delete, see Data#remove
elem[ dataUser.expando ] = undefined;
}
}
}
}
我們可以看到cleanData把元素的data和event都給清除了
3. 其他技巧
jQuery.fn.extend( {
hover: function( fnOver, fnOut ) {
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
}
} );
我們用jquery寫一個鼠標(biāo)上去個鼠標(biāo)下來事件攘烛,上面一個函數(shù)很輕松就解決了。
trigger: function( type, data ) {
return this.each( function() {
jQuery.event.trigger( type, data, this );
} );
}
我們在個一個元素綁定事件后,會用trigger來主動觸發(fā)這個綁定的事件沟突,還可以給他傳遞一些參數(shù)庸论。
下面就會看到我們經(jīng)常用的動畫函數(shù)了
jQuery.fn.extend( {
fadeTo: function( speed, to, easing, callback ) {
return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
.end().animate( { opacity: to }, speed, easing, callback );
},
animate: function( prop, speed, easing, callback ) {
var empty = jQuery.isEmptyObject( prop ),
optall = jQuery.speed( speed, easing, callback ),
doAnimation = function() {
// Operate on a copy of prop so per-property easing won't be lost
var anim = Animation( this, jQuery.extend( {}, prop ), optall );
// Empty animations, or finishing resolves immediately
if ( empty || dataPriv.get( this, "finish" ) ) {
anim.stop( true );
}
};
doAnimation.finish = doAnimation;
return empty || optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation );
}
Jquery.speed是一個控制動畫速度的函數(shù)簇秒,來看看如何定義
jQuery.speed = function( speed, easing, fn ) {
var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
complete: fn || !fn && easing ||
jQuery.isFunction( speed ) && speed,
duration: speed,
easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
};
//注意這里飄逸的js語法,一般speed給的是數(shù)字,所以opt實際是一個運動對象掐场。
// Go to the end state if fx are off
if ( jQuery.fx.off ) {
opt.duration = 0;
} else {
if ( typeof opt.duration !== "number" ) {
if ( opt.duration in jQuery.fx.speeds ) {
opt.duration = jQuery.fx.speeds[ opt.duration ];
} else {
opt.duration = jQuery.fx.speeds._default;
}
}
}
if ( opt.queue == null || opt.queue === true ) {
opt.queue = "fx";
}
// Queueing
opt.old = opt.complete;
//重新定義了complete函數(shù),dequeue是干嘛的呢蝗罗。
opt.complete = function() {
if ( jQuery.isFunction( opt.old ) ) {
opt.old.call( this );
}
if ( opt.queue ) {
jQuery.dequeue( this, opt.queue );
}
};
return opt;
};
由于dequeue涉及隊列比較復(fù)雜瓶颠,自己可以去看源碼吸祟,最后來看一個很重的函數(shù)。
jQuery.fn.extend( {
offset: function( options ) {
// 如果options存在,則是設(shè)置他的offset
if ( arguments.length ) {
return options === undefined ?
this :
this.each( function( i ) {
jQuery.offset.setOffset( this, options, i );
} );
}
var doc, docElem, rect, win,
elem = this[ 0 ];
if ( !elem ) {
return;
}
if ( !elem.getClientRects().length ) {
return { top: 0, left: 0 };
}
rect = elem.getBoundingClientRect();
doc = elem.ownerDocument;
docElem = doc.documentElement;
win = doc.defaultView;
return {
top: rect.top + win.pageYOffset - docElem.clientTop,
left: rect.left + win.pageXOffset - docElem.clientLeft
};
},
我們想獲得某個元素距離窗口左邊和上邊的距離可以用這個方法纤虽,解釋一下getBoundingClientRect
rectObject = object.getBoundingClientRect();
DOMRect 對象包含了一組用于描述邊框的只讀屬性——left、top杰刽、right和bottom,單位為像素。除了 width 和 height 外的屬性都是相對于視口的左上角位置而言的蔗怠。
好了,今天就先講到這里了梁丘,以后有機(jī)會在繼續(xù)把最后一期給做完吧。值漫。。