Почему в нодлисте нет forEach?

91

Я работал над коротким скриптом для изменения <abbr>внутреннего текста элементов, но обнаружил, что у nodelistнего нет forEachметода. Я знаю, что nodelistэто не наследуется Array, но разве это не кажется forEachполезным методом? Есть конкретная проблема реализации я не в курсе , что мешает добавляющих forEachк nodelist?

Примечание. Мне известно, что у Dojo и jQuery forEachв той или иной форме есть нодлисты. Я не могу использовать ни то, ни другое из-за ограничений.

Змеи и кофе
источник
6
Привет из будущего! nodeList имеет forEach начиная с ES6 .
Blaise

Ответы:

94

В NodeList теперь есть forEach () во всех основных браузерах.

См. NodeList forEach () в MDN .

Оригинальный ответ

Ни один из этих ответов не объясняет, почему NodeList не наследуется от Array, что позволяет ему иметь forEachи все остальное.

Ответ можно найти в этой ветке обсуждения . Короче говоря, он ломает сеть:

Проблема заключалась в коде, который неверно предполагал, что instanceof означает, что экземпляр был массивом в сочетании с Array.prototype.concat.

В библиотеке закрытия Google была ошибка, из-за которой почти все приложения Google прекращали работу. Библиотека была обновлена, как только это было обнаружено, но все еще может быть код, который делает такое же неверное предположение в сочетании с concat.

То есть какой-то код делал что-то вроде

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

Однако concat"настоящие" массивы (не instanceof Array) будут обрабатываться иначе, чем другие объекты:

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

это означает, что приведенный выше код сломался, когда xбыл NodeList, потому что до того, как он пошел по doSomethingElseWith(x)пути, тогда как после он пошел по otherArray.concat(x)пути, что сделало что-то странное, поскольку xне было настоящим массивом.

Некоторое время предлагалось создать Elementsкласс, являющийся реальным подклассом Array, который будет использоваться как «новый NodeList». Однако это было удалено из стандарта DOM , по крайней мере, на данный момент, поскольку реализовать его еще не представлялось возможным по ряду технических и связанных со спецификациями причин.

Доменик
источник
11
Мне кажется, это плохой звонок. Серьезно, я думаю, что это правильное решение время от времени ломать что-то, особенно если это означает, что у нас есть нормальные API на будущее. Кроме того, это не похоже на то, что Интернет даже близок к тому, чтобы быть стабильной платформой, люди привыкли к тому, что их двухлетний javascript больше не работает, как ожидалось ..
JoyalToTheWorld
3
«Разрушает сеть»! = «Разрывает материалы Google»
Мэтт,
Почему бы просто не позаимствовать метод forEach из Array.prototype? Например, вместо добавления массива в качестве прототипа просто сделайте this.forEach = Array.prototype.forEach в конструкторе или даже просто реализуйте forEach однозначно для NodeList?
PopKernel
1
Заметка; это обновление, NodeList now has forEach() in all major browsersпохоже, подразумевает, что IE не является основным браузером. Надеюсь, это правда для некоторых людей, но не для меня (пока).
Грэм П. Хит
58

Ты можешь сделать

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );
Акун
источник
3
Array.prototype.forEach.callможно сократить до[].forEach.call
CodeBrauer 02
7
@CodeBrauer: это не просто сокращение Array.prototype.forEach.call, это создание пустого массива и использование его forEachметода.
Пол Д. Уэйт
34

Вы можете рассмотреть возможность создания нового массива узлов.

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

Примечание. Это просто список / массив ссылок на узлы, которые мы здесь создаем, без повторяющихся узлов.

  nodes[0] === nodeList[0] // will be true
сбр
источник
22
Или просто сделай Array.prototype.forEach.call(nodeList, fun).
akuhn
Я хотел бы также предложить альясинг функции Foreach такую , что: var forEach = Array.prototype.forEach.call(nodeList, callback);. Теперь вы можете просто позвонитьforEach(nodeList, callback);
Эндрю Крэсуэлл
19

Никогда не говори никогда, сейчас 2016 год, и NodeListобъект реализовал forEachметод в последней версии Chrome (v52.0.2743.116).

Слишком рано использовать его в производстве, поскольку другие браузеры еще не поддерживают это (проверено FF 49), но я предполагаю, что скоро это будет стандартизировано.

