Skip to content

Instantly share code, notes, and snippets.

@brossetti1
Forked from tsvetkovpro/a-look-at-es6-maps.js
Created July 25, 2017 22:19
Show Gist options
  • Save brossetti1/d55305f2751f037a5e9fa570fe6e66dd to your computer and use it in GitHub Desktop.
Save brossetti1/d55305f2751f037a5e9fa570fe6e66dd to your computer and use it in GitHub Desktop.
es6
// http://www.barbarianmeetscoding.com/blog/2016/04/27/a-look-at-es6-maps/
const wizardsArchive = new Map();
wizardsArchive.set('jaime', {
name: 'jaime',
title: 'The Bold',
race: 'ewok',
traits: ['joyful', 'hairless']
});
console.log('Wizard with key jaime => ', wizardsArchive.get('jaime'));
/* => Item with key jaime =>
[object Object] {
name: "jaime",
race: "ewok",
trait: ["joyful", "hairless"]
}
*/
wizardsArchive.set(42, "What is the answer to life, the universe and everything?")
console.log(wizardsArchive.get(42));
// => What is the answer to life, the universe and everything?
wizardsArchive.set('firebolt', (target) => console.log(`${target} is consumed by fire`));
wizardsArchive.get('firebolt')('frigate');
// => frigate is consumed by fire
2
console.log(`there are ${wizardsArchive.size} thingies in the archive`)
// => there are 3 thingies in the archive
wizardsArchive.delete(42);
wizardsArchive.delete('firebolt');
console.log(`Wizards archive has info on '42': ${wizardsArchive.has(42)}`);
// => Wizards archive has info on '42': false
console.log(`Wizards archive has info on 'firebolt':
${wizardsArchive.has('firebolt')}`);
// => Wizards archive has info on 'firebolt': false
wizardsArchive.clear();
console.log(`there are ${wizardsArchive.size} wizards in the archive`);
// => there are 0 wizards in the archive
// https://hackernoon.com/recursion-in-javascript-with-es6-destructuring-and-rest-spread-4b22ae5998fa#.pz34qlw05
// Destructuring
var [ first, second ] = [ 1, 2, 3, 4 ];
// first: 1
// second: 2
var [ first, , third, fourth ] = [ 1, 2, 3, 4 ];
// first: 1
// third: 3
// fourth: 4
// This is actually quite easily back-ported to the equivalent ES5
var arr = [ 1, 2, 3, 4 ];
var first = arr[0];
var second = arr[1];
// etc ...
// Rest
var [ first, ...notFirst ] = [ 1, 2, 3, 4 ];
// first: 1
// notFirst: [ 2, 3, 4 ]
var [ first, second, ...rest ] = [ 1, 2, 3, 4 ]
// first: 1
// second: 2
// rest: [ 3, 4 ]
var [ first, ...rest ] = [ 1 ]
// first: 1
// rest: []
// The equivalent in ES5 (and below) is to use theArray.slice function.
var arr = [ 1, 2, 3, 4 ];
var first = arr[0];
var rest = arr.slice(1);
// first: 1
// rest: [ 2, 3, 4 ]
// Parameter destructuring
function something([ first, ...rest ]) {
return {
first: first,
rest: rest
};
}
var result = something([1, 2, 3]);
// result: { first: 1, rest: [ 2,3 ] }
// Equivalent ES5:
function something(arr){
var first = arr[0];
var rest = arr.slice(1);
return {
first: first,
rest: rest
};
}
// Spread
var arr = [ 1, 2, 3 ];
var newArr = [ ...arr ];
// newArr: [ 1, 2, 3]
// ES5 equivalent:
var arr = [ 1, 2, 3 ];
var newArr = [].concat(arr);
// We can also do things like appending or prepending an array.
var arr = [ 1, 2, 3] ;
var withPrepend = [ ...arr, 3, 2, 1];
var withAppend = [ 3, 2, 1, ...arr ];
// withPrepend: [ 1, 2, 3, 3, 2, 1]
// withAppend: [ 3, 2, 1, 1, 2, 3 ]
// Functional Programming: lists & recursion
arr = [ 1, 2, 3 ]
// head(arr): 1
// tail(arr): [ 2, 3 ]
// The head is the first element of the list, the tail is the list composed of the list minus the head.
// In ES6 we can do this just by naming the variable appropriately with destructuring and rest:
var [ head, ...tail ] = [ 1, 2, 3 ];
// head: 1
// tail: [ 2, 3 ]
// We can also trivially implement the head and tail functions using ES6:
function head([ head, ...tail ]) {
return head;
}
function tail([ head, ...tail ]) {
return tail;
}
// or with arrow function syntax
var head = ([ head, ...tail ]) => head;
var tail = ([ head, ...tail ]) => tail;
// (Tail) Recursion
function map([ head, ...tail ], fn) {
if(head === undefined && !tail.length) return [];
if(tail.length === 0){
return [ fn(head) ];
}
return [ fn(head) ].concat(map(tail, fn));
}
// Very ES6 map
// Our ES6 recursive/destructuring map can be simplified to:
function map([ head, ...tail ], fn) {
if (head === undefined && !tail.length) return [];
return tail.length ? [ fn(head), ...(map(tail, fn)) ] : [ fn(head) ];
}
// Or if we want to abuse ES6 and allow ourselves to forget that we’re actually doing JavaScript:
var map = ([ head, ... tail ], fn) =>
( (head !== undefined && tail.length) ? ( tail.length ? [ fn(head), ...(map(tail, fn)) ] : [ fn(head) ] ) : []);
// ES5 equivalent
function map(arr, fn) {
var head = arr[0];
var tail = arr.slice(1);
if(head === undefined && tail.length === 0) return [];
if(tail.length === 0) {
return [ fn(head) ];
}
return [].concat(fn(head), map(tail, fn));
}
// Recursive list operations in ES6 with rest/spread and destructuring
// Filter implementation using ES6, destructuring and recursion
function filter([ head, ...tail ], fn) {
const newHead = fn(head) ? [ head ] : [];
return tail.length ? [ ...newHead, ...(filter(tail, fn)) ] : newHead;
}
// Reduce implementation using ES6, destructuring and recursion
function reduce([ head, ...tail ], fn, initial) {
if(head === undefined && tail.length === 0) return initial;
if(!initial) {
const [ newHead, ...newTail] = tail;
return reduce(newTail, fn, fn(head, newHead));
}
return tail.length ? reduce(tail, fn, fn(initial, head)) : [ fn(initial, head) ];
}
// Join implementation using ES6, destructuring and recursion
function join([ head, ...tail ], separator = ',') {
if (head === undefined && !tail.length) return '';
return tail.length ? head + separator + join(tail, separator) : head;
}
// http://www.2ality.com/2016/05/six-nifty-es6-tricks.html
// Enforcing mandatory parameters via parameter default values
/**
* Called if a parameter is missing and
* the default value is evaluated.
*/
function mandatory() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = mandatory()) {
return mustBeProvided;
}
>
foo()// Error: Missing parameter
> foo(123)// 123
// Iterating over Array indices and elements via the for-of loop
var arr = ['a', 'b', 'c'];
arr.forEach(function (elem, index) {
console.log('index = ' + index + ', elem = ' + elem);
});
// Output:
// index = 0, elem = a
// index = 1, elem = b
// index = 2, elem = c
// The ES6 for-of loop is a loop that supports ES6 iteration (via iterables and iterators) and destructuring. If you combine destructuring with the new Array method entries(), you get:
const arr = ['a', 'b', 'c'];
for (const [index, elem] of arr.entries()) {
console.log(`index = ${index}, elem = ${elem}`);
}
// Iterating over Unicode code points
for (const ch of 'x\uD83D\uDE80y') {
console.log(ch.length);
}
// Output:
// 1
// 2
// 1
>
[...
'x\uD83D\uDE80y'
].
length// 3
// Swapping variable values via destructuring
// If you put two variables into an Array and then destructure that Array “into” the same variables, you can swap their values without needing an intermediate variable:
[a, b] = [b, a];
//Simple templating via template literals
const tmpl = addrs =
>
`
<table>
${addrs.map(addr = > `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
// The function tmpl (an arrow function) maps the Array addrs to a string. Let’s use tmpl() on the Array data:
const data = [
{first: '<Jane>', last: 'Bond'},
{first: 'Lars', last: '<Croft>'},
];
console.log(tmpl(data));
// Output:
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>
//Simple mixins via subclass factories
//If an ES6 class extends another class, that class is specified dynamically, via an arbitrary expression (not statically via an identifier):
// Function id() simply returns its parameter
const id = x =
>
x;
class Foo extends id
(Object)
{
}
// That allows you to implement a mixin as a function that maps a class C to a new class (with the mixin methods) whose superclass is C. For example, the following two functions Storage and Validation are mixins:
const Storage = Sup =
> class extends
Sup
{
save(database)
{ ···
}
}
;
const Validation = Sup =
> class extends
Sup
{
validate(schema)
{ ···
}
}
;
// You can use them to compose a class Employee as follows.
class Person { ···
}
class Employee extends Storage
(Validation(Person))
{ ···
}
// In ES5 it's solved with .apply(): an unfriendly and verbose approach. Let's take a look:
var fruits = ['banana'];
var moreFruits = ['apple', 'orange'];
Array.prototype.push.apply(fruits, moreFruits);
console.log(fruits); // => ['banana', 'apple', 'orange']
// The rest operator is used to get the arguments list passed to function on invocation.
function countArguments(...args) {
return args.length;
}
// get the number of arguments
countArguments('welcome', 'to', 'Earth'); // => 3
// The spread operator is used for array construction and destruction, and to fill function arguments from an array on invocation.
let cold = ['autumn', 'winter'];
let warm = ['spring', 'summer'];
// construct an array
[...cold, ...warm] // => ['autumn', 'winter', 'spring', 'summer']
// destruct an array
let otherSeasons, autumn;
[autumn, ...otherSeasons] = cold;
otherSeasons // => ['winter']
// function arguments from an array
cold.push(...warm);
cold // => ['autumn', 'winter', 'spring', 'summer']
// The rest operator solves this elegantly. It allows to define a rest parameter ...args in a function declaration:
function sumOnlyNumbers(...args) {
var numbers = filterNumbers();
return numbers.reduce((sum, element) => sum + element);
function filterNumbers() {
return args.filter(element => typeof element === 'number');
}
}
sumOnlyNumbers(1, 'Hello', 5, false); // => 6
// 2.2 Selective rest parameter
function filter(type, ...items) {
return items.filter(item => typeof item === type);
}
filter('boolean', true, 0, false); // => [true, false]
filter('number', false, 4, 'Welcome', 7); // => [4, 7]
// arguments object doesn't have this selective property and always includes all the values.
// 2.3 Arrow function case
(function() {
let outerArguments = arguments;
const concat = (...items) => {
console.log(arguments === outerArguments); // => true
return items.reduce((result, item) => result + item, '');
};
concat(1, 5, 'nine'); // => '15nine'
})();
// 3. Improved function call
//Let's see an example of .apply() usage:
let countries = ['Moldova', 'Ukraine'];
countries.push.apply(countries, ['USA', 'Japan']);
console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan']
//Let's improve the above sample with a spread operator:
let countries = ['Moldova', 'Ukraine'];
countries.push(...['USA', 'Japan']);
console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan']
//The following example is removing from an array existing elements, then adds other array and an element:
var numbers = [1, 2];
var evenNumbers = [4, 8];
const zero = 0;
numbers.splice(0, 2, ...evenNumbers, zero);
console.log(numbers); // => [4, 8, 0]
// 4. Improved array manipulation
// 4.1 Array construction
// Create an array with initial elements from another array:
var initial = [0, 1];
var numbers1 = [...initial, 5, 7];
console.log(numbers1); // [0, 1, 5, 7]
let numbers2 = [4, 8, ...initial];
console.log(numbers2); // => [4, 8, 0, 1]
//Concatenate 2 or more arrays:
var odds = [1, 5, 7];
var evens = [4, 6, 8];
var all = [...odds, ...evens];
console.log(all); // => [1, 5, 7, 4, 6, 8]
// Clone an array instance:
var words = ['Hi', 'Hello', 'Good day'];
var otherWords = [...words];
console.log(otherWords); // => ['Hi', 'Hello', 'Good day']
console.log(otherWords === words); // => false
//otherWords is a clone version of words array. Notice that cloning happens only on array itself, but not on the contained elements (i.e. it's not a deep clone).
// 4.2 Array destruction
var seasons = ['winter', 'spring', 'summer', 'autumn'];
var coldSeason, otherSeasons;
[coldSeason, ...otherSeasons] = seasons;
console.log(coldSeason); // => 'winter'
console.log(otherSeasons); // => ['spring', 'summer', 'autumn']
/*[winter, ...otherSeasons] extracts the first item
'winter' into coldSeason variable and the rest of
elements into otherSeasons array.*/
//5. Spread operator and iteration protocols
var str = 'hi';
var iterator = str[Symbol.iterator]();
iterator.toString(); // => '[object String Iterator]'
iterator.next(); // => { value: 'h', done: false }
iterator.next(); // => { value: 'i', done: false }
iterator.next(); // => { value: undefined, done: true }
[...str]; // => ['h', 'i']
// The following sample makes an array-like object conformed to iteration protocols, then transforms it to an array using spread operator:
function iterator() {
var index = 0;
return {
next: () => ({ // Conform to Iterator protocol
done : index >= this.length,
value: this[index++]
})
};
}
var arrayLike = {
0: 'Cat',
1: 'Bird',
length: 2
};
arrayLike[Symbol.iterator] = iterator; //Conform to Iterable Protocol
var array = [...arrayLike];
console.log(array); // => ['Cat', 'Bird']
//You can create a new set using the Set constructor:
let set = new Set();
//Or you can create a set from an iterable collection like an array:
let elementsOfMagic = new Set(['earth', 'fire', 'air', 'earth', 'fire', 'water']);
console.log(`These are the elements of magic: ${[...elementsOfMagic]}`);
// => These are the elements of magic: earth, fire, air, water
// As you can appreciate from the example above, a Set will automatically remove the duplicated items and only store each specific item once. You can easily add more items to a Set using the add method:
elementsOfMagic.add('aether');
console.log(`More magic!: ${[...elementsOfMagic]}`);
// => More magic!: earth, fire, air, water, aether
elementsOfMagic.add('earth').add('air').add('water');
// You can check whether an item exists within a Set by using the has method:
console.log(`Is water one of the sacred elements of magic? ${elementsOfMagic.has('water')}`)
// => Is water one of the sacred elements of magic? true
elementsOfMagic.delete('aether');
console.log(`The aether element flows like the tides and
like the tides sometimes disappears:
${[...elementsOfMagic]}`);
// => The aether element flows
// like the tides and sometimes disappears:
// earth,fire,air,water
console.log(`${elementsOfMagic.size} are the elements of magic`);
//And remove all the items from a set using the clear method:
const castMagicShield = () => elementsOfMagic.clear();
castMagicShield();
console.log(`ups! I can't feel the elements: ${elementsOfMagic.size}`);
// => ups! I can't feel the elements: 0
//Iterating Sets
// Just like Map you can iterate over the elements of a Set using the for/of loop:
elementsOfMagic.add('fire').add('water').add('air').add('earth');
for(let element of elementsOfMagic){
console.log(`element: ${element}`);
}
// => element: fire
// element: water
// element: air
// element: earth
//The default iterator for a Set is the values iterator. The next snippet of code is equivalent to the one above:
for(let element of elementsOfMagic.values()){
console.log(`element: ${element}`);
}
//In addition to using either of these iterators, you can take advantage of the Set.prototype.forEach method to traverse the items in a Set:
elementsOfMagic.forEach((value, alsoValue, set) => {
console.log(`element: ${value}`);
})
// => element: fire
// element: water
// element: air
// element: earth
// Using Array Methods With Sets
// The conversion between Sets to Arrays and back is so straightforward that using all the great methods available in the Array.prototype object is one little step away:
function filterSet(set, predicate){
var filteredItems = [...set].filter(predicate);
return new Set(filteredItems);
}
var aElements = filterSet(elementsOfMagic, e => e.startsWith('a'));
console.log(`Elements of Magic starting with a: ${[...aElements]}`);
// => Elements of Magic starting with a: air
// Let’s illustrate this problematic situation with an example. Let’s say that we have a Set of persons which of course are unique entities (we are all beautiful wonders like precious stones):
let persons = new Set();
// We create a person object randalf and we attempt to add it twice to the Set:
let randalf = {id: 1, name: 'randalf'};
persons
.add(randalf)
.add(randalf);
console.log(`I have ${persons.size} person`)
// => I have 1 person
console.log([...persons]);
// => [[object Object] {
// id: 1,
// name: "randalf"
//}]
// The Set has our back and only adds the person once. Since it is the same object, using strict equality works in this scenario. However, what would happen if we were to add an object that we considered to be equal in our problem domain? And let’s say that in our current example two persons are equal if they have the same properties, and particularly the same id (because I am sure there’s many randalfs around, although I’ve never met any of them):
persons.add({id: 1, name: 'randalf'});
console.log(`I have ${person.size} persons?!?`)
// => I have 2 persons?!?
console.log([...persons]);
/*
*= [[object Object] {
id: 1,
name: "randalf"
}, [object Object] {
id: 1,
name: "randalf"
}]
*/
// We are free to imagine how it would look though, and something like this would work wonderfully:
let personsSet = new Set([], p => p.id);
// In the meantime, if you need to use Set-like functionality for objects your best bet is to use a dictionary indexing objects by a key that represents their uniqueness.
var fakeSetThisIsAHack = new Map();
fakeSetThisIsAHack
.set(randalf.id, randalf)
.set(1, {id: 1, name: 'randalf'});
console.log(`fake set has ${fakeSetThisIsAHack.size} item`);
// => fake set has 1 item
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment