コードリーディングjQuery.fn.nextAll

経緯

jQuery.fn.nextAllが割と早いのでどうせnextSiblingを次々辿って要素を詰めたりしてるんだろうと思ってコードを追ってみた。
バージョンは1.2.3を使用。

(1)ソースコード上を「nextAll」で検索したら、1253行目〜1272行目にそれっぽいのがあった。

jQuery.each({
/*中略*/
	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
/*中略*/
}, function(name, fn){
	jQuery.fn[ name ] = function( selector ) {
		var ret = jQuery.map( this, fn );

		if ( selector && typeof selector == "string" )
			ret = jQuery.multiFilter( selector, ret );

		return this.pushStack( jQuery.unique( ret ) );
	};
});

(2)jQuery.each(732行目〜755行目)。

(1)で使ってるのだと引数が二つ…argsなし、第一引数は配列ではない…object.length == undefinedなので、
その条件の場合のところだけ見る。

	each: function( object, callback, args ) {
		if ( args ) {
/*中略*/
		} else {
			if ( object.length == undefined ) {
				for ( var name in object )
					if ( callback.call( object[ name ], name, object[ name ] ) === false )// ココ
						break;
			} else
/*中略*/
		}

		return object;
	},

ループがくるくる回ってキーがnextAllのところが「ココ」とコメントがついてるところを通ったときの処理を書き下すと、↓こうなる。

(function(name, fn){
	jQuery.fn[ name ] = function( selector ) {
		var ret = jQuery.map( this, fn );

		if ( selector && typeof selector == "string" )
			ret = jQuery.multiFilter( selector, ret );

		return this.pushStack( jQuery.unique( ret ) );
	};
}).call(function(elem){return jQuery.dir(elem,"nextSibling");},'nextAll',function(elem){return jQuery.dir(elem,"nextSibling");});

Function.prototype.callの第一引数は実行される関数のthis扱いだけど、
その関数内にthisはなく、関数内で定義される関数にthisがあるだけなので今回は関係ない。
第二引数以降はそれぞれ順番が一つずつ前にずれた引数として使われるので、↓こうなる。

jQuery.fn.nextAllの定義

jQuery.fn[ 'nextAll' ] = function( selector ) {
	var ret = jQuery.map( this, function(elem){return jQuery.dir(elem,"nextSibling");} );

	if ( selector && typeof selector == "string" )
		ret = jQuery.multiFilter( selector, ret );

	return this.pushStack( jQuery.unique( ret ) );
};

thisを第一引数として、jQuery.map(1193行目〜1210行目)を呼び出しているのは、
thisであるjQueryオブジェクトに配列のように格納されたDOM要素全部について、
第二引数の関数を実行して各戻り値を全部配列に詰めて返してくれるという処理になる。


jQuery.dir(elem,"nextSibling")がelem以降の全ての兄弟要素を返すので、
retはjQueryオブジェクトが持ってる要素全てについてそれぞれの、
自分以降の全ての兄弟要素を持っている。


途中を飛ばして、jQuery.fn.pushStackは配列を渡すと、
後でjQuery.fn.endやjQuery.fn.andSelfで使う用にスタックに積んだ後、
jQueryオブジェクトを返す。


例えば、http://twitter.com/homeで、↓を流すと、
jQuery('.vcard:eq(79),tr:eq(9)').nextAll()
81番目以降の.vcard全部と11番目以降のtr全部が返ってくる。

jQueryオブジェクトが持ってる要素全てについてそれぞれの、
自分以降の全ての兄弟要素を持つjQueryオブジェクト

ということになる。


飛ばしていたところに話を戻すと、
jQuery.multiFilter(1448行目〜1459行目)はそこから呼ばれてるjQuery.filterが結構重たいので、今回は割愛。
何をやってるかというと引数で指定されたCSSセレクタで絞り込んでるだけ。
jQuery('tr:eq(1)').nextAll(':contains(monjudoh)')
↑とかやると3番目以降で「monjudoh」という文字列が含まれるtrが返ってくる。


jQuery.unique(1160行目〜1179行目)はjQuery.dataを追わなきゃいけないので、やっぱり割愛。
配列中のDOM要素の重複を取り除いてると把握しておけばOK。
例えば↓は1番目のtrへの参照が二つ入ったjQueryオブジェクトを返す。
jQuery('tr:eq(0),tr:eq(0)')
↓だと2番目〜20番目のtrが2回入ってsizeが38のjQueryオブジェクトではなく、sizeは19のjQueryオブジェクトが返る。
jQuery('tr:eq(0),tr:eq(0)').nextAll()

jQuery.dir(1779行目〜1788行目)

	dir: function( elem, dir ){
		var matched = [];
		var cur = elem[dir];// 今回の場合elem.nextSiblingと同じ
		while ( cur && cur != document ) {
			if ( cur.nodeType == 1 )// Node.ELEMENT_NODE == 1 
				matched.push( cur );
			cur = cur[dir];// 今回の場合cur.nextSiblingと同じ
		}
		return matched;
	},

第一引数で渡した要素を起点としてnextSiblingを次々に辿り、
ELEMENT_NODEの場合のみ配列に詰めていって、これ以上辿れなくなったら配列を返す。

感想

  • jQuery.eachで同一構造の関数を一括定義してるのが面白かった。
  • あっち行ったりこっちいったりするので追うのが大変

抑えるポイントは↓

  • jQueryオブジェクトの関数はjQuery.fn.hogehoge
  • 内部で使用する関数はjQuery.hogehoge
  • jQuery.fnとjQueryで同名の関数が定義されていることもある
    • jQuery.dir.toString()のようにして実際に定義された関数をみる