JS
JavaScript and Real First Tasks
Agenda
- forEach vs filter vs map vs reduce
-
Additional cases
-
Performance
forEach
Foreach takes a callback function and run that callback function on each element of array one by one.
const menu = [{
name: 'Burger',
modifiers: [],
}, {
name: 'Soup',
modifiers: [],
}, {
name: 'Salad',
modifiers: [],
}, {
name: 'Fresh Salad',
modifiers: ['olive oil', 'sunflower oil'],
}];
menu.forEach((item, index) => {
console.log(`Delicious name: ${ item.name }, index: ${ index }`);
})
let productIds = [];
menu.forEach((item, index) => {
const id = item.id;
const isAvailableModifiers = !!item.modifiers.length;
if (isAvailableModifiers) productIds.push(id);
})
forEach
Which Should You Use
forEach
1. forEach keeps the variable’s scope to the block
2. Lower chance of accidental errors with forEach
3. forEach is easier to read
for
1. You can break out of a for loop earlier
Need To Remember
forEach just loop over the array and executes the callback
filter
The main difference between forEach and filter is that filter always returns an array, even if there is only one matching element.
const menu = [{
name: 'Burger',
section: 'main',
}, {
name: 'Soup',
section: 'soup',
}, {
name: 'Salad',
section: 'starters',
}, {
name: 'Fresh Salad',
section: 'starters',
}];
const onlyStarters = menu.filter((item) => item.section === 'starters');
console.log(onlyStarters);
filter
Be Careful
Copying of objects is carried out by reference
const menu = [{
name: 'Burger',
section: 'main',
}, {
name: 'Soup',
section: 'soup',
}, {
name: 'Salad',
section: 'starters',
}, {
name: 'Fresh Salad',
section: 'starters',
}];
onlyStarters[0].name = 'new name for burger'
console.log(menu[2].name) // <- new name for burger
map
Map like filter & forEach takes a callback and runs for each value in the array and returns each new value in the resulting array.
const menu = [{
name: 'Burger',
id: 'qewffewfwef',
}, {
name: 'Soup',
id: 'qdfh4k58',
}, {
name: 'Salad',
id: 'fjk7frjt',
}, {
name: 'Fresh Salad',
id: 'fjk7fefeft',
}];
// Example 1. We need get array of product names
const listOfProducts = menu.map((product) => product.name);
console.log(listOfProducts); // <-- [ 'Burger', 'Soup', 'Salad', 'Fresh Salad' ]
// Example 2. Compare with forEach
let listOfProducts2 = [];
menu.forEach((product) => {
listOfProducts2.push(product.name);
});
console.log(listOfProducts2); // <-- [ 'Burger', 'Soup', 'Salad', 'Fresh Salad' ]
map
const menu = [{
name: 'Burger',
id: 'qewffewfwef',
}, {
name: 'Soup',
id: 'qdfh4k58',
}, {
name: 'Salad',
id: 'fjk7frjt',
}, {
name: 'Fresh Salad',
id: 'fjk7fefeft',
}];
// Example 3. Need to add additional property
const availableProductIds = ['qewffewfwef', 'qdfh4k58', 'fjk7fefeft'];
const updatedMenu = menu.map(product => ({
...product,
is_enabled: availableProductIds.includes(product.id),
}));
console.log(updatedMenu); // <-- [{name: 'Burger', id: 'qewffewfwef', is_enabled: true }}, ...]
map
const menu = [{
name: 'Burger',
id: 'qewffewfwef',
}, {
name: 'Soup',
id: 'qdfh4k58',
}];
const products = menu.map((product) => {
product.is_new = true;
return product
});
products[0].name = 'new name';
console.log('parent item: ', menu.find(item => item.id === products[0].id)) // <-- new name
console.log('products[0]: ', products[0]) // <-- new name
/* For resolve this problem need to provide one of the copying mechanisms */
const products2 = menu.map((product) => ({ ...product, is_new: true}));
products2[0].name = 'test name';
console.log('fixed parent item: ', menu.find(item => item.id === products2[0].id)) // <-- { name: 'new name' }
console.log('fixed products[0]: ', products2[0]) // <-- { name: 'test name' }
Be Careful
Copying of objects is carried out by reference
reduce
Reduce method of the array object is used to reduce the array to one single value.
const menu = [{
name: 'Burger',
}, {
name: 'Soup',
}, {
name: 'Salad',
}, {
name: 'Fresh Salad',
}];
// Example 1
const collection = [1, 2, 3];
const sum = collection.reduce((acc, elem) => acc + elem, 0);
console.log(sum); // <- 6
// Example 2
const nameLength = menu.reduce((acc, item) => acc + item.name.length, 0);
console.log(nameLength); // <-- 26
// Example 3 forEach
let nameLength2 = 0;
menu.forEach((item) => nameLength2 += item.name.length);
console.log(nameLength2); // <-- 26
/* Syntax:
* array.reduce(function, initialValue);
*/
reduce
Reduce method of the array object is used to reduce the array to one single value.
const prices = [{
id: 'efwefwe3',
entity_id: 'qewffewfwef',
price: 100,
}, {
id: 'efwefwe3',
entity_id: 'qdfh4k58',
price: 120,
}, {
id: 'efwefwe3',
entity_id: 'fjk7frjt',
price: 130,
}, {
id: 'efwefwe3',
entity_id: 'fjk7fefeft',
price: 140,
}];
// Example 4
const getMaxPrice = (data) => {
return data.reduce((acc, item) => Math.max(acc, item.price), data[0].price);
};
console.log(getMaxPrice(prices));
reduce
const menu = [{
name: 'Burger',
changes: [{date: new Date(), prevName: 'King Burger'}],
}, {
name: 'Soup',
changes: [],
}, {
name: 'Salad',
changes: [{date: new Date(), prevName: 'Summer Salad'}],
}, {
name: 'Fresh Salad',
changes: [],
}];
// Example 5. array get list of properties from changes
const propertiesFromChanges = menu
.filter((product) => product.changes)
.map((product) => product.changes)
.reduce(
(properties, changes) =>
properties.concat(
...changes.map(change => Object.keys(change))
), []
);
console.log(propertiesFromChanges); // <-- [ 'date', 'prevName', 'date', 'prevName' ]
reduce
const menu = [{
name: 'Burger',
description: 'the best burger!',
}, {
name: 'Soup',
description: 'Classic soup',
}, {
name: 'Salad',
}, {
name: 'Fresh Salad',
description: 'From best traditions',
}];
// Example 6. get object structure { [name]: [description] }
const description = menu.reduce((descriptionsTemplate, product) => {
descriptionsTemplate[product.name] = product.description || '';
return descriptionsTemplate;
}, {});
console.log(description);
// <-- {
// Burger: 'the best burger!',
// Soup: 'Classic soup',
// Salad: '', '
// Fresh Salad': 'From best traditions'
// }
import * as _ from 'lodash';
const menu = [{
name: 'Burger',
changes: [{date: new Date(), prevName: 'King Burger'}],
}, {
name: 'Soup',
changes: [],
}, {
name: 'Salad',
changes: [{date: new Date(), prevName: 'Summer Salad'}],
}, {
name: 'Fresh Salad',
changes: [],
}];
// Lodash
const propertiesFromChanges2 = _.flow(
(item) => _.filter(item, (product) => product.changes),
(item) => _.map(item, (product) => product.changes),
(item) => _.reduce(item,(properties, changes) =>
properties.concat(
...changes.map(change => Object.keys(change))
), []),
)(menu);
console.log(propertiesFromChanges2); // <-- [ 'date', 'prevName', 'date', 'prevName' ]
Additional cases
Lodash
import * as fp from 'lodash/fp';
const menu = [{
name: 'Burger',
changes: [{date: new Date(), prevName: 'King Burger'}],
}, {
name: 'Soup',
changes: [],
}, {
name: 'Salad',
changes: [{date: new Date(), prevName: 'Summer Salad'}],
}, {
name: 'Fresh Salad',
changes: [],
}];
// Lodash fp
const propertiesFromChanges3 = fp.flow(
fp.filter((product) => product.changes),
fp.map((product) => product.changes),
fp.reduce((properties, changes) =>
properties.concat(
...changes.map(change => Object.keys(change))
), []),
)(menu);
console.log(propertiesFromChanges3); // <-- [ 'date', 'prevName', 'date', 'prevName' ]
Additional cases
Lodash fp
const menu = [{
name: 'Burger',
id: 'qewffewfwef',
is_available: true,
}, {
name: 'Salad',
id: 'fjk7frjt',
is_available: false,
}];
Async cases
const prices = [{
id: 'efwefwe3',
entity_id: 'qewffewfwef',
price: 100,
}, {
id: 'efwefwe3',
entity_id: 'fjk7frjt',
price: 130,
}];
let availablePositions = [];
async function composeMenuData(menu) {
await storeAvailablePositions(menu)
console.log(availablePositions); // <-- { name: 'Burger', price: { id: 'efwefwe3', entity_id: 'qewffewfwef', price: 100 } }
}
async function getPrice(id) {
return Promise.resolve(prices.find(item => item.entity_id === id));
}
async function storeAvailablePositions(menu) {
const availableItems = menu.filter(item => item.is_available);
// await Promise.all(availableItems.forEach(async (item) => { // Be Careful 'forEach' just loop over the array
// For this case we need 'map'
await Promise.all(availableItems.map(async (item) => {
item.price = await getPrice(item.id);
setAvailablePositions(item);
return item;
}))
}
function setAvailablePositions(item) { availablePositions.push(item); }
composeMenuData(menu);
const values = Array(10 ** 7).fill(1).map(() => _.random(1, 10 ** 5)); // <-- 10000000
// Example 1
console.time('forEach');
const obj = {};
values.forEach((item) => {
obj[`item_${item}`] = item;
})
console.timeEnd('forEach'); // <-- 4661.977ms
// Example 2
console.time('forEach with Object.assign');
const obj2 = {};
values.forEach((item) => {
Object.assign(obj2, {[`item_${item}`]: item});
})
console.timeEnd('forEach with Object.assign'); // <-- 14487.470ms
// Example 3
console.time('forEach with spread');
const obj3 = {};
values.forEach((item) => ({...obj3, [`item_${item}`]: item}))
console.timeEnd('forEach with spread'); // <-- 12204.542ms
// Example 4
console.time('reduce');
values.reduce((acc, number) => {
acc[`item_${number}`] = number;
return acc;
}, {});
console.timeEnd('reduce'); // <-- 4485.078ms
Performance
Summary
Links
- https://github.com/OlegRovenskyi/work-with-collection
- https://quokkajs.com/docs/#getting-started
- https://lodash.com/
- https://github.com/lodash/lodash/wiki/FP-Guide
- https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d
- https://hackernoon.com/3-javascript-performance-mistakes-you-should-stop-doing-ebf84b9de951
JS
By Oleg Rovenskyi
JS
- 357