オブジェクトを配列みたいに扱いたい

[新着] Webテンプレートを仮オープンしました



0   名前: げほ : 2007/03/02(金) 19:04  ID:k8LAlnd7 sub-fF
タイトル通りですが、たとえば、<div id="foo"> の要素にいくつかのSPAN要素とひとつの
IMG要素があるとして、
var spans = document.getElementById("foo").getElementsByTagName("SPAN");

for(var i = 0, len = spans.length; i < len; i++){
  適当な処理
} 
みたいなループ処理に、ひとつだけあるIMG要素にも同じ処理を施したいとします。
このときに、ついでにと思って(オブジェクトも配列だからと)、
spans.push(document.getElementById("foo").getElementsByTagName("IMG")[0])
みたいに出来たら楽だ、と思ったのですが、当然エラーになります。何かうまい方法
(IMG要素も一緒くたにループ処理にいれてしまう)はありませんかねぇ?

1   名前: げほ : 2007/03/02(金) 19:04  ID:k8LAlnd7 sub-fF
あ、ちなみに
document.getElementById("foo").getElementsByTagName("*")
以外で。HTMLSpanElementとかHTMLImageElementとかの集合を、配列みたいに
concatしたりsliceしたり出来たら良いなと思う次第です。

2   名前: 匿名 : 2007/03/02(金) 19:04  ID:Z/T9lFdK sub-kJ
以下は HTML 限定の話とする(XML/XHTML は考えない、つまり IE の document.selectNodes() を使わない)。

>>0
> IMG要素も一緒くたにループ処理にいれてしまう

そのための DOM2 Traversal。
// Gecko、Opera、Safari
/*
if (document.implementation &&
    document.implementation.hasFeature ('Traversal', '2.0'))
*/
{
    var node, tw = document.createTreeWalker (
        document.getElementById ('foo'),
        NodeFilter.SHOW_ELEMENT,
        function (n) {
            switch (n.tagName) {
            case 'SPAN' :
            case 'IMG' :
                return NodeFilter.FILTER_ACCEPT;
            dfault :
                return NodeFilter.FILTER_SKIP;
            }
        },
        false
    );
    
    while (node = tw.nextNode ()) {
        node;  // *[@id="foo"] 要素内の span/img 要素
    }
    
    node = tw = null;
}

もしノード数が多ければ、DOM3 XPath が圧倒的に速い。
// Gecko、Opera
/*
if (document.implementation &&
    document.implementation.hasFeature ('XPath', '3.0'))
*/
{
    var node, result = document.evaluate (
        'descendant::*[ self::SPAN or self::IMG ]',
        document.getElementById ('foo'),
        null,
        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
        null
    );
    
    for (var i = 0; i < result.snapshotLength; i++) {
        node = result.snapshotItem (i);  // *[@id="foo"] 要素内の span/img 要素
    }
    
    node = result = null;
}

IE を考慮するなら >>1 のように DOM1 Core の範囲内でやるしかない(TreeWalker のエミュレーション自体は割と簡単にできるが、遅い)。


>>1
> 配列みたいにconcatしたりsliceしたり出来たら良いな

NodeList は、文書ツリーに対する修正の影響を即座に受けるインタフェース(たとえば、文書ツリーからノードが除去されたら、NodeList からも除去される)。

単に配列として扱いたいだけならば、Array に移し替えた方が良い。JavaScript 1.6 の Array.map を使えば簡単。
<script type="application/javascript; version=1.6">

var callback = function (n) { return n; };

// span 要素と img 要素のリストを Array で作成
var nodeArray = Array.map (document.getElementsByTagName ('span'), callback);

nodeArray.push.apply (
    nodeArray,
    Array.map (document.getElementsByTagName ('img'), callback)
);
</script>

基本的にループでコピーするだけだから、IE でも(効率を気にしなければ)すぐできる。

3   名前: げほ : 2007/03/02(金) 19:04  ID:k8LAlnd7 sub-fF
Z/T9lFdKさん、どうもありがとうございます。
XPathやTraverseは、IEでシミュレートするとなるとどうしようもなくパフォーマンス
が悪く、正直、使い物にならないなと思っていたのですが、Array.mapを使えば
そうパフォーマンスも悪くなさそうですね。まさにこんなイメージでした。
 それにしても
 http://nanto.asablo.jp/blog/2005/10/08/101406
 にあるように、
<blockquote>IE では DOM ノードは JavaScript のオブジェクトではないかららしい。
</blockquote>
というのが痛いでえすねぇ。IE7になっても、Firefox,Opera,Safariに遅れること数百年。
でも、これが8割以上のシェアを占めているって、IE6と何ら状況は変わらんですなぁ。
 お、思わず愚痴ってしまった。

いずれにせよ、大変参考になりました。

4   名前: 匿名 : 2007/03/02(金) 19:04  ID:Z/T9lFdK sub-kJ
完全にスレ違いの蛇足だけど。

>>1 では除外したが、もし application/xml として XHTML を扱えるのであれば、ご存知のように IE でも XPath が使える。弄りたい XHTML を外部リソースとして取得し、弄った後に XSLT で HTML 変換すれば良い。まあ、かえって面倒だけど。
try {
    var xmlDoc = new ActiveXObject ('Msxml2.DOMDocument.3.0');
    xmlDoc.async = false;
    xmlDoc.load ('sample.xml');  // application/xml
    var nodeList = xmlDoc.getElementById ('foo').selectNodes (
        'descendant::*[ namespace-uri()="http://www.w3.org/1999/xhtml"\
            and (local-name()='span' or local-name()='img') ]');
    
    //  ... nodeList に対する処理 ...
    
    var xslDoc = new ActiveXObject ('Msxml2.FreeThreadedDOMDocument.3.0');
    xslDoc.async = false;
    xslDoc.load ('sample.xsl');
    
    var xsltProc = new ActiveXObject ('Msxml2.XSLTemplate.3.0');
    xsltProc.stylesheet = xslDoc;
    xsltProc = xsltProc.createProcessor ();
    xsltProc.input = xmlDoc;
    xsltProc.transform ();
    
    var resultHTML = xsltProc.output;

} catch (error) { ; }

話ついでに、application/xml なら Gecko で XPointer も使える。
try {
    var xmlDoc = document.implementation.createDocument ('', '', null);
    xmlDoc.async = false;
    xmlDoc.load ('sample.xml');  // application/xml
    
    var result = xmlDoc.evaluateXPointer ('xpath1(/descendant::*[ @id="foo" ]/\
        descendant::*[ namespace-uri()="http://www.w3.org/1999/xhtml"\
            and (local-name()='span' or local-name()='img') ])');

} catch (error) { ; }

この場合、どの道 XPath を使っているので旨味は少ないのだが、ミソは戻り値が Range の集合であること。
var selection = getSelection ();
var I = result.length;
var i = 0;
while (i < I) selection.addRange (result.item (i++));

で、取得範囲をまとめて選択状態にできる。まあ、テキストデータの範囲を選択できない(xpointer() の range-to() 関数などが実装されてない)以上、あまり意味はないのだが。

一覧へ戻る