Чому у nodelist немає 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 у поєднанні з Array.prototype.concat.

У бібліотеці закриття Google виникла помилка, яка спричинила збій майже всіх програм Google через це. Бібліотека була оновлена, як тільки це було знайдено, але все ще може бути код, який робить те саме неправильне припущення в поєднанні з concat.

Тобто якийсь код робив щось подібне

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

Однак ми concatбудемо обробляти "реальні" масиви (не екземпляри масиву) інакше, ніж інші об'єкти:

[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 на майбутнє. Крім того, це не схоже на те, що Інтернет навіть близький до того, щоб бути стабільною платформою, люди звикли, що їх 2-річний javascript вже не функціонує, як очікувалося ..
JoyalToTheWorld

3
"Breaks the web"! = "Ламає речі Google"
Метт,

Чому б просто не запозичити метод forEach у Array.prototype? Наприклад, замість того, щоб додавати Array як прототип, просто зробіть це.forEach = Array.prototype.forEach у конструкторі або навіть просто реалізуйте forEach унікально для NodeList?
PopKernel

1
Примітка; це оновлення, NodeList now has forEach() in all major browsersмабуть, означає, що IE не є основним браузером. Сподіваємось, це правда для деяких людей, але для мене це ще не так (поки).
Graham P Heath

58

Ви можете зробити

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

    // Your code here.

} );

3
Array.prototype.forEach.callможна скоротити до[].forEach.call
CodeBrauer

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);
Andrew Craswell

19

Ніколи не кажи ніколи, це 2016 рік, і NodeListоб’єкт реалізував forEachметод в останньому хромі (v52.0.2743.116).

Поки що рано використовувати його у виробництві, оскільки інші браузери цього ще не підтримують (протестований FF 49), але я гадаю, що це скоро буде стандартизовано.


2
Opera також підтримує це, і підтримка буде додана у v50 Firefox, запланований до випуску 15/11/16.
Кудлатий

1
Незважаючи на те, що він реалізований, він не є частиною жодного стандарту. Все-таки найкраще робити Array.prototype.slice.call(nodelist).forEach(…)стандартний варіант, який працює у старих браузерах.
Нейт

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 містить метод item, але жоден з методів 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 вводить в оману неправильними приміщеннями. У цьому конкретному випадку даний підхід виправляє NodeList, щоб він поводився так, як очікував розробник. Це найбільш правильний спосіб виправити проблему класу системи. (NodeList здається незавершеним і його слід виправити в майбутніх версіях мови.)
Даніель Гармошка,

1
тепер, коли NodeList.forEach існує, це стає весело простим поліфілом!
Деймон

7

У ES2015 тепер ви можете використовувати forEachметод для nodeList.

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

Див. Посилання MDN

Однак якщо ви хочете використовувати HTML-колекції або інші масивоподібні об'єкти, у es2015 ви можете використовувати Array.from()метод. Цей метод бере об'єкт, схожий на масив, або ітерабельний (включаючи nodeList, колекції HTML, рядки тощо) і повертає новий екземпляр масиву. Ви можете використовувати його так:

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 є частиною API DOM. Подивіться на прив’язки 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.