jQueryのeachの仕組みを徹底的にわかりやすく解説してみた。

eachのキホン:要素を1個ずつ処理する

まずおさらい。
このようなHTMLがあるとします。
<ul>
    <li>foo</li>
    <li>bar</li>
</ul>
eachを使うと、要素1個ずつに対して順繰りに処理を行うことができます。
<script>
$(function(){

    $('li').each(function(){
        alert( $(this).text() );  // 'foo', 'bar'と表示
    });

});
</script>
これがキホンです。

変形してみる

上のeach式は、下のように変形できます。
(一番外側のブロック$(function(){....})は省略します)
$.each( $('li'),  function(){
    alert($(this).text());
});
さらに変形してみます。
グローバル変数 "$" はグローバル変数 "jQuery" の別名なので、
var func_each = jQuery.each;

func_each( $('li'),  function(){
    alert($(this).text());
});
このfunc_eachは、メソッドでもなんでもないただの関数です。
つまり、jQuery.eachは単なる関数といってよいです。
グローバル関数みたいなものです。

ここが重要なポイントです。

eachは、実はjQueryとあまり関係がない。

eachは、他のclickやらfindやらshowやらと違って、jQueryとの関係が薄いのです。
jQueryの中にあるけれどもjQueryとは独立している。
例えていうとイタリアの中にあるバチカン市国のようなものです。

HTML要素ではなく単純な配列を処理させてみると、よくわかります。
var x = ['a','b','c'];
$.each( x, function(){
    alert(this);    // 'a', 'b', 'c'と表示
});
これを書き換えます。
var func_each = jQuery.each;

func_each( ['a','b','c'] , function(){
    alert(this);
});
どうですか?
このfunc_eachを見ると、もうjQueryはほとんど関係がなさそうですね。
そう、eachはJavaScriptのループ処理を便利にするための単なるユーティリティ関数なのです。
HTMLやDOMとはほぼ関係がないのです。

なぜコールバック内でthisが使えるのか?

さて、eachに渡すコールバックの中でなぜかthisが使えています。
あれは一体なんなんでしょうか?

ここでjQueryのソースコードを除いてみましょう。
jquery-1.7.2.js
// args is for internal usage only
each: function( object, callback, args ) {
	var name, i = 0,
		length = object.length,
		isObj = length === undefined || jQuery.isFunction( object );

	if ( args ) {
		if ( isObj ) {
			for ( name in object ) {
				if ( callback.apply( object[ name ], args ) === false ) {
					break;
				}
			}
		} else {
			for ( ; i < length; ) {
				if ( callback.apply( object[ i++ ], args ) === false ) {
					break;
				}
			}
		}

	// A special, fast, case for the most common use of each
	} else {
		if ( isObj ) {
			for ( name in object ) {
				if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
					break;
				}
			}
		} else {
			for ( ; i < length; ) {
				if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
					break;
				}
			}
		}
	}

	return object;
},
ちょっと大掛かりですね。
大胆に省略して、本質的な部分を残すとこうなります。
each: function( object, callback) {
    var key;
    for ( key in object ) {
        var val = object[key];

        if ( callback.call( val, key, val ) === false ) {
            break;
        }
    }
    return object;
}
要素を1個ずつまわしながら、callback.callを実行しています。
関数.call
これがポイントです。
これは、ただの関数をメソッド呼び出しとして呼び出すためのJavaScriptの機能です。

あらゆる関数オブジェクトは、callというメソッドを持っています。
関数オブジェクトのcallメソッドを呼び出すと、第1引数がthisに化けます。
つまり、callback.call(val)とするとcallback内では this = val となるのです。
これを"bind"とか「束縛」とか言います。
詳しくは下記の記事でめちゃめちゃわかりやすく解説してますのでご覧ください。

JavaScriptで、メソッドをコールバックとして渡す方法(コールバック関数でthisをbindさせる方法)

ここまで理解できれば、
下記2通りの書き方が同じであることがわかると思います。
$.each( ['a','b','c'] , function(){
    alert(this);
});
$.each( ['a','b','c'] , function(index, v){
    alert(v);
});
これで、eachに関するもやもやがすっきりしたのではないでしょうか?

オレオレeachを実装してみる。

では、自分でeachを実装してみましょう。
意外と簡単にできてしまいます。
もちろんjQueryは使いません。
var myEach = function( object, callback) {
    var key;
    for ( key in object ) {
        var val = object[key];

        if ( callback.call( val, key, val ) === false ) {
            break;
        }
    }
    return object;
};
実行してみましょう。
var list = ['a','b','c'];

myEach( list, function(index, val) {
    alert(index + ' : ' + val);
});

myEach( list, function(index, val) {
    alert(index + ' : ' + this);
});

var obj = { x : 'foo', y : 'bar'};

myEach( obj, function(index, val) {
    alert(index + ' : ' + val);
});

myEach( obj, function(index, val) {
    alert(index + ' : ' + val);
});
ちゃんと動きましたか?
これでeachの謎が解けたことと思います。

アロホモーラ!
カテゴリ: