2010-01-27 12 views
7

Mam następujący kod, który działa w FF/Chrometylko IE javascript błędu z getElementsByTagName

var stack = [Array.prototype.slice.call(document.getElementsByTagName("body")[0].childNodes)], nodes, node, parent, text, offset; 
while (stack.length) { 
    nodes = stack.pop(); 
    for (var i=0, n=nodes.length; i<n; ++i) { 
     node = nodes[i]; 
     switch (node.nodeType) { 
      case Node.ELEMENT_NODE: 
       if (node.nodeName.toUpperCase() !== "SCRIPT") { 
        stack.push(Array.prototype.slice.call(node.childNodes)); 
       } 
       break; 
      case Node.TEXT_NODE: 
       text = node.nodeValue; 
       offset = text.indexOf("[test="); 
       if (offset >= 0 && text.substr(offset).match(/^(\[test=(\d+)\])/)) { 
        parent = node.parentNode; 
        var before = document.createTextNode(text.substr(0, offset)); 
         link = document.createElement("a"), 
         after = document.createTextNode(text.substr(offset + RegExp.$1.length)); 
        link.appendChild(document.createTextNode(text.substr(offset, RegExp.$1.length))); 
        link.setAttribute("href", "http://example.com/" + RegExp.$2); 
        parent.insertBefore(after, node); 
        parent.insertBefore(link, after); 
        parent.insertBefore(before, link); 
        parent.removeChild(node); 
        stack.push([after]); 
       } 
     } 
    } 
} 

Zasadniczo co robi to, jeśli stwierdzi [test = 25] na stronie konwertuje go do linku, który wskazuje example.com/25

w IE pojawia się następujący błąd: JScript Obiekt Oczekiwany w pierwszej linii:

var stack = [Array.prototype.slice.call(document.getElementsByTagName("body")[0].childNodes)], nodes, node, parent, text, offset; 

błąd ten występuje zarówno w IE7 i IE8.

Każda pomoc zostanie doceniona.

Dzięki.

Odpowiedz

12

To nie jest legalne zadzwonić Array.prototype.slice na NodeList obiektu zwracanego przez właściwość childNodes (lub różnych innych metod DOM).

Normalnie nie byłoby legalne zadzwonić Thing.prototype.method na niczym, ale wystąpienie Thing jednak przeglądarek tradycyjnie dozwolone - a średnia ECMAScript Third Edition wymaga - przypadek szczególny dla wielu Array.prototype metod tak, że mogą zostać wezwane dowolny natywny obiekt JavaScript, który jest wystarczająco podobny do Array. Oznacza to w szczególności, że mogą one być używane na obiekcie arguments, który wygląda jak Array, ale tak naprawdę nie jest.

Jednak inne obiekty kolekcji w DOM nie są zdefiniowane jako natywne obiekty JavaScript; mogą być "obiektami hosta", które są całkowicie zaimplementowane przez przeglądarkę, a nie język. Wszystkie zakłady są wyłączone dla obiektów goszczących ...

Whether the slice function can be applied successfully to a host object is implementation-dependent.

Więc Array.prototype.slice może nie działać na liście węzłów, aw IE przed wersji 8, rzeczywiście, to nie będzie.

Jeśli chcesz zrobić kopię zwykły tablicę na liście węzłów, musisz zrobić to długo, ale bezpieczny sposób:

Array.fromSequence= function(seq) { 
    var arr= new Array(seq.length); 
    for (var i= seq.length; i-->0;) 
     if (i in seq) 
      arr[i]= seq[i]; 
    return arr; 
}; 

var stack = [Array.fromSequence(document.body.childNodes)]; 

Nawiasem mówiąc, można sprawić, aby linkifier nieco prostsze przy użyciu textnode.splitText, a ja bym bardzo nieufnie podchodził do korzystania z globalnych właściwości RegExp, tak jakby w nieoczekiwanej pracy z wyrażeniem regularnym wystąpił jeden z pośrednich wywołań, które zostaną utracone. Patrzenie na obiekt meczu jest zwykle lepsze. Zobacz this question dla innego ataku w zasadzie ten sam problem.

+0

+1. I zupełnie przypadkowo: w niektórych testach JavaScript w Firefoksie, które ostatnio zrobiłem, odkryłem, że skonstruowanie tablicy za pomocą pustego literału '[]' i 'push' jest, jak na ironię, szybsze niż metoda tutaj pokazana. –

+0

Interesujące! 'push' nie może odtworzyć listy rzadkiej (z brakującymi elementami), jak w powyższym, ale dla NodeList nie będziesz jej potrzebował. – bobince

+0

Dziękuję za informacje, a zwłaszcza za link do tego drugiego pytania. Właśnie tego szukałem. – Rob

2

Spróbuj użyć zamiast tego:

var stack = [Array().slice.call(document.getElementsByTagName("body")[0].childNodes)] 

Jest trochę funkyness z IE i prototypów/konstruktorów. Nie mogę przetestować teraz, na Macu.

Więcej informacji tutaj: Difference between Array.slice and Array().slice

3

Myślę, że to dlatego getElementsByTagname zwraca NodeList - nie tablicę (choć niektóre rzeczy takie jak [] pracy operatora w tym, że jak one działają na tablicach, nie są one takie same)

Może to być rozwiązane w mniej skomplikowany sposób:

var els = document.body.getElementsByTagName("*"); 
for (var i=0, numEls=els.length, el; i<numEls; i++){ 
    el = els.item(i); 
    el.normalize();   
    for (var j=0, chs = el.childNodes, numChs=chs.length, ch; j<numChs; j++){ 
     ch = chs.item(j); 
     if (ch.nodeType==Node.TEXT_NODE){ 
      //you code for replacing text with link goes here 
      //ps i suggest using ch.data instead of ch.nodeValue 
     } 
    } 
} 
+0

Używanie 'getElementsByTagName (" * ")' jest dobrym pomysłem, ale pamiętaj, że jest to NodeList na żywo: podczas dodawania linków do strony, otrzyma dodatkowe elementy. Będzie to oznaczać, że elementy z ostatnią liczbą elementów dodawanych nie będą sprawdzane (ze względu na optymalizację zapamiętywania "numEls"), a jeśli tekst zastępczy może zawierać wyszukiwany tekst, to zrobi zwariowany rekurencyjny zamiennik. Lepiej zabrać kopię nodelisty, jak pierwotnie próbował, lub po prostu powtórzyć NodeList w odwrotnej kolejności. – bobince

+0

@bobince: rzeczywiście, masz rację :) dzięki za wskazanie tego. Ale w tym przypadku, myślę, że chciałbym spróbować przechowywać wszystkie textnodes, które pasują w tablicy, a następnie poza pętlą, pętla nad tablicą ponownie, aby dokonać wymiany - myślę, że powinno być możliwe bez potrzeby żadnych wyraźnych pozycji węzła. Będę musiał pomyśleć o twojej sugestii, żeby przejść na drugą stronę ... Byłem zbyt długo i potrzebowałem snu. Ale pomyślę o tym - dzięki! –

Powiązane problemy