яке використання має метод javascript forEach (що карта не може зробити)?


90

Єдину різницю, яку я бачу в map і foreach, полягає в тому, що mapповертає масив, а forEachні. Однак я навіть не розумію останній рядок forEachметоду " func.call(scope, this[i], i, this);". Наприклад, не « this» і « scope» зі посиланням на той самий об'єкт і не є , this[i]і з iпосиланням на поточне значення в циклі?

Я помітив, що в іншому дописі хтось сказав: "Використовуйте, forEachколи ви хочете зробити щось на основі кожного елемента списку. Можливо, ви додаєте речі на сторінку, наприклад. По суті, це чудово, коли ви хочете" побічні ефекти ". Я не знаю, що мається на увазі під побічними ефектами.

Array.prototype.map = function(fnc) {
    var a = new Array(this.length);
    for (var i = 0; i < this.length; i++) {
        a[i] = fnc(this[i]);
    }
    return a;
}

Array.prototype.forEach = function(func, scope) { 
    scope = scope || this; 
    for (var i = 0, l = this.length; i < l; i++) {
        func.call(scope, this[i], i, this); 
    } 
}

Нарешті, чи існує реальне використання цих методів у javascript (оскільки ми не оновлюємо базу даних), крім маніпулювання такими числами:

alert([1,2,3,4].map(function(x){ return x + 1})); //this is the only example I ever see of map in javascript.

Дякую за будь-яку відповідь.


Як можна знайти визначення функції рідного JavaScript, як ви це зробили з mapта forEach? Все, що я отримую від Google - це специфікації використання та навчальні посібники.
WoodrowShigeru

Див. Також мовно-агностичний Чи існує різниця між foreach та map?
Бергі,

Відповіді:


94

Суттєва різниця між mapі forEachу вашому прикладі полягає в тому, що forEachоперує вихідними елементами масиву, тоді як mapявно повертає новий масив як результат.

Коли forEachви виконуєте певні дії з кожним елементом у вихідному масиві та, при необхідності, змінюєте його. forEachМетод запускає функцію , яку Ви надали для кожного елемента, але нічого не повертає ( undefined). З іншого боку, mapпроходить через масив, застосовує функцію до кожного елемента і видає результат як новий масив .

"Побічним ефектом" forEachє те, що початковий масив змінюється. "Без побічних ефектів" mapозначає, що при ідіоматичному використанні вихідні елементи масиву не змінюються; новий масив - це індивідуальне відображення кожного елемента в оригінальному масиві - перетворення відображення є вашою наданою функцією.

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


4
примітка з майбутнього: ця відповідь насправді нонсенс; .mapможе робити все, що .forEachможе (включаючи модифікуючі елементи), він просто повертає новий список, побудований з функції ітератора.
Тревіс Вебб

6
Відповідь на минуле: Я з повагою не погоджуюсь. .map()створює новий масив і не змінює оригінал. Ви дійсно могли б змінити масив, який сам по собі наноситься на карту, але це було б принаймні неідіоматичним, якщо не безглуздим. .forEach(), хоча подібний, застосовує свою функцію до кожного елемента, але завжди повертається undefined. Незважаючи на все вищесказане, OP також запитує про власні конкретні функції, додані до прототипу масиву; в минулому тут не було ES5.
Кен Редлер

4
Досі немає нічого, forEachщо може зробити те, з чим ви не могли б зробити map. Ви можете просто не дбати про повернене значення від, mapі у вас буде forEach. Різниця полягає в тому, що його дуже неефективно використовувати mapдля завдань, де ви не хочете створювати новий масив за результатами. Отже, для тих, кого ви використовуєте forEach.
тикати

