Що робить [] .forEach.call () у JavaScript?


135

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

Наприклад, у мене є щось на кшталт:

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

але я не можу зрозуміти, як це працює. Хтось може пояснити мені поведінку порожнього масиву перед forEach і як це callпрацює?

Відповіді:


203

[]- це масив.
Цей масив взагалі не використовується.

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

Це просто швидше, ніж набирати текст Array.prototype.forEach.call(...);

Далі, forEachце функція, яка приймає функцію як вхід ...

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

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

  1. елемент у масиві
  2. індекс елемента (третій елемент пройшов би 2)
  3. посилання на масив

Нарешті, .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>замовлення ви викликаєте надану функцію.

EDIT

Логічний висновок / очищення

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

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

Тож як ми маємо справу з невідповідністю, оскільки Мотто - це талановитий та знаючий хлопець, і я хотів би уявити, що я знаю, що роблю / куди йду (зараз і далі ... ... інше раз це головне навчання)?

Відповідь насправді досить проста, і те, що дядько Боб і сер Крокфорд, обидва було б обличчям долонею, через нагляд:

очистити його .

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 + браузерах порівняно-близько Майбутнє або "перенесення" в Вавилон сьогодні, ви маєте вбудовані мовні функції, які роблять цей предмет непотрібним.

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 та кроком складання.


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


Тепер я трохи заплутався, якщо б це зробив: console.log (this); Я завжди отримаю об’єкт вікна І я подумав, що .call змінює значення цього
Мухаммед Салех

.call це змінити значення this, тому ви використовуєте callз Foreach. Якщо forEach виглядає так forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }, змінюючи this, кажучи forEach.call( newArrLike ), це означає, що він буде використовувати цей новий об'єкт як this.
Норгуард

2
@MuhammadSaleh .callне змінює значення thisвнутрішньої частини того, що ви переходите forEach, .callзмінює значення this внутрішньої частини forEach . Якщо вас бентежить питання про те, чому thisне передається forEachфункція, яку ви хотіли викликати, вам слід шукати поведінку thisв JavaScript. В якості доповнення, може бути застосовано тільки тут (і карта / фільтр / зменшення), є другий аргумент forEach, який встановлює thisвсередині функції arr.forEach(fn, thisInFn)або [].forEach.call(arrLike, fn, thisInFn);або просто використовувати .bind; arr.forEach(fn.bind(thisInFn));
Норгуард

1
@thetrystero саме в цьому справа. []є лише масив, щоб ви могли .callвикористовувати .forEachметод і змінити thisнабір, над яким ви хочете працювати. .callвиглядає так: function (thisArg, a, b, c) { var method = this; method(a, b, c); }за винятком того, що внутрішня чорна магія повинна бути встановлена thisвсередині, methodщо дорівнює тому, що ви пройшли thisArg.
Норгуард

1
так коротше ... Array.from ()?
zakius

53

querySelectorAllМетод повертає NodeList, який схожий на масив, але це не зовсім масив. Тому у нього немає forEachметоду (який об'єкти масиву успадковуються через Array.prototype).

Оскільки a NodeListсхожий на масив, методи масиву фактично працюватимуть на ньому, тому, використовуючи, [].forEach.callви викликаєте Array.prototype.forEachметод у контексті NodeList, як якщо б вам це вдалося просто зробити yourNodeList.forEach(/*...*/).

Зауважте, що літерал порожнього масиву - це лише ярлик до розгорнутої версії, який ви, мабуть, бачите також досить часто:

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

7
Таким чином, щоб уточнити, чи немає ніякої переваги в використанні [].forEach.call(['a','b'], cb)більш ніж ['a','b'].forEach(cb)в повсякденних додатках з використанням стандартних масивів, тільки при спробі перебрати масив подібні структури , які не мають forEachна своєму власному прототипі? Це правильно?
Метт Флетчер

2
@MattFletcher: Так, це правильно. Обидва будуть працювати, але чому надскладні речі? Просто викличте метод безпосередньо на самому масиві.
James Allardice

Класно, дякую. І я не знаю, чому, можливо, саме для вуличних кредитів, що справляє враження на бабусь в ALDI та подібних. Я дотримуюся [].forEach():)
Метт Флетчер

var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) працює добре
daslicht

@daslicht не в старих версіях IE! (Що робити , проте, підтримка forEachмасивів, але не Nodelist об'єкти)
Djave

21

Інші відповіді дуже добре пояснили цей код, тому я просто додам пропозицію.

Це хороший приклад коду, який слід відновити для простоти та чіткості. Замість використання [].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
});

5

Це можна краще записати, використовуючи

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

});

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


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

Це в основному те саме, що:

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

2

Хочете оновити це старе питання:

Причина використання [].foreach.call()для перегляду елементів у сучасних браузерах здебільшого закінчена. Ми можемо використовувати document.querySelectorAll("a").foreach()безпосередньо.

Об'єкти NodeList - це колекції вузлів, які зазвичай повертаються такими властивостями, як Node.childNodes, і такими методами, як document.querySelectorAll ().

Хоча NodeList не є масивом, його можна повторити за допомогою forEach () . Він також може бути перетворений у реальний масив за допомогою Array.from ().

Однак деякі старі браузери не реалізували NodeList.forEach () і Array.from (). Це можна обійти за допомогою Array.prototype.forEach () - див. Приклад цього документа.


1

Порожній масив forEachу своєму прототипі має властивість, яка є об’єктом Функції. (Порожній масив - це просто простий спосіб отримати посилання на forEachфункцію, яку мають усі Arrayоб'єкти.) Об'єкти функцій, у свою чергу, мають callвластивість, яка також є функцією. Коли ви викликаєте функцію callфункції, вона запускає функцію із заданими аргументами. Перший аргумент стає thisв викликаній функції.

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


1

Просто додайте один рядок:

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

І вуаля!

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

Насолоджуйтесь :—)

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


1

Просто швидке і брудне рішення, яке я завжди закінчую використовувати. Я б не торкався прототипів, так само як хороша практика. Звичайно, існує маса способів зробити це краще, але ви отримуєте ідею.

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

0

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

Викликає функцію із заданим цим значенням та аргументами, що подаються окремо. мдн


Хоча []насправді завжди буде повертати масив, в той час як його window.Arrayможна буде перезаписати чим завгодно (у всіх старих браузерах), так само теж можна window.Array.prototype.forEachперезаписати чим завгодно. У будь-якому випадку немає гарантій безпеки в браузерах, які не підтримують геттери / сетери або герметизацію / заморожування об'єктів (заморожування викликає власні проблеми з розширюваністю).
Норгуард

Ви можете змінити відповідь , щоб додати додаткову інформацію про можливості перезапису prototypeабо це методи: forEach.
Xotic750

0

Норгуард пояснив, що [].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);
        }
    };
}

0

На цій сторінці багато корисної інформації (див. Відповідь + відповідь + коментар ), але нещодавно у мене було те саме питання, що і в ОП, і для того, щоб отримати повну картину, знадобилося трохи викопати. Отже, ось коротка версія:

Мета полягає у використанні Arrayметодів у масиві NodeList, який не має цих методів.

Старіший зразок кооперував методи Array за допомогою Function.call()і використовував матрицю literal ( []), а не Array.prototypeтому, що вона була коротшою для введення:

[].forEach.call(document.querySelectorAll('a'), a => {})

Більш новий шаблон (після ECMAScript 2015) полягає у використанні Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.