DOM 要素の集合のイテレーション

Update: id:reinyannyan:20060411:p1 に続きます


getElementsByTagName の様なメソッドで得られた、配列ではない要素集合を配列のように扱いたい時に、prototype.js では "$A" という関数によってまず配列化する、という方法を用います。

これは非常に素晴らしい、便利な方法なんですが、集合の規模が大きくなると、それだけ配列化によるオーバーヘッドも大きなものになってしまいます。

そこで、仮にこのようなものを作ってみました。

// DOM Iterator
var Domi =
{
    each: function (collection, yield)
    {
	for (var i = 0; i < collection.length; i++)
	    yield(collection[i]);
    }
};

べつに配列が欲しいわけじゃない、単純に each でイテレートしたいだけ、という場合に使える方法です:

Domi.each(document.getElementsByTagName('A'), function (a)
{
    print(a.href);
});

配列化のオーバーヘッドが無くなるという点で、結構高速化が見込めるんじゃないかと思います。

さらに、この each に対して用途に応じたメソッドを被せていくことで、Enumerable と同じような応用をすることもできます (何なら Enumerable を組み込んでも良いかも知れません)。

このことを踏まえて、次の記事: IT戦記『prototype.js の document.getElementsByClassName は重い』で指摘されている同様の問題について考えてみました。

根本的な解決にはなりませんが、each が出来さえすればよい、というケースに限れば以下のようなメソッドを作ることができるでしょう:

Domi.class_each = function (className, yield, parentElement)
{
    var children = ($(parentElement) || document.body).getElementsByTagName('*');
    // 正規表現はキャッシュしておく!
    var pattern = new RegExp("(?:^|\\s)" + className + "(?:\\s|$)");
    this.each(children, function (child)
    {
	if (child.className.match(pattern))
	    // 1.5.0_pre1 (以降) の Element.extend は省略しています
	    yield(child);
    });
};

個人的に DOM 要素を配列として保持したいと思うことがまず無いので、これで十分かなという感じです。