Чому document.querySelectorAll повертає StaticNodeList, а не реальний масив?


103

Мене клопотають, що я не можу просто зробити document.querySelectorAll(...).map(...)навіть у Firefox 3.6, і я все ще не можу знайти відповідь, тому я подумав, що перехресне повідомлення на ТАК на це запитання з цього блогу:

http://blowery.org/2008/08/29/yay-for-queryselectorall-boo-for-staticnodelist/

Хтось знає технічну причину, чому ви не отримуєте масив? Або чому StaticNodeList не буде наслідувати від масиву таким чином , що ви могли б використовувати map, concatі т.д.?

(До речі, якщо це лише одна функція, яку ви хочете, ви можете виконати щось на зразок NodeList.prototype.map = Array.prototype.map;... але знову ж таки, чому ця функціональність (навмисно?) Заблокована в першу чергу?)


3
Насправді getElementsByTagName не повертає масив, а колекцію, і якщо ви хочете використовувати його як масив (з такими методами, як concat тощо), ви повинні перетворити таку колекцію в масив, зробивши цикл і скопіювавши кожен елемент колекція в масив. На це ніхто ніколи не скаржився.
Марко Демайо

Відповіді:


81

Я вважаю, що це філософське рішення W3C. Конструкція W3C DOM [специфікація] цілком ортогональні до конструкції JavaScript, як РОМ означає бути платформи і мови нейтральна.

