Почему document.querySelectorAll возвращает StaticNodeList, а не реальный массив?

103

Меня беспокоит то, что я не могу просто сделать это document.querySelectorAll(...).map(...)даже в Firefox 3.6, и я все еще не могу найти ответ, поэтому я подумал, что я отправлю кросс-пост на SO вопрос из этого блога:

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

Кто-нибудь знает техническую причину, по которой вы не получаете массив? Или почему StaticNodeList не наследует от массива таким образом , что вы могли бы использовать map, concatи т.д.?

(Кстати, если вам нужна только одна функция, вы можете сделать что-то вроде NodeList.prototype.map = Array.prototype.map;... но опять же, почему эта функция (намеренно?) Заблокирована в первую очередь?)

Кев
источник
3
Фактически также getElementsByTagName возвращает не массив, а коллекцию, и если вы хотите использовать ее как массив (с такими методами, как concat и т. Д.), Вы должны преобразовать такую ​​коллекцию в массив, выполнив цикл и скопировав каждый элемент коллекцию в массив. На это никто никогда не жаловался.
Марко Демайо,

Ответы:

82

Я считаю, что это философское решение W3C. Дизайн W3C DOM [spec] полностью ортогонален дизайну JavaScript, поскольку DOM должен быть нейтральным к платформе и языку.

Такие решения, как « getElementsByFoo()возвращает упорядоченный NodeList» или « querySelectorAll()возвращает StaticNodeList», в значительной степени преднамеренные, поэтому реализациям не нужно беспокоиться о выравнивании структуры возвращаемых данных на основе языковых реализаций (например .map, доступность в массивах в JavaScript и Ruby, но нет в списках в C #).

W3C стремятся к низкому уровню: они скажут, что a NodeListдолжно содержать свойство readonly .lengthтипа unsigned long, потому что они считают, что каждая реализация может хотя бы поддерживать это , но они не будут явно говорить, что []оператор индекса должен быть перегружен для поддержки получения позиционных элементов, потому что они не хотят заглушать какой-нибудь бедный маленький язык, который хотят реализовать, getElementsByFoo()но не могут поддерживать перегрузку операторов. Это преобладающая философия, присутствующая в большей части спецификации.

Джон Ресиг озвучил аналогичный вариант, как и ваш, к которому он добавляет :

Мой аргумент не столько в том, что NodeIteratorон не очень похож на DOM, сколько в том , что он не очень похож на JavaScript. Он не использует преимущества функций языка JavaScript и не использует их в меру своих возможностей ...

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

Crescent Fresh
источник
Спасибо, это помогает мне разобраться в ситуации.
Кев
@Kev: Я видел ваш комментарий на странице той статьи в блоге, в котором вы спрашивали, как бы вы преобразовали StaticNodeListобъект в массив. Я бы поддержал ответ @mck89 как способ преобразования a NodeList/ StaticNodeListв собственный массив, но это не сработает в IE (8 obv) с ошибкой JScript, поскольку эти объекты размещены / "специальные".
Crescent Fresh
Правда, поэтому я проголосовал за него. Но кто-то еще отменил мой +1. Что вы имеете в виду под "размещенным / специальным"?
Кев
1
@Kev: размещенные переменные - это любые переменные, предоставляемые "хост-средой" (например, веб-браузером). Например document, windowи т. Д. IE часто реализует эти «специально» (например, как объекты COM), которые иногда не соответствуют нормальному использованию, маленькими и тонкими способами, такими как Array.prototype.slice.callбомбардировка при задании StaticNodeList;)
Crescent Fresh
201

Вы можете использовать оператор распространения 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(...);
mck89
источник
3
На самом деле он не обновляет результат при обновлении DOM - следовательно, «статический». Вы должны вручную вызвать qSA еще раз, чтобы обновить результат. +1 за sliceлинию хоть.
Кев
1
Да, как сказал Кев: набор результатов qSA является статическим, набор результатов getElementsByTagName () - динамическим.
joonas.fi
IE8 поддерживает querySelectorAll () только в стандартном режиме
mbokil 02
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-терминалу подсказку о том, как долго будет работать массив. Это могло бы позволить ему заранее выделить это количество места, что потенциально могло бы привести к ускорению, так как некоторых перераспределений памяти можно было бы избежать по мере роста массива. Я не знаю, действительно ли это помогает в современных браузерах ... Я подозреваю, что это не заметно.
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.

  • Такой подход исключил бы возможное использование подметодов.

Я бы ни в коем случае не рекомендовал это, если вам честно не наплевать [вы знаете, что].

vsync
источник