ForEach() vs Array.prototype.forEach.call()

Я просто просматривал образцы кода демонов Electron API, когда внезапно появилось дикое выражение, совершенно чуждое мне:

const links = document.querySelectorAll('a[href]');

Array.prototype.forEach.call(links, function (link) {
    // WWIII here
})

Я определенно понимаю, что делает этот кусок кода, но я привык к синтаксису:

links.forEach(function (links) {});

Так в чем же разница между этими двумя? Я уже читал различные темы StackOverflow по этой теме, но они либо неоднозначны, либо вообще не отвечают на вопрос. Некоторые сказали, что у него есть что-то, что связано с массивными коллекциями, которые не могут быть итерационными .forEach() в отличие от Array.prototype.forEach.call(). Это единственное преимущество слишком утомительной и длинной версии?

Спасибо заранее!

Ответ 1

"методы класса" в JavaScript являются фактически функциями, определенными в prototype. Это означает, что даже если объект не наследуется от прототипа Array, вы можете вызвать на нем методы Array, если это следует за структурой массива (т.е. это объект с свойством length и свойствами, индексированными целыми числами). Однако объект не ссылается на Array.prototype, поэтому вам нужно явно выбрать Array.prototype как объект, в котором находится метод.

Функция document.querySelectorAll возвращает NodeList, который не является ни Array и не наследуется от прототипа Array. Однако, поскольку NodeList имеет аналогичную внутреннюю структуру для Array, вы все равно можете использовать функцию forEach. Но поскольку NodeList не наследуется от прототипа Array, попытка использовать .forEach на NodeList вызовет ошибку (это не совсем так - см. Примечание в конце моего ответа). По этой причине вам нужно явно указать, что вы вызываете метод из Array.prototype на NodeList, и это делается с помощью .call метод от Function.prototype.

Вкратце:

Array.prototype.forEach.call(links, function(link) { /* something */ })

означает:

Возьмите функцию forEach из Array.prototype и назовите ее на links, которая является объектом не Array, а в качестве аргумента используется некоторая функция.

Обратите внимание, что в последних версиях браузеров прототип NodeList предоставляет a forEach метод, который работает так же, как Array один, поэтому пример из API электрона, вероятно, использует версию Array для совместимости со старыми версиями. Если у вас есть веб-приложение и вы заботитесь только о поддержке современных версий Chrome и Firefox, вы можете просто позвонить forEach на ваш NodeList. Фактически, поскольку Электрон обновляется примерно через 2 недели после каждого обновления Chrome, безопасно использовать NodeList.prototype.forEach в Electron.:)

Ответ 3

Это интересный вопрос. Полгода назад я бы сказал, что link.forEach не о более коротком синтаксисе, но на самом деле он не должен работать. Затем я хотел бы объяснить, что это значит, что многие методы массива преднамеренно генерируют, а это означает, что их внутренняя реализация учитывает только числовые индексы и свойство length объекта this, но не заботится о том, что это экземпляр массива. В основном, что сказал @Pedro Кастильо в своем ответе.

Однако теперь я скажу, что в эти дни вечнозеленые браузеры (за исключением IE11, Edge, по состоянию на апрель 2017 года) уже реализованы NodeList.prototype.forEach, поэтому вам больше не нужно использовать .call hack или Array.from, чтобы просто перебрать NodeList с помощью forEach.

Итак, мое резюме: если вам не нужно поддерживать IE, используйте NodeList.prototype.forEach, а не Array.prototype.forEach. Это может быть одно и то же внутренне, но более чистое концептуально. Если вам нужно поддерживать IE, и вы не хотите включать еще один pollyfill, используйте Array.prototype.call или лучше Array.from.