by Daniil Suleiman

There are two ways how to write code in ES6 way:

  • choose an appropriate JS Engine
  • to use a transpiler

let, const

  1. var declarations are globally scoped or function scoped while let and const are block scoped.

  2. var variables can be updated and re-declared within its scope; let variables can be updated but not re-declared; const variables can neither be updated nor re-declared.

  3. They are all hoisted to the top of their scope but while var variables are initialized with undefined, let and const variables are not initialized.

  4. While var and let can be declared without being initialized, const must be initialized during declaration.

Array destructuring assignment

const arr = ['Daniil', 'Suleiman'];
const [name, surname] = arr

console.log(name); // Daniil
console.log(surname); // Suleiman

----------------------->

const footballClubs = ['Real Madrid', 'Barcelona', 'Juventus', 'Chelsea', 'Monaco'];
const [first, second, ...rest] = footballClubs;

console.log(rest); // ['Juventus', 'Chelsea', 'Monaco']

----------------------->

const arr = ['User'];
const [user = 'noname', id = 'unregistered'] = arr;

console.log(user); // User
console.log(id); // unregistered

----------------------->
let a = 1;
let b = 2;
[a,b] = [b,a];

console.log(a); // 2
console.log(b); // 1

Practice!

function swap(array, i, j) {

 // your code here 

}

const array = [1,2,3];

swap(array, 0, 2)) // true

console.log(array) // [3, 2, 1]
function swap(array, i, j) {
    [array[i], array[j]] = [array[j], array[i]];
  
    return true;
}

Solution:

function swap(array, i, j) {
     if(i < array.length && j < array.length) {
    	[array[i], array[j]] = [array[j], array[i]];

        return true;
     }
    
     return false; 
}

Better:

Object destructuring assignment

const obj = {
    name: 'Freddie',
    surname: 'Mercury',
    birthDate: '05.09.1946'
};
const { name, birthDate } = obj;

console.log(name); // Freddie
console.log(birthDate); // 05.09.1946

------------------------->

const { name: singer, birthDate: date } = obj;

console.log(singer); // Freddie
console.log(date); // 05.09.1946

------------------------->

const { name: singer='Michael', genre='pop' } = obj;

console.log(singer); // Freddie
console.log(genre); // pop

Practice!

function copy(object, config) {

 // your code here 

}

const obj = { name: 'Vasya', lastName: 'Pupkin' };

const copyObj = copy(obj);

console.log(copyObj) // { name: 'Vasya', lastName: 'Pupkin' }
console.log(copyObj === obj) // false

const copyObjWithConfig = copy(obj, { lastName: 'Ivanov', age: 25 });

console.log(copyObj) // { name: 'Vasya', lastName: 'Ivanov', age: 25 };
function copy(object, config) {

 return { ...object, ...config }

}

Solution:

Functions

function printUser(name = 'noname', surname = 'unknown') {
    console.log(name + ' ' + surname);
}

printUser('Banksy'); // Banksy unknown
  • default parameters
  • spread operator in function parameters
function printUserInfo(name, surname, ...otherInfo) {
    console.log(otherInfo);
}

printUserInfo('Brendan', 'Eich', 'Pittsburgh', 'Mocha'); // ['Pittsburgh', 'Mocha']
  • spread operator in function calls
const arr = ['Lexus', 'Infinity', 'Toyota'];

function foo(first, second, third) {
    console.log(second);
}

foo(...arr); // Infinity

Functions

const user = { name: 'Michael', surname: 'Kors', occupation: 'designer'}

function printUser({ name, occupation }) {
    console.log(name + ' ' + occupation);
}

printUser(user); // Michael designer
  • parameters destructuring
function printUser({ name = 'Giorgio', surname: lastName = 'Armani' }) {
    console.log(name + ' ' + lastName);
}

printUser({}); // Giorgio Armani

--------------->

function printUser({ name = 'Giorgio', surname: lastName = 'Armani' } = {}) {
    console.log(name + ' ' + lastName);
}

printUser(); // Giorgio Armani
  • parameters destructuring with default parameters

Functions

function foo () {}

console.log(foo.name); // foo

let func = function () {};