2
@poke: ..і так само, ти можеш робити все, що тобі потрібно, із звичайним старим forциклом. Я не погоджуюся з тим, що існує якась різниця в "ефективності", але також я не думаю, що хтось сперечається, що один має якусь магію, інший не може якось досягти. Хоча насправді є одне, чого mapне можна зробити: не повертати масив. Там це значення в тому , JS виправдала очікування від інших мов, на поведінку функціональних фаворитів , як map, reduce, filterі т.д. Наприклад, ви могли б реалізувати з reduceRightвикористанням аналогічних будівельних блоків, але чому б не використовувати reduceRight?
Кен Редлер,

1
@ChinotoVokro, ваш приклад явно мутує вихідний масив, призначаючи b.val = b.val**2 всередині повернутого об'єкта . Це саме те, що ми говоримо, насправді можливо, але заплутане та неідіоматичне. Зазвичай ви просто повертаєте нове значення, а також не присвоюєте його початковому масиву:a=[{val:1},{val:2},{val:3},{val:4}]; a.map((b)=>{b.val**2}); JSON.stringify(a);
Ken Redler

66

Основна різниця між цими двома методами - концептуальна та стилістична: ви використовуєте, forEachколи хочете щось зробити з кожним елементом масиву або з кожним елементом (робити «з» - це те, що, на вашу думку, повідомлення, яке ви цитуєте, означало під «побічними ефектами») , тоді як ви використовуєте, mapколи хочете скопіювати та перетворити кожен елемент масиву (без зміни оригіналу).

Оскільки mapі forEachвикликає функцію для кожного елемента масиву, і ця функція визначається користувачем, ви майже нічого не можете зробити з одним, а не з іншим. Можна, хоч і потворно, використовувати mapдля модифікації масиву на місці та / або робити щось з елементами масиву:

var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.map(function(el) {
    el.val++; // modify element in-place
    alert(el.val); // do something with each element
});
// a now contains [{ val: 2 }, { val: 3 }, { val: 4 }]

але набагато чистіший та більш очевидний щодо вашого наміру використовувати forEach:

var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.forEach(function(el) { 
    el.val++;
    alert(el.val);
});

Особливо, якщо, як це зазвичай буває в реальному світі, elкорисно читається людиною змінна:

cats.forEach(function(cat) { 
    cat.meow(); // nicer than cats[x].meow()
});

Таким же чином ви можете легко forEachстворити новий масив:

var a = [1,2,3],
    b = [];
a.forEach(function(el) { 
    b.push(el+1); 
});
// b is now [2,3,4], a is unchanged

але він чистіший у використанні map :

var a = [1,2,3],
    b = a.map(function(el) { 
        return el+1; 
    });

Також зауважте, що, оскільки mapстворює новий масив, він, ймовірно , спричиняє принаймні деякий показник продуктивності / пам’яті, коли вам потрібна лише ітерація, особливо для великих масивів - див. Http://jsperf.com/map-foreach

Що стосується того, чому ви хочете використовувати ці функції, вони корисні будь-коли, коли вам потрібно виконати маніпуляції з масивами в javascript, що (навіть якщо ми просто говоримо про javascript у середовищі браузера) є досить часто, майже в будь-який час ви отримуєте доступ до масиву, який ви не записуєте від руки у своєму коді. Можливо, ви маєте справу з масивом елементів DOM на сторінці, або даними, витягнутими із запиту AJAX, або даними, введеними користувачем у форму. Одним із найпоширеніших прикладів, з яким я стикаюся, є витягування даних із зовнішнього API, де ви можете скористатися mapдля перетворення даних у потрібний формат, а потім використати forEachдля ітерації нового масиву, щоб показати їх своєму користувачеві.


я бачу, як люди .map()весь час використовують для модифікації вбудованих елементів. Я був під враженням , що було головною перевагою використання .mapбільш.forEach
chovy

1
@chovy Я вважаю, що вам слід вибирати, виходячи з того, чи хочете ви отримати масив результатів. .mapможна змінювати елементи, так, але це створить масив, який вам не потрібен. Ви також повинні подумати, як вибір того чи іншого дозволяє читачеві здогадатися, чи є у вас побічні ефекти
leewz,

16

