2010-01-27 4 views
7

У меня есть следующий код, который работает в FF/ChromeIE только Javascript ошибка 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]); 
       } 
     } 
    } 
} 

В основном то, что он делает, если он находит [тест = 25] на странице он преобразует его в ссылку, которая указывает на example.com/25

в IE я получаю следующее сообщение об ошибке: JScript Object Expected на первой линии:

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

Эта ошибка возникает в обоих IE7 и IE8.

Любая помощь будет оценена по достоинству.

Спасибо.

ответ

12

Это не законно называть Array.prototype.slice на NodeList объект, возвращаемый свойством childNodes (или различные другие методы DOM).

Обычно это не было бы законно назвать Thing.prototype.method на что-либо, кроме экземпляра Thing, однако браузеры традиционно разрешено - и стандарт ECMAScript Третье издание требует - особый случай для многих Array.prototype методов, так что они могут быть вызваны на любой объект нативного JavaScript, который достаточно похож на Array. Это означает, что они могут использоваться на объекте arguments, который выглядит как Array, но на самом деле это не так.

Однако NodeList и другие объекты коллекции в DOM не являются родными объектами JavaScript; им разрешено быть «объектами-хозяевами», которые полностью реализованы браузером, а не языком. Все ставки выключены для объектов хозяевах ...

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

Так Array.prototype.slice может не работать NodeList, и в IE до версии 8, на самом деле, это не будет.

Если вы хотите сделать равнину массива копию NodeList, вы должны сделать это долгий, но безопасный путь:

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)]; 

Кстати, вы можете сделать что linkifier немного проще, используя textnode.splitText, и я был бы очень осторожен в использовании глобальных свойств RegExp, как если бы какая-либо неожиданная работа с регулярным выражением возникала в одном из промежуточных вызовов, они будут потеряны. Обычно смотреть на объект совпадения лучше. См. this question для другой атаки при той же самой проблеме.

+0

+1. И чисто как случайное примечание: в некоторых тестах JavaScript в Firefox, которые я делал недавно, я обнаружил, что построение массива с использованием пустого литерала '[]' и 'push', по иронии судьбы, быстрее, чем метод, показанный здесь. –

+0

Интересно! 'push' не может воссоздать разреженный список (с отсутствующими элементами), как это было выше, но для NodeList вам это не понадобится. – bobince

+0

Спасибо за информацию и особенно за эту ссылку на другой вопрос. Это именно то, что я искал. – Rob

2

Попробуйте использовать вместо этого:

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

Там какая-то funkyness с IE и прототип/конструкторах. Я не могу проверить прямо сейчас, на mac.

Больше информации здесь: Difference between Array.slice and Array().slice

3

Я думаю, это потому, что getElementsByTagname возвращает нодлист - не массив (хотя некоторые вещи, как [] работы оператора в том, как они работают с массивами, они не совпадают)

Возможно, это может быть решена в менее сложном виде:

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

Использование 'getElementsByTagName (" * ")' - хорошая идея, но помните, что это живой NodeList: по мере добавления ссылок на страницу он получит дополнительные элементы. Это будет означать, что последние элементы с добавленным количеством элементов не будут проверяться (из-за оптимизации оптимизации numEls), и если заменяющий текст может содержать искомый текст, он сделает сумасшедшую рекурсивную замену. Лучше взять копию нодлиста, как было первоначально предпринято, или просто перебрать NodeList в обратном порядке. – bobince

+0

@bobince: действительно, ты прав :) Спасибо, что указал. Но в этом случае, я думаю, я попытаюсь сохранить все текстовые поля, которые соответствуют в массиве, а затем за пределами цикла, снова петлю над массивом, чтобы сделать замену - я думаю, что это должно быть возможно без каких-либо явных позиций узла. Мне нужно подумать о вашем предположении, чтобы пройти список в обратном порядке ... Я слишком долго и тебе нужно спать. Но я подумаю об этом - спасибо! –

Смежные вопросы