console.log(func.name); // func
  • function name
  • block scoped
'use strict';

if (true) {
    function foo(param) {
        console.log(param);
    }
}

foo('param'); // ReferenceError: foo is not defined

Arrow functions

let foo = param => param + ' ' + 'sucks'

console.log(foo('jQuery')); // jQuery sucks

-------------------------->

let func = (param1, param2) => {
    console.log(param1);

    return param2 + 5;
}

func(2, 5); // 10

Arrow functions don't have 'this'

'use strict';

let garage = {
  title: 'My garage',
  cars: ["Lexus", "Infinity", "Toyota"],
  showCars: function() {
    this.cars.forEach(
      car=> alert(this.title + ': ' + car)
    )
  }
}

garage.showCars();
// My garage: Lexus
// My garage: Infinity
// My garage: Toyota

Arrow functions restrictions

  • don't have its own 'this';
  • arrow function cannot be a constructor (can't be executed with new);
  • don't have arguments;
  • arrow function != .bind(this)

Practice!

const store = {
  values: [1,2,3],
  increment: 0,
  
  getSumm: function() {

    // your code here

  },

  addValues: function() {

    // your code here

  }
}


store.addValues(4,5,6,7) // 
console.log(store.values) // [1,2,3,4,5,6,7]

store.addValues(1, 2) // 
console.log(store.values) // [1,2,3,4,5,6,7,1,2]


store.getSumm() // 31

store.increment = 1

store.getSumm() // 40
const store = {
  values: [1,2,3],
  increment: 0,
  
  getSumm: function() {

    return this.values.reduce((cur, next) => {
       return cur + next + this.increment;
    }, 0)

  },

  addValues: function(...args) {

     this.values = [...this.values, ...args];  

  }
}


store.addValues(4,5,6,7) // 
console.log(store.values) // [1,2,3,4,5,6,7]

store.addValues(1, 2) // 
console.log(store.values) // [1,2,3,4,5,6,7,1,2]


store.getSumm() // 31

store.increment = 1

store.getSumm() // 40

Solution:

Strings

  • Multi-line strings
  • Expression interpolation
  • Nesting templates
console.log('string text line 1\n' +
'string text line 2');

console.log(`string text line 1
string text line 2`);

// "string text line 1
// string text line 2"

----------------------->

let name = 'Daniil';

console.log(`My name is ${name}`); // My name is Daniil

----------------------->

const output= `I ${ false ? '' :
 `like ${ true ? 'it' : 'that'}` }`;

console.log(output); // I like it

Strings

Tagged templates

let person = 'Mike';
let age = 28;

function myTag(strings, person, age) {
  let str0 = strings[0]; // "That "
  let str1 = strings[1]; // " is a "

  let ageStr;
  if (age > 99) {
    ageStr = 'centenarian';
  } else {
    ageStr = 'youngster';
  }

  // We can even return a string built using a template literal
  return `${str0}${person}${str1}${ageStr}`;
}

let output = myTag`That ${ person } is a ${ age }`;

console.log(output); // That Mike is a youngster

Strings

Added new methods

  • str.includes(x) – determines whether one string may be found within another string, returning true or false.
  • str.endsWith(x) – determines whether a string ends with x, returning true or false.
  • str.startsWith(x) – method determines whether a string begins with x, returning true or false.
  • str.repeat(t) – repeats the str with t times, return a concatenated string.

Symbols

New data type, is used to create unique identifiers

let symbol = Symbol();

typeof symbol === 'symbol'; // true

Every symbol is unique!!!

Symbol() == Symbol(); // false
Symbol() === Symbol(); // false

A symbol could has a name (optional)

let symbol = Symbol('sasha');

console.log(symbol); // Symbol(sasha)

Global Symbol registry

// to create a symbol in the global reqistry
let name = Symbol.for("Den");

// if a symbol with such name is already exists, it will be returned
let anotherName = Symbol.for("Den");

anotherName === name; // true

Symbol.for()

Symbol.keyFor()

// to retrieve symbol name by symbol

let name = Symbol.for("Den");
let anotherName = Symbol.for("Den");

Symbol.keyFor(name); // "Den"
Symbol.keyFor(anotherName); // "Den"

Symbols usage

// use symbols as object property name

let isAdmin = Symbol("isAdmin");

let user = {
  name: "Вася",
  [isAdmin]: true
};

user[isAdmin] // true

Symbols are not enumerable in for...in iterations.

var obj = {
 [Symbol('a')]: 'a',
 [Symbol.for('b')]: 'b',
 ['c']: 'c',
 d: 'd'
};

for (var i in obj) {
   console.log(i); // logs "c" and "d"
}

Object.keys(obj); // ["c", "d"]
Object.values(obj); // ["c", "d"]

Symbols usage

var obj = {
 [Symbol('a')]: 'a',
 [Symbol.for('b')]: 'b',
 ['c']: 'c',
 d: 'd'
};

Object.getOwnPropertyNames(obj); // ["c", "d"]

Object.getOwnPropertySymbols(obj); // [Symbol(a), Symbol(b)]

In addition, Object.getOwnPropertyNames() will not return symbol object properties, however, you can use Object.getOwnPropertySymbols() to get these.

Iterators

Allows objects to define or customize their iteration behavior, such as what values are looped over in a for..of construct.

In order to be iterable, an object must implement the @@iterator method (must have a property with a @@iterator key which is available via constant Symbol.iterator)

The iterator protocol defines a standard way to produce a sequence of values (either finite or infinite), and potentially a return value when all values have been generated.

Iterators

 Iterable objects are:

  • built-in String,
  • Array, Array-like objects (e.g., arguments or NodeList),
  • TypedArray,
  • Map,
  • Set,
  • user-defined iterables.

Iterators

The for...of statement creates a loop iterating over iterable objects

let arr = [1, 2, 3]; 

for (let value of arr) {
  console.log(value)       
}

// 1
// 2
// 3

--------------------->

let str = 'pizza'; 

for (let value of str) {
  console.log(value)       
}

// p
// i
// z
// z
// a

Custom Iterators

An object is an iterator when it implements a next() method with the following semantics:

A zero arguments function that returns an object with at least the following two properties:

done (boolean)

  • Has the value true if the iterator is past the end of the iterated sequence.
  • Has the value false if the iterator was able to produce the next value in the sequence. This is equivalent of not specifying the done property altogether.

value

  • any JavaScript value returned by the iterator. Can be omitted when done is true.

The next method always has to return an object with appropriate properties including done and value. If a non-object value gets returned (such as false or undefined), a TypeError ("iterator.next() returned a non-object value") will be thrown.

Custom Iterators

const obj = {
	from: 1,
	to: 5,
	[Symbol.iterator] () {
		const from = this.from;
		const to  = this.to;
		let current = from;
	
		return {
			next() {
				if (current <= to) {
					return { value: current++, done: false}
				}
				return { done: true }
			}
		}
	}
}

for (let val of obj) {
	console.log(val)
}

// 1
// 2
// 3
// 4
// 5

Custom Iterators

Array.prototype[Symbol.iterator] = function () {
    const from = 10;
    const to  = 20;
    let current = from;
	
    return {
    		next() {
		    if (current <= to) {
		        return { value: current++, done: false}
		    }

		    return { done: true }
		}
    }
};

const arr = [1,2,3];

for (let val of arr) {
	console.log(val)
}

Replace a build-in Array.prototype @@iterator by custom

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}