Відповідь, яку проголосували (від Кена Редлера), вводить в оману.

Побічний ефект в інформатиці означає , що властивість функції / метод змінює глобальний стан [вікі] . У якомусь вузькому розумінні це може також включати читання з глобальної держави, а не з аргументів. При імперативному або OO програмуванні побічні ефекти проявляються більшу частину часу. І ви, мабуть, цим користуєтесь, не усвідомлюючи.

Значуща різниця між forEachі mapполягає у тому, що mapвиділяє пам'ять і зберігає повертається значення, а forEachвикидає його. Див. Специфікацію emca додаткової інформації див. .

Що стосується причини, за якою люди кажуть forEach, що використовується, коли ви хочете побічний ефект, полягає у тому, що повернене значення forEachзавжди undefined. Якщо він не має побічних ефектів (не змінює глобальний стан), тоді функція просто витрачає час процесора. Компілятор оптимізації усуне цей блок коду та замінить його кінцевим значенням ( undefined).

До речі, слід зазначити, що JavaScript не має обмежень щодо побічних ефектів. Ви все ще можете змінити оригінальний масив всередині map.

var a = [1,2,3]; //original
var b = a.map( function(x,i){a[i] = 2*x; return x+1} );
console.log("modified=%j\nnew array=%j",a,b);
// output:
// modified=[2,4,6]
// new array=[2,3,4]

1
Це питання та його відповіді та коментарі завжди цікаво повертати кожні пару років. Мапа способу реалізації в JS, з точки зору розподілу пам'яті, є ще одним приємним кутом.
Кен Редлер,

6

Це гарне запитання з несподіваною відповіддю.

Далі ґрунтується на офіційному описіArray.prototype.map() .

Немає нічого, що forEach()може зробити, що map()не може. Тобто, map()є суворим супер-набір з forEach().

Хоча map()зазвичай використовується для створення нового масиву, він також може використовуватися для зміни поточного масиву. Наступний приклад ілюструє це:

var a = [0, 1, 2, 3, 4], mapped = null;
mapped = a.map(function (x) { a[x] = x*x*x; return x*x; });
console.log(mapped); // logs [0, 1, 4, 9, 16]  As expected, these are squares.
console.log(a); // logs [0, 1, 8, 27, 64] These are cubes of the original array!!

У наведеному вище прикладі aбуло зручно встановити таке, що a[i] === iдля i < a.length. Незважаючи на це, це демонструє силу Росіїmap() та, зокрема, свою здатність змінювати масив, на якому він викликаний.

Примітка1:
Офіційний опис передбачає, що map()навіть може змінюватися довжина масиву, на якому він викликається! Однак я не бачу (вагомої) причини для цього.

Примітка2:
Хоча map()map є надмножиноюforEach() , forEach()все одно слід використовувати там, де хочеться змінити даний масив. Це дає зрозуміти ваші наміри.

Сподіваюся, це допомогло.


8
Насправді є 1 річ, яку forEach може зробити, а карта не може - не повернути масив.
Antti Haapala

1
Ви також можете використовувати третій аргумент для функції зіставлення для мутації цільового масиву, замість змінної mapped = a.map(function (x, i, arr) { arr[i] = x * x * x; return x * x; });.
сфери