Такі рішення, як " getElementsByFoo()повертає впорядкований NodeList" або " querySelectorAll()повертає StaticNodeList", є дуже навмисними, так що реалізаціям не потрібно турбуватися про вирівнювання повернутої структури даних на основі залежних від мови реалізацій (наприклад, .mapвони доступні для масивів у JavaScript та Ruby, але не в Списках на C #).

W3C мета низький: вони говорять, що NodeListповинно містити тільки для читання .lengthвластивості типу непідписаного довгої , тому що вони вважають , що кожна реалізація може принаймні підтримка , що , але вони не будуть говорити явно , що []оператор індексу повинен бути перевантажені для підтримки отримання позиційних елементів, тому що вони не хочуть стиміювати якийсь поганий маленький мову, який йде разом із цим, який хоче реалізувати, getElementsByFoo()але не може підтримувати перевантаження оператора. Це поширена філософія, яка присутня на більшій частині специфікації.

Джон Резіг озвучив подібний варіант, як ваш, до якого додає :

Мій аргумент не так вже й багато, що NodeIteratorне дуже DOM - це те, що він не дуже JavaScript. Він не користується функціями, наявними в мові JavaScript, і використовує їх якомога краще ...

Я дещо співпереживаю. Якби DOM був написаний спеціально з функціями JavaScript на увазі, це було б набагато менш незручно і більш інтуїтивно. У той же час я розумію дизайнерські рішення W3C.


Дякую, що допомагає мені зрозуміти ситуацію.
Кев

@Kev: Я побачив ваш коментар на цій статті статті блогу, що ставить питання про те, як ви б перейшли до перетворення StaticNodeListмасиву в масив. Я б схвалив відповідь @ mck89 як спосіб перетворення а NodeList/ StaticNodeListв нативний масив, але це не вдасться в IE (8 обв.) З помилкою JScript, оскільки ці об'єкти розміщені / "спеціальні".
Півмісяць Свіжий

Щоправда, тому я його висловив. Хтось ще скасував мій +1. Що ви маєте на увазі під влаштованим / спеціальним?
Кев

1
@Kev: розміщені змінні - це будь-які змінні, що надаються середовищем "хост" (наприклад, веб-браузер). Наприклад document, windowі т. Д. IE часто реалізує ці "спеціально" (наприклад, об'єкти COM), які іноді не відповідають нормальному використанню, невеликими і тонкими способами, такими як Array.prototype.slice.callбомбардування, коли їм дано StaticNodeList;)
Crescent Fresh

200

Ви можете використовувати оператор розкидання ES2015 (ES6) :

[...document.querySelectorAll('div')]

перетворить StaticNodeList в масив елементів.

Ось приклад того, як ним користуватися.

[...document.querySelectorAll('div')].map(x => console.log(x.innerHTML))
<div>Text 1</div>
<div>Text 2</div>


24
Ще один спосіб - це використовувати Array.from () :Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
Михайло Бердишев

42

Я не знаю, чому він повертає список вузлів замість масиву, можливо, тому що, як getElementsByTagName, він оновить результат при оновленні DOM. У будь-якому випадку дуже простим методом перетворення результату в простий масив є:

Array.prototype.slice.call(document.querySelectorAll(...));

і тоді ви можете зробити:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);

3
Насправді він не оновлює результат під час оновлення DOM - отже, "статичний". Вам потрібно вручну зателефонувати в qSA, щоб оновити результат. sliceХоча +1 для рядка
Кев

1
Так, як сказав Кев: набір результатів qSA є статичним, набір результатів getElementsByTagName () є динамічним.
joonas.fi

IE8 підтримує лише querySelectorAll () у стандартному режимі
mbokil

13

Просто додати те, що сказав Півмісяць,

якщо це лише одна функція, яку ви хочете, ви можете зробити щось на зразок NodeList.prototype.map = Array.prototype.map

Не робіть цього! Це зовсім не гарантовано працює.

Жоден стандарт JavaScript або DOM / BOM не вказує на те, що NodeListфункція конструктора навіть існує як глобальна / windowвластивість, або що NodeListповернута особа querySelectorAllуспадковуватиме її, або що її прототип записується, або що функція Array.prototype.mapфактично буде працювати в NodeList.

NodeList дозволено бути "хост-об'єктом" (і є одним, в IE та деяких старих браузерах). Ці Arrayметоди визначаються як буде дозволено працювати на «рідний» об'єкт будь-якого в JavaScript , який надає числові і lengthвластивості, але вони не потрібні для роботи на об'єктах господарів (і в IE, вони не роблять).

Прикро, що ви не отримуєте всіх методів масиву у списках DOM (усі вони, не лише StaticNodeList), але немає надійного способу їх обходу. Вам доведеться конвертувати кожен список DOM, який ви повернетесь до масиву вручну:

Array.fromList= function(list) {
    var array= new Array(list.length);
    for (var i= 0, n= list.length; i<n; i++)
        array[i]= list[i];
    return array;
};

Array.fromList(element.childNodes).forEach(function() {
    ...
});

1
Стріляй, я про це не думав. Дякую!
Кев

Я згоден +1. Просто коментар, я думаю, що робити "var array = []" замість "var array = new Array (list.length)" зробить код ще коротшим. Але мені цікаво, якщо ви знаєте, що в цьому може виникнути проблема.
Марко Демайо

@MarcoDemaio: Ні, немає проблем. new Array(n)просто дає підказку JS terp про те, як довго масив буде закінчуватися. Це може дозволити йому заздалегідь виділити таку кількість простору, що потенційно може призвести до прискорення, оскільки деякі перерозподіли пам’яті можна буде уникнути у міру збільшення масиву. Я не знаю, чи насправді це допомагає в сучасних браузерах ... Я б не підозрював.
bobince

2
Тепер він реалізований в Array.from ()
Михайло Бердишев

2

Я думаю, ви можете просто зробити наступне

Array.prototype.map.call(document.querySelectorAll(...), function(...){...});

Це прекрасно працює для мене


0

Це варіант, який я хотів додати до кола інших можливостей, запропонованих тут іншими. Він призначений лише для інтелектуальної забави, і не рекомендується .


Для задоволення це ось спосіб "змусити" querySelectorAllстати на коліна і поклонитися вам:

Element.prototype.querySelectorAll = (function(QSA){
    return function(){
        return [...QSA.call(this, arguments[0])]
    }
})(Element.prototype.querySelectorAll);

Тепер відчуває себе добре переступити через цю функцію, показуючи, хто це начальник. Тепер я не знаю , що краще, створюючи абсолютно нову ім'я функції обгортки і потім все використовувати ваш код , що дивне ім'я (досить багато JQuery-стилю) або перевизначити функцію , як і вище один раз , так що інша частина коди буде по- , як і раніше мати можливість використовувати оригінальну назву методу DOM querySelectorAll.

  • Такий підхід дозволить виключити можливе використання підметодів

Я б не рекомендував це ні в якому разі, якщо тільки ви чесно не дасте [ви знаєте що].

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.