「Firefox3でネイティブ実装の document.getElementsByClassName() が良い!」と聞きつけ、クロスブラウザ化
こちらが最新版です ⇒ http://d.hatena.ne.jp/uupaa/20090608/1244449986
まず、getElementsByClassName()と同様に、ノードリストを返すメソッド document.getElementsByTagName() が、何を返しているのか, document.getElementsByClassName() は実装されているのか調べました。
Browser | document. getElementsByTagName() result type |
document. getElementsByClassName() implemented |
---|---|---|
Firefox2(2.0.0.13) | HTMLCollection | No |
Firefox3(Beta5) | HTMLCollection | Yes |
Opera9(9.25) | NodeList | No |
Opera9(9.50Beta) | NodeList | Yes |
Safari3(3.1) | NodeList | Yes |
IE6 | Object | No |
IE7 | Object | No |
IE8(Beta1) | Object | No |
次に HTMLCollection と NodeList の対応状況を調べました。
Browser | HTMLCollection | NodeList |
---|---|---|
Firefox2(2.0.0.13) | Yes | Yes |
Firefox3(Beta5) | Yes | Yes |
Opera9(9.25) | Yes | Yes |
Opera9(9.50Beta) | Yes | Yes |
Safari3(3.1) | No | Yes |
IE6 | No | No |
IE7 | No | No |
IE8(Beta1) | No | No |
いつも通りバラけてますね。
Web Applications 1.0 によると、document.getElementsByClassName() が返すのは、生きている(Liveな) NodeList ですので、Firefoxは仕様と異なる型が返ってきています。
さて、document.getElementsByClassName() の代替実装付きテスト用コードです。(xsnapはXPathのスナップショットを取る関数です)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>JavaScript::getElementsByClassName test</title> <!--[if IE]><script type="text/javascript" src="../../lib/xpath.js"></script><![endif]--> <style type="text/css"> body { background-color: black; color: white; } .false { color: gray } </style> </head> <body> <div id="example"> <p id="p1" class="aaa bbb"/> <p id="p2" class="aaa ccc"/> <p id="p3" class="bbb ccc"/> </div> <a href="http://www.whatwg.org/specs/web-apps/current-work/#getelementsbyclassname">WHATWG Web Applications 1.0: getElementsByClassName</a> <pre> <div id="example"> <p id="p1" class="aaa bbb"/> <p id="p2" class="aaa ccc"/> <p id="p3" class="bbb ccc"/> </div> A call to <input type="button" value="document.getElementById('example').getElementsByClassName('aaa')" onclick="test1()" /> would return a NodeList with the two paragraphs p1 and p2 in it. A call to <input type="button" value="getElementsByClassName('ccc bbb')" onclick="test2()" /> would only return one node, however, namely p3. A call to <input type="button" value="document.getElementById('example').getElementsByClassName('bbb ccc ')" onclick="test3()" /> would return the same thing. A call to <input type="button" value="getElementsByClassName('aaa,bbb')" onclick="test4()" /> would return no nodes; none of the elements above are in the "aaa,bbb" class. </pre> <script> function test1() { // document.getElementById('example').getElementsByClassName('aaa') uu.forEach(uu.enumClass('aaa', document.getElementById('example')), function(v) { alert(v.id); }); } function test2() { // getElementsByClassName('ccc bbb') uu.forEach(uu.enumClass('ccc bbb'), function(v) { alert(v.id); }); } function test3() { // document.getElementById('example').getElementsByClassName('bbb ccc ') uu.forEach(uu.enumClass('bbb ccc ', document.getElementById('example')), function(v) { alert(v.id); }); } function test4() { // getElementsByClassName('aaa,bbb') uu.forEach(uu.enumClass('aaa,bbb'), function(v) { alert(v.id); }); } </script> <script> var uu = window.uu = { enumClass: function(className, context /* = document */, tagName /* = undefined */) { var d = document, c = className, ctx = context || d, tag = tagName || '*'; if (d.getElementsByClassName) { return ctx.getElementsByClassName(c); } var x = function(c) { return 'contains(concat(" ", @class, " "), " ' + c + ' ")'; }; var rule = (c.indexOf(' ') >= 0) ? c.match(/\w+/g).map(x).join(" and ") : x(c); return uu.xsnap('.//' + tag + '[' + rule + ']', "", ctx, false); }, xsnap: function(xpath, attr, context, sort) { var n = document.evaluate(xpath, context || document, null, sort ? 7 : 6, null); var rv = [], i = 0; attr = attr || ""; if (attr.length) { for (; i < n.snapshotLength; ++i) { rv.push(n.snapshotItem(i).getAttribute(attr)); } } else { for (; i < n.snapshotLength; ++i) { rv.push(n.snapshotItem(i)); } } return rv; }, forEach: function(map, iter, bindThis /* = undefined */) { if (!map || typeof iter !== "function") { throw TypeError(); } if (typeof map.forEach === "function") { return map.forEach(iter, bindThis); } if (map.length) { // Array like collection for (var i = 0, sz = map.length; i < sz; ++i) { (i in map) && iter.call(bindThis, map[i], i, map); } } else { for (var p in map) { map.hasOwnProperty(p) && iter.call(bindThis, map[p], p, map); } } return this; } }; if (!Array.prototype.forEach) { Array.prototype.forEach = function(iter, bindThis /* = undefined */) { if (typeof iter !== "function") { throw TypeError(); } var i = 0, sz = this.length; for (; i < sz; ++i) { (i in this) && iter.call(bindThis, this[i], i, this); } return this; }; } if (!Array.prototype.map) { Array.prototype.map = function(iter, bindThis /* = undefined */) { if (typeof iter !== "function") { throw TypeError(); } var rv = new Array(this.length), i = 0, sz = this.length; for (; i < sz; ++i) { if (i in this) { rv[i] = iter.call(bindThis, this[i], i, this); } } return rv; }; } </script> </body> </html>
今回は、document.prototype.getElementsByClassName() とはしませんでした。
document.evaluate()が使えない環境(IE6/7/8)では、JavaScript-XPath等を使用してください。
テストコードは、Web Applications 1.0 にあるコードそのままです。
'ccc bbb'でも <p id="p3" class="bbb ccc"/> が探せなきゃいけないし、'bbb ccc 'でも探せなきゃいけないので実装がほんのちょっとだけ面倒くさいですね。
多くの人が getElementsByClassName() の独自実装を試みているようですが、
'ccc bbb'や'bbb ccc ' に対応している実装(仕様を満たしている実装)はあんまりないみたいです。