майоман
источник
2
Opera также поддерживает его, и поддержка будет добавлена ​​в Firefox версии 50, выпуск которой запланирован на 15/11/16.
Shaggy
1
Несмотря на то, что он реализован, он не является частью какого-либо стандарта. Лучше всего сделать то, Array.prototype.slice.call(nodelist).forEach(…)что стандартно и работает в старых браузерах.
Nate
17

Короче говоря, реализация этого метода - конфликт дизайна.

Из MDN:

Почему я не могу использовать forEach или сопоставить в NodeList?

NodeList используются очень похоже на массивы, и было бы заманчиво использовать для них методы Array.prototype. Однако это невозможно.

В JavaScript есть механизм наследования, основанный на прототипах. Экземпляры массивов наследуют методы массива (например, forEach или map), потому что их цепочка прототипов выглядит следующим образом:

myArray --> Array.prototype --> Object.prototype --> null (цепочку прототипов объекта можно получить, вызвав несколько раз Object.getPrototypeOf)

forEach, map и т.п. являются собственными свойствами объекта Array.prototype.

В отличие от массивов, цепочка прототипов NodeList выглядит следующим образом:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype содержит метод элемента, но ни один из методов Array.prototype, поэтому их нельзя использовать в NodeLists.

Источник: https://developer.mozilla.org/en-US/docs/DOM/NodeList (прокрутите вниз до раздела «Почему я не могу использовать forEach или сопоставить в NodeList?» )

Мэтт Ло
источник
8
Итак, если это список, почему он так устроен? Что им мешало сделать цепочку myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null:?
Игорь Пантович
14

Если вы хотите использовать forEach в NodeList, просто скопируйте эту функцию из Array:

NodeList.prototype.forEach = Array.prototype.forEach;

Вот и все, теперь вы можете использовать его так же, как и для Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});
AlexTR
источник
4
За исключением того, что изменение базовых классов не очень очевидно для среднего читателя. Другими словами, глубоко в каком-то коде вы должны помнить каждую настройку, которую вы вносите в базовые объекты браузера. Полагаться на документацию MDN больше не полезно, потому что поведение объектов изменилось по сравнению с нормой. Лучше явно применить прототип во время вызова, чтобы читатель мог легко понять, что forEach - это заимствованная идея, а не что-то, что является частью определения языка. См. Ответ @akuhn выше.
Sukima
@Sukima заблуждается из-за неправильных предпосылок. В этом конкретном случае данный подход исправляет поведение NodeList в соответствии с ожиданиями разработчика. Это наиболее правильный способ исправить проблему с системным классом. (NodeList кажется незаконченным и должен быть исправлен в будущих версиях языка.)
Даниэль Гармошка
1
теперь, когда существует NodeList.forEach, это становится забавно простым полифилом!
Дэймон
7

В ES2015 теперь вы можете использовать forEachметод для nodeList.

document.querySelectorAll('abbr').forEach( el => console.log(el));

См. Ссылку MDN

Однако, если вы хотите использовать коллекции HTML или другие объекты, подобные массиву, в es2015 вы можете использовать Array.from()метод. Этот метод принимает подобный массиву или повторяемый объект (включая nodeList, HTML-коллекции, строки и т. Д.) И возвращает новый экземпляр Array. Вы можете использовать это так:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

В качестве Array.from() метод является изменяемым, вы можете использовать его в коде es5 следующим образом

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

Подробнее см. странице MDN .

Проверить текущую поддержку браузера .

ИЛИ

Другой способ es2015 - использовать оператор спреда.

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

Оператор спреда MDN

Оператор распространения - Поддержка браузера

Мишель Танвир Хабиб
источник
2

Мое решение:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
Бакос Бенце
источник
1
Часто не рекомендуется расширять функциональность DOM с помощью прототипов, особенно в старых версиях IE ( статья ).
KFE
0

NodeList является частью DOM API. Посмотрите на привязки ECMAScript, которые также применимы к JavaScript. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . NodeList и свойство длины только для чтения и функция элемента (индекса) для возврата узла.

Ответ: вам нужно повторить. Альтернативы нет. Foreach работать не будет. Я работаю с привязками Java DOM API и имею ту же проблему.

случайность
источник
Но есть ли конкретная причина, по которой его не следует реализовывать? И jQuery, и Dojo реализовали его в своих собственных библиотеках
Snakes and Coffee
2
но как это конфликт дизайна?
Snakes and Coffee
0

Проверьте MDN на предмет спецификации NodeList.forEach .

NodeList.forEach(function(item, index, nodeList) {
    // code block here
});

В IE используйте ответ Akuhn :

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
VesperX
источник