Difference between for...of and for...in

The for...in statement iterates over the enumerable properties of an object, in an arbitrary order.

The for...of statement iterates over data that the iterable object defines to be iterated over.

Practice!

const arr = [{ name: "neo" }, { name: "trinity" }, { name: "morpheus" }]

arr[Symbol.iterator] = function() {
 
   // your code here

}


const nameArr = [...arr] // ["neo", "trinity", "morpheus"]
const arr = [{ name: "neo" }, { name: "trinity" }, { name: "morpheus" }]

arr[Symbol.iterator] = function() {
  const that = this; 
  let index = 0;
 
  return {
    next() {
	if(index < that.length) {
           return { done: false, value: that[index++].name }
        } else {
           return { done: true }
        }
    } 
  }
}

Solution:

New collection types

Map 

Holds key-value pairs and remembers the original insertion order of the keys. Any value may be used as either a key or a value.

let map = new Map();
let obj = { a: 1, b: 2 };

map.set('999', 'hello');   // key as a string
map.set(999, 'number');     // key as a number
map.set(true, 'boolean'); // key as a boolean
map.set(obj, 'hi'); // key as an object

// map save a key type
console.log( map.get(999)   ); // 'number'
console.log( map.get('999') ); // 'hello'

console.log( map.size ); // 4

Set method is chainable

map
  .set('1', 'string')
  .set(1, 'number')
  .set(false, 'boolean');

Map

new Map([iterable])

iterable - an array or other iterable object whose elements are key-value pairs (arrays with two elements, e.g. [[ 1, 'one' ],[ 2, 'two' ]]). Each key-value pair is added to the new Map; null values are treated as undefined.

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

Key equality is based on the "SameValueZero" algorithm: NaN is considered the same as NaN (even though NaN !== NaN) and all other values are considered equal according to the semantics of the === operator

Map

  • map.delete(key) - returns true if an element in the map existed and has been removed, or false if the element does not exist;
  • map.clear() – removes all key/value pairs from the map;
  • map.has(key) – returns a boolean asserting whether a value has been associated to the key in the map or not;
  • map.keys() – returns a new Iterator object that contains the keys for each element in the map in insertion order;
  • map.values() – returns a new Iterator object that contains the values for each element in the map in insertion order;
  • map.entries() – returns a new Iterator object that contains an array of [key, value] for each element in the map in insertion order

Set

The Set object lets you store unique values of any type, whether primitive values or object references.

let set = new Set();

let vasya = {name: "Вася"};
let petya = {name: "Петя"};
let dasha = {name: "Даша"};

set.add(vasya);
set.add(petya);
set.add(dasha);
set.add(vasya);
set.add(petya);

set.size; // 3

set.forEach( user => console.log(user.name ) ); // Вася, Петя, Даша

Set

Main methods:

  • set.add(item) – appends an item into the set. Returns the set (chainable);
  • set.delete(item)removes the item associated to the value and returns the value that Set.prototype.has(item) would have previously returned;
  • set.has(item) returns a boolean asserting whether an element is present with the given value in the set or not;
  • set.clear()removes all elements from the set;
  • set.forEach() - executes a provided function once for each value in the Set object, in insertion order.

WeakMap, WeakSet

The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced.  The keys must be objects and the values can be arbitrary values.

let activeUsers = [
  {name: "Mike"},
  {name: "Pete"},
  {name: "John"}
];

let weakMap = new WeakMap();

weakMap.set(activeUsers[0], 1);
weakMap.set(activeUsers[1], 2);
weakMap.set(activeUsers[2], 3);

activeUsers.splice(0, 1); // Mike has left

// weakMap contains only 2 elements

activeUsers.splice(0, 1); // Pete has left


// weakMap contains only 1 element

WeakMap, WeakSet

The WeakSet object lets you store weakly held objects in a collection.

var ws = new WeakSet();
var foo = {};
var bar = {};

ws.add(foo);
ws.add(bar);

ws.has(foo);    // true
ws.has(bar);    // true

ws.delete(foo); // removes foo from the set
ws.has(foo);    // false, foo has been removed

WeakMap, WeakSet

The WeakSet object lets you store weakly held objects in a collection.

var ws = new WeakSet();
var foo = {};
var bar = {};

ws.add(foo);
ws.add(bar);

ws.has(foo);    // true
ws.has(bar);    // true

ws.delete(foo); // removes foo from the set
ws.has(foo);    // false, foo has been removed

Practice!

function intersection(arr1, arr2) {

  // your code here

}


const arr1 = [1,4,2,3,4,5,3,6,6]
const arr2 = [3,4,6,7,8,9,4,4,3]

console.log(intersection(arr1, arr2)) // [4,3,6]
function intersection(arr1, arr2) {

   const intersection = arr1.filter(x => arr2.includes(x)) // find the same numbers 

   const uniq = new Set(intersection) 

   return [...uniq] // transform to array



}


const arr1 = [1,4,2,3,4,5,3,6,6]
const arr2 = [3,4,6,7,8,9,4,4,3]

console.log(intersection(arr1, arr2)) // [4,3,6]

Solution:

Generators

The function* declaration (function keyword followed by an asterisk) defines a generator function, which returns a Generator object.

Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.

Generators in JavaScript -- especially when combined with Promises -- are a very powerful tool for asynchronous programming as they mitigate -- if not entirely eliminate -- the problems with callbacks, such as Callback Hell and Inversion of Control.
This pattern is what async functions are built on top of.

Generators

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

const generator = generateSequence();

generator.next(); // {value: 1, done: false}
generator.next(); // {value: 2, done: false}
generator.next(); // {value: 3, done: true}
generator.next(); // {value: undefined, done: true}

--------------------------->

function* idMaker() {
  var index = 0;
  while (index < index+1)
    yield index++;
}

const gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Generators composition

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i) {
  yield i;
  yield* anotherGenerator(i);
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

The yield* expression is used to delegate to another generator or iterable object.

Generators composition

function* g4() {
  yield* [1, 2, 3];
  return 'foo';
}

var result;

function* g5() {
  result = yield* g4();
}

var iterator = g5();

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}, 
                              // g4() returned {value: 'foo', done: true} at this point

console.log(result);          // "foo"

Generators are powerful

function* gen() {
  let result = yield "2 + 2?";

  alert(result);
}

let generator = gen();

alert(generator.next().value); // "2 + 2?"

setTimeout(() => generator.next(4), 2000);

------------------------------->

function* gen() {
  let ask1 = yield "2 + 2?";

  alert(ask1); // 4

  let ask2 = yield "3 * 3?"

  alert(ask2); // 9
}

let generator = gen();

alert( generator.next().value ); // "2 + 2?"

alert( generator.next(4).value ); // "3 * 3?"

alert( generator.next(9).done ); // true

generator.throw

function* gen() {
  try {
    let result = yield "What is the best program language?";

    alert("The alert won't be shown");
  } catch(e) {
    alert(e); // this alert will be shown
  }
}

let generator = gen();

alert(generator.next().value); // "What is the best program language?"

generator.throw(new Error("It's a difficult question"));

Modules

An ES6 module is a file containing JS code. There’s no special module keyword; a module mostly reads just like a script. There are two differences.

  • ES6 modules are automatically strict-mode code, even if you don’t write "use strict"; in them.

  • You can use import and export in modules.

export

export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}

export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };

export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;

The export statement is used when creating JavaScript modules to export functions, objects, or primitive values from the module so they can be used by other programs with the importstatement.

import

import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
var promise = import("module-name");

The static import statement is used to import bindings which are exported by another module.

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

let proxy = new Proxy(target, handler);
  • target - a target object to wrap with Proxy. It can be any sort of object, including a native array, a function or even another proxy.
  • handler - an object whose properties are functions which define the behavior of the proxy when an operation is performed on it.

handler.get()

handler.get(target, property, receiver) method is a trap for getting a property value.

const myInfo= {
  name: 'Daniil',
  surname: 'Suleiman'
};

const fakeInfo = {
  get: function(target, prop, receiver) {
    if (prop === 'name') {
      return 'Brad';
    }

    if (prop === 'surname') {
      return 'Pitt';
    }

    return `Do you really need the ${prop}??? ${target.name} ${target.surname} is awesome!`
  }
};

const proxy = new Proxy(myInfo, fakeInfo);

console.log(proxy.name); // Brad

console.log(proxy.surname); // Pitt

console.log(proxy.secret); // Do you really need the secret??? Daniil Suleiman is awesome!

handler.set()

handler.set(target, property, value, receiver) method is a trap for setting a property value.

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 150) {
        throw new RangeError('The age seems invalid');
      }
    }

    // The default behavior to store the value
    obj[prop] = value;

    // Indicate success
    return true;
  }
};

let person = new Proxy({}, validator);

person.age = 100;
console.log(person.age); // 100
person.age = 'young'; // Throws an exception
person.age = 300; // Throws an exception

ES6

By Daniel Suleiman