JS

JavaScript and Real First Tasks

Agenda

  1. forEach vs filter vs map vs reduce
  2. Additional cases

  3. 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

JS

By Oleg Rovenskyi