@ pdoherty926 правда, але може і так a.forEach(function(x, i, arr) { ..., що, знову ж таки, є концептуально більш правильним способом, коли ви маєте намір мутувати кожен оригінальний елемент на відміну від зіставлення значень з одного масиву в інший
conny

@conny: Погоджено. Також перегляньте цю відповідь на подібне запитання.
Сумух Барве

3

Ви можете використовувати mapяк би forEach.

Однак він зробить більше, ніж повинен.

scopeможе бути довільним об’єктом; це далеко не обов'язково this.

Що стосується того , існують реальні варіанти використання mapі forEach, а також запитати, чи є реальні варіанти використання forабо whileпетлі.


Але чому тут називають і "scope", і "this": func.call (scope, this [i], i, this); Хіба сфера не є параметром, який дорівнює поточному об'єкту, який є "цим"?
JohnMerlino

Ні, це може дорівнювати поточному об’єкту. Сам об’єкт передається масиву як третій параметр. scope = scope || thisозначає "якщо scopeє помилковим (невизначеним, нульовим, хибним тощо) встановіть thisнатомість область і продовжуйте".
wombleton

Чи можете ви прив'язати мене до прикладу, коли він не рівний цьому?
JohnMerlino

developer.mozilla.org/en/Core_JavaScript_1.5_Reference/… має таку у розділі "Друк вмісту масиву за допомогою методу об'єкта"
wombleton

1

Незважаючи на те, що всі попередні запитання є вірними, я точно визначив би різну різницю. Використання mapта forEachможе передбачати умисел.

Мені подобається використовувати, mapколи я просто певним чином перетворюю існуючі дані (але хочу переконатися, що вихідні дані не змінюються.

Я люблю використовувати, forEachколи я модифікую колекцію на місці.

Наприклад,

var b = [{ val: 1 }, { val: 2 }, { val: 3 }];
var c = b.map(function(el) {
    return { val: el.val + 1 }; // modify element in-place
});
console.log(b);
//  [{ val: 1 }, { val: 2 }, { val: 3 }]
console.log(c);
//  [{ val: 3 }, { val: 4 }, { val: 5 }]

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

Якщо у вас немає реальної потреби змінити існуючий список, насправді немає сенсу змінювати його на місці, а краще вписується у функціональний / незмінний стилі програмування.


1

TL; відповідь DR -

map завжди повертає інший масив.

forEach ні. Що робити, це вирішувати вам. Поверніть масив, якщо хочете, або зробіть щось інше, якщо цього не хочете.

Бажана гнучкість у певних ситуаціях. Якщо справа не в тому, з чим ви маєте справу, використовуйте карту.


0

Ще раз, я відчуваю себе некромантом, додаючи відповідь на запитання, яке було задано 5 років тому (щасливо, що нещодавно відбулася активність, тож не я турбую мертвих :).

Інші вже писали про ваше головне запитання щодо різниці між функціями. Крім...

чи існує реальне використання цих методів у javascript (оскільки ми не оновлюємо базу даних), крім маніпулювання такими числами:

... смішно, про що слід запитати. Щойно сьогодні я написав шматок коду, який призначає ряд значень із регулярного виразу декільком змінним, використовуючи map для перетворення. Він використовувався для перетворення дуже складної текстової структури у візуалізовані дані ... але для простоти я запропоную приклад із використанням рядків дат, оскільки вони, мабуть, є більш звичними для всіх (однак, якби моя проблема була насправді з датами , замість map я б використовував Date-object, який би чудово зробив цю роботу самостійно).

const DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/;
const TEST_STRING = '2016-01-04T03:20:00.000Z';

var [
    iYear,
    iMonth,
    iDay,
    iHour,
    iMinute,
    iSecond,
    iMillisecond
    ] = DATE_REGEXP
        // We take our regular expression and...
        .exec(TEST_STRING)
        // ...execute it against our string (resulting in an array of matches)...
        .slice(1)
        // ...drop the 0th element from those (which is the "full string match")...
        .map(value => parseInt(value, 10));
        // ...and map the rest of the values to integers...

// ...which we now have as individual variables at our perusal
console.debug('RESULT =>', iYear, iMonth, iDay, iHour, iMinute, iSecond, iMillisecond);

Отже ... хоча це був лише приклад - і лише дуже базове перетворення даних (лише заради прикладу) ... зробити це без карти було б набагато більш нудним завданням.

Звичайно, це написано в версії JavaScript , що я не думаю , що занадто багато браузери підтримують поки (принаймні , повністю) , але - ми отримуємо там. Якби мені потрібно було запустити його в браузері, я вважаю, що це буде гарно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.