Почему в нодлисте нет 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';
});

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.