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
-
var declarations are globally scoped or function scoped while let and const are block scoped.
-
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.
-
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.
-
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
ES6
- 1,443