Что делает [].forEach.call() в JavaScript?

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

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

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

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

Ответ 1

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

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

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

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

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

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

  • элемент в массиве
  • индекс элемента (третий элемент пройдет 2)
  • ссылка на массив

Наконец, .call является прототипом, функции которого имеют (это функция, вызываемая на другие функции).
.call примет свой первый аргумент и заменит this внутри регулярной функции тем, что вы передали call, поскольку первый аргумент (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"

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

ИЗМЕНИТЬ

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

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

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

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

Ответ на самом деле довольно прост, и что-то дядя Боб и сэр Крокфорд будут оба 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) и Beyond

Не только вспомогательный метод 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 ];

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


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

Ответ 2

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

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

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

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

Ответ 3

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

Это хороший пример кода, который нужно реорганизовать для простоты и ясности. Вместо использования [].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
});

Ответ 4

Его можно лучше написать с помощью

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

});

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

Ответ 5

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

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

Ответ 6

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

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

И вуаля!

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

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

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

Ответ 7

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

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

Ответ 8

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);
        }
    };
}