Что делает [].инструкция foreach.call () делать в JavaScript?

Я смотрел на некоторые фрагменты кода, и я нашел несколько элементов, вызывающих функцию над списком узлов с forEach, примененным к пустому массиву.

например, у меня есть что-то вроде:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

но я не могу понять, как это работает. Может ли кто-нибудь объяснить мне поведение пустого массива перед forEach и как call работает?

9 ответов


[] - это массив.
Этот массив вообще не используется.

он помещается на страницу, потому что использование массива дает вам доступ к прототипам массива, например .forEach.

это просто быстрее, чем набирать текст Array.prototype.forEach.call(...);

далее forEach - это функция, которая принимает функцию в качестве входа...

[1,2,3].forEach(function (num) { console.log(num); });

...и для каждого элемента this (где this похож на массив, в том, что он имеет length и вы можете получить доступ к его части как this[1]) пройдет три вещи:

  1. элемент в массиве
  2. индекс элемента (третий элемент будет проходить 2)
  3. ссылка на массив

и наконец, .call - это прототип, который имеет функции (это функция, которая вызывается другими функциями).
.call возьмет свой первый аргумент и заменит this внутри регулярной функции с тем, что вы прошли call, as первый аргумент (undefined или null использовать window в повседневной JS, или будет то, что вы прошли, если бы в "строгом режиме"). Остальные аргументы будут переданы исходной функции.

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
    console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

таким образом, вы создаете быстрый способ вызова , а ты this из пустого массива в список всех <a> теги, и для каждого <a> в порядке, вы вызываете функцию предоставлена.

редактировать

Логическое Заключение / Очистка

ниже есть ссылка на статью, предлагающую нам отказаться от попыток функционального программирования и придерживаться ручного, встроенного цикла каждый раз, потому что это решение Хак-иш и неприглядно.

я бы сказал, что во время .forEach менее полезно, чем его коллеги, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), он по-прежнему служит целям, когда все, что вы действительно хотите сделать, это изменить снаружи мир (не массив), n-раз, имея доступ либо к arr[i] или i.

Итак, как мы справляемся с неравенством, поскольку девиз явно талантливый и знающий парень, и я хотел бы представить, что я знаю, что я делаю/куда я иду (время от времени... ...в других случаях это head-first learning)?

ответ на самом деле довольно прост, и что-то дядя Боб и Сэр Крокфорд будут оба facepalm, из-за надзор:

его убрать.

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

теперь, если вы спрашиваете, нужно ли вам это делать самому, ответ вполне может быть "нет"...
Это точно сделано... ...каждый(?) библиотека с функциями более высокого порядка в эти дни.
Если вы используете lodash или подчеркивание или даже jQuery, все они будут иметь способ взять набор элементов и выполнить действие n-раз.
Если вы не используете такой вещь, тогда, конечно, пишите свою.

lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
  var others = lib.array(arguments, 1);
  return others.reduce(appendKeys, subject);
};

обновление для ES6 (ES2015) и за его пределами

не только slice( )/array( )/etc вспомогательный метод собирается сделать жизнь проще для людей, которые хотят использовать списки так же, как они используют массивы (как они должны), но для людей, у которых есть роскошь работать в браузерах ES6+ относительно близкого будущего или "транспилирования" в Babel сегодня, у вас есть встроенные языковые функции, которые делают этот тип вещи ненужный.

function countArgs (...allArgs) {
  return allArgs.length;
}

function logArgs (...allArgs) {
  return allArgs.forEach(arg => console.log(arg));
}

function extend (subject, ...others) { /* return ... */ }


var nodeArray = [ ...nodeList1, ...nodeList2 ];

супер-чистый и очень полезный.
Найдите остальное и распространение операторы; попробуйте их на сайте BabelJS; если ваш технический стек в порядке, используйте их в производстве с Babel и шагом сборки.


нет веской причины не использовать преобразование из не-массива в массив... ...просто не порть свой код, ничего не делая. но вставить ту же уродливую линию, везде.


на querySelectorAll метод возвращает NodeList, который похож на массив, но это не совсем массив. Поэтому у него нет forEach метод (какие объекты массива наследуются через Array.prototype).

С NodeList похож на массив, методы массива будут работать на нем, поэтому с помощью [].forEach.call вы призываете Array.prototype.forEach метод в контексте NodeList, как будто вы были в состоянии просто сделать yourNodeList.forEach(/*...*/).

обратите внимание, что пустой массив literal - это просто ярлык для расширенной версии, которую вы, вероятно, тоже увидите довольно часто:

Array.prototype.forEach.call(/*...*/);

другие ответы очень хорошо объяснили этот код, поэтому я просто добавлю предложение.

Это хороший пример кода, который должен быть приведен к простоте и ясности. Вместо использования [].forEach.call() или Array.prototype.forEach.call() каждый раз, когда вы это делаете, сделайте из него простую функцию:

function forEach( list, callback ) {
    Array.prototype.forEach.call( list, callback );
}

теперь вы можете вызвать эту функцию вместо более сложного и неясного кода:

forEach( document.querySelectorAll('a'), function( el ) {
   // whatever with the current node
});

его можно улучшить, используя

Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {

});

что это document.querySelectorAll('a') возвращает объект, похожий на массив, но не наследуется от Array тип. Поэтому мы называем forEach метод Array.prototype объект с контекстом в качестве значения, возвращаемого document.querySelectorAll('a')


пустой массив имеет свойство forEach в его прототипе, который является объектом функции. (Пустой массив-это простой способ получить ссылку на элемент вот и все Array объекты.) Функциональные объекты, в свою очередь, имеют call свойство, которое также является функцией. При вызове функции call function, он запускает функцию с заданными аргументами. Первым аргументом становится this в вызываемой функции.

вы можете найти документацию call функции здесь. Документация forEach is здесь.


просто добавьте одну строку:

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

и вуаля!

document.querySelectorAll('a').forEach(function(el) {
  // whatever with the current node
});

наслаждайтесь :-)

предупреждение: NodeList является глобальным классом. Не используйте эту рекомендацию, если вы пишете публичную библиотеку. Однако это очень удобный способ повышения самоэффективности при работе на веб-сайте или узле.приложение на JS.


просто быстрое и грязное решение, которое я всегда использую. Я бы не стал трогать прототипы, просто хорошая практика. Конечно, есть много способов сделать это лучше, но вы понимаете идею.

const forEach = (array, callback) => {
  if (!array || !array.length || !callback) return
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach(document.querySelectorAll('.a-class'), (item, index) => {
  console.log(`Item: ${item}, index: ${index}`);
});

[] всегда возвращает новый массив, это эквивалентно new Array() но гарантированно возвращает массив, потому что Array может быть перезаписан пользователем, тогда как [] не может. Так что это безопасный способ получить прототип Array, тогда как описано, call используется для выполнения функции в arraylike nodelist (this).

вызывает функцию с заданным значением и аргументами индивидуально. mdn


Norguard объяснил что [].forEach.call() и Джеймс Аллардис почему мы делаем это: потому что querySelectorAll возвращает NodeList у этого нет метода forEach...

Если у вас нет современного браузера, такого как Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Если нет, вы можете добавить Polyfill:

if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}