ES6 (ES2015) new features

The let keyword

The let keyword

The let statement declares a block scope local variable
Reference: let @mdn

			var i = 999;

			if (true){
				let i = 111;
				console.log(`if block i=${i}`);
			}
			console.log(`main i=${i}`);

			for (let i = 0; i < 3; i++) {
				console.log(`for block i=${i}`);
			}
			console.log(`main i=${i}`);
		
Redeclaring the same variable within the same scope raises a SyntaxError.

			let x;
			let x = 2; // Uncaught SyntaxError: Identifier 'x' has already been declared

			// next example will not through error, as second let declaration is in other scope
			let x;
			{
				let x = 2;
			}
		

The const keyword

The const keyword

The const declaration creates a read-only reference to a value.
It does not prevent the value it holds to be changed, it just says that the variable identifier cannot be reassigned.

			const account = {
				'balance' : 200
			};

			// TypeError: Assignment to constant variable.
			account = {};

			// no error - the object is mutable and const did not prevent that
			account.balance = 300;

			console.log(account.balance);
			// 300

		

The arrow function syntax

The arrow function syntax

arrow functions???

ES6 introduces a new function declaration syntax called arrow notation.
Also known in JavaScript as Arrow functions or Fat arrow functions
In other languages (Python, Java, C#,...) they are know as lambda functions
The term originated from Lisp language
It is essentially syntactic sugar that reduces the number of times you have to type the word function , as well as the number of braces you have to type

Function expression vs arrow syntax:


			const pi = 3.14;

			// function expression syntax:
			let circleAreaExp = function(r){
				return r*r*pi;
			}

			// arrow function syntax:
			let circleAreaArrow = r=>r*r*pi;
		

Basic Syntax

Arrow function syntax always creates an anonymous function.
And this is usually it main use-case.
In order to simplify the examples, we'll assign the arrow function to a variable.

			(param1, param2, …, paramN) => { statements }
		

			const pi = 3.14;

			let circleArea = (r)=>{return r*r*pi};

			console.log(`circleArea(2) = ${circleArea(2)}`);
		

In next slides we'll see how we can shorten the circleArea definition

"single parameter" syntax

If the function takes a single parameter, you can omit the parameter's parentheses:
But note that braces are required for multiple parameters.

			singleParam => { statements }
		

			const pi = 3.14;

			let circleArea = r=>{return r*r*pi};

			console.log(`circleArea(2) = ${circleArea(2)}`);
		

"single expression" syntax

If the function body is a single expression, you can omit curly braces and the return statement

			(param1, param2, …, paramN) => expression
		

			let circleArea = r=>r*r*pi;

			console.log(`circleArea(2) = ${circleArea(2)}`);
		

"single expression" syntax notes

Note again, that when the curly braces are not omitted, then the return is not implied.
In other words, next two syntaxes are not equivallent:
(param1, param2, …, paramN) => expression
(param1, param2, …, paramN) => {expression}

			const pi = 3.14;

			// no return is implied, so the return value will be 'undefined':
			let circleArea = r=>{r*r*pi};

			console.log(`circleArea(2) = ${circleArea(2)}`);
			// undefined
		

lexical "this"

"this" value in arrow functions is lexically scoped in contrast to standard functions, whose "this" value is dynamically scoped.
I.e the value of "this" in arrow functions is this same as the value of "this" in the enclosing scope.
"this" in arrow functions can not be changed with call/apply or bind methods!

lexical "this" - examples


			const obj = {
				'id': 1,
				'exp': function(){
					console.log(this.id);
				},
				'arr': ()=>{console.log(this.id)}
			}


			obj.exp(); // 1
			obj.arr(); // undefined
		

			lexicalThis = this;

			var obj = {
				'exp': function(){
					console.log(this === lexicalThis);
				},
				'arr': ()=>{console.log(this === lexicalThis)}
			}


			obj.exp(); // false
			obj.arr(); // true
		

"this" problem - solution with arrow function


			const Person = function(name){
				this.name = name;

				this.greet = function(name){
					console.log(`Hi ${name}, I'm ${this.name}`)
				};

				this.greetArr = name=>{
					console.log(`Hi ${name}, I'm ${this.name}`)
				};
			}

			const pesho = new Person('Pesho');
			const friends = ['George', 'Ana'];

			friends.forEach(pesho.greet)
			friends.forEach(pesho.greetArr)

			// OUTPUT:
			// Hi George, I'm undefined
			// Hi Ana, I'm undefined
			// Hi George, I'm Pesho
			// Hi Ana, I'm Pesho
		

lexical arguments

the arguments object in arrow function is reference to the arguments object in the enclosing scope

			const logArgs = ()=>{
				for (let i = 0; i < arguments.length; i++) {
					console.log(arguments[i]);
				}
			}

			logArgs(1,2,3);
		

Notes

Arrow functions are always anonymous!
Arrow functions are perfect for callbacks
Arrow functions cannot be used as constructors and will throw an error when used with new

Default parameter values

Default parameter values

Before ES6


			function f(x, y, z){
				var x = x || 1;
				var y = y || 2;
				var z = z || 3;

				console.log(x, y, z); //6,7,3
			}
			f(6, 7);
		

After ES6


			function f(x=1, y=2, z=3){
				console.log(x, y, z); //6,7,3
			}
			f(6, 7);
		

The spread operator

The spread operator

in function call

Converts an iterable object (like array or strings) into a list of values
Useful to turn array into list od arguments, like the old ES5 trick with apply()

			myFunction(...iterableObj);
		

			function foo(a,b,c){
				console.log(`a=${a}, b=${b}, c=${c}`)
			}

			let arr = [1,2,3];

			// pass foo three arguments with spread operator (ES6 way):
			foo(...arr); // a=1, b=2, c=3

			// pass foo three arguments with the apply() method (ES5 way):
			foo.apply(this, arr); //a=1, b=2, c=3
		

in array literals

With spread syntax in array literal we can create a new array using an existing array.
Or concatenate two or more arrays

			[...iterableObj, 4, 5, 6];
		

			let arr = [1,2,3];
			let str = 'abc';

			console.log( [...arr, 9] );       // [1, 2, 3, 9]
			console.log( [9, ...arr] );       // [9, 1, 2, 3]
			console.log( [...arr, ...str] );   // [1, 2, 3, "a", "b", "c"]
		

The rest operator

The rest operator

The last parameter of a function prefixed with " ... " is called as a rest parameter. It collects all remaining arguments and 'condenses' them into a single array element
We can use it to replaces the arguments object. The difference is that the ...rest parameter is an array type
The rest parameter must be provided after the positional parameters.

				function foo(a, b, ...args){
					// a = 1, b = 2, args = [3,4,5]
					console.log(args); //"3, 4, 5"
				}
				foo(1, 2, 3, 4, 5);

				function bar(a,...args, b){
					console.log(args);
				}
				// SyntaxError: Rest parameter must be last formal parameter
			

De-structuring assignments

De-structuring assignments

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Reference: Destructuring_assignment @mdn

Array destructuring - examples


			// assign variables from array:
			let [a, b] = [1,2];
			console.log(`a = ${a}; b = ${b}`); // a = 1; b = 2

			// we can first declare variables, and then destrucure:
			let a,b;
			[a,b] = [1,2]
			console.log(`a = ${a}; b = ${b}`); // a = 1; b = 2

			// same as above - no matter that we pass more elements:
			let [a, b] = [1,2,3,4,5];
			console.log(`a = ${a}; b = ${b}`); // a = 1; b = 2

			// assign variables from array in conjunction with rest syntax:
			let [a, ...rest] = [1,2,3];
			console.log(a);     // 1
			console.log(rest);  // [2, 3]
		

use case: swapping variables values


			let a = 1, b = 2;

			// we do the swap without tmp variable, just with one line:
			[a,b] = [b,a];

			console.log(`a = ${a}; b = ${b}`); // a = 2; b = 1
		

Object destructuring

Destructuring on objects let us bind variables to different properties of an object.
It is a nice syntactical shortcut when the property and variable names are the same:

				let obj = {p1: 1, p2: 2};

				// assign obj properties to variables with same name:
				let {p1, p2} = obj;

				console.log(p1);  // 1
				console.log(p2);  // 2
			
If we need first to declare variables, and then to destrucure, we must use braces:

				let obj = {p1: 1, p2: 2};
				let p1,p2;

				// assign obj properties to variables with same name (note the braces):
				({p1, p2} = obj);

				console.log(p1);  // 1
				console.log(p2);  // 2
			

Example: delete object property


			let obj = {'a':1,'b':2,'c':3}

			// 'c' will go into c, and the rest of obj into obj2
			let {c,...obj2} = obj

			console.log(c);
			console.log(obj2);
		

Example

Imagine, we have next user data as object:

				const userData = {
					id: 1,
					name:'Ada',
					age: 23,
				}
			
and we need to define greet() function, which needs only user name, and user age. Instead of writing this:

				function greet( name, age ){
					console.log(`Hello ${name}. You are ${age} years old!`);
				}

				greet(userData.name,userData.age);
			
we can use:

				// note the curly braces in params declaration
				function greet( {name,age} ){
					console.log(`Hello ${name}. You are ${age} years old!`);
				}

				greet(userData);
			

Note, that such object destructuring (in function call) is a very common pattern used in React!

New object literal features

New object literal features

Shorthand property names

Declaring an object literal with keys that match variables is quite common use case. In such situations property values shorthands is quite useful:

			let userName = 'pesho';
			let userAge = 23;

			// ES6 way:
			let p1 = {userName, userAge};

			// ES5 way:
			// let p1 = {userName:userName, userAge:userAge};

			console.log(p1); // { userName: 'pesho', userAge: 23 }
		

Method definitions

ES6 introduces a shorter syntax for method definitions on objects initializers:

			let p1 = {
				name: 'Pesho',
				greet(){
					console.log(`Hi, I'm ${this.name}`);
				}
			}

			p1.greet(); // Hi, I'm Pesho
		

object.assign() method

object.assign() method

Overview

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.
It will return the target object.
Reference: Object.assign() @mdn

			let source = {a:1, b:2}
			let target = {a:2, c:3}

			let new_object = Object.assign(target, source);

			console.log(target);      // { a: 1, c: 3, b: 2 }
			console.log(new_object);  // { a: 1, c: 3, b: 2 }
		

Shallow vs Deep copy

Object.assign() copies property values, and if they are reference to other objects, then the same reference will be copy.
Which means that with Object.assign() we make a shallow copy of an object.

			let p1 = {
				name: 'Pesho',
				address: {
					town: 'Sofia',
					zip: 1504
				}
			}

			// shallow copy the object:
			let p2 = Object.assign({}, p1);
			console.log(p2.address);     // { town: 'Sofia', zip: 1504 }

			// now change p1
			p1.address.town = 'Plovdiv';

			// check if the change is reflected into p2
			console.log(p2.address); // { town: 'Plovdiv', zip: 1504 } (yes, so we have a shallow copy)
		

Deep copy

The Deep copy on an object means that fields are dereferenced rather than references to objects being copied
For deep copy in JavaScript we can use the JSON methods:
target = JSON.parse(JSON.stringify(source))

			let p1 = {
				name: 'Pesho',
				address: {
					town: 'Sofia',
					zip: 1504
				}
			}

			// create a deep copy
			let p2 = JSON.parse(JSON.stringify(p1));
			console.log(p2.address); // { town: 'Sofia', zip: 1504 }

			// now change p1
			p1.address.town = 'Plovdiv';

			// check if the change is reflected into p2
			console.log(p2.address); // { town: 'Sofia', zip: 1504 } (no, so we have a deep copy)
		

Example: concatenate objects properties


			const obj1 = {
				a:1,
				b:2
			}
			const obj2 = {
				c:3
			}

			// Variant 1: with spread operator:
			const obj3 = {...obj1,...obj2};


			// constiant 2: with Object.assign:
			const obj4 = Object.assign({}, obj1, obj2)

			console.log(obj3);
			console.log(obj4);

			// OUTPUT:
			// { a: 1, b: 2, c: 3 }
			// { a: 1, b: 2, c: 3 }

		

The for...of statement

The for...of statement

Overview

ES6 introduced a new statement for...of that creates a loop that iterates over iterable objects such as: Built-in Array, String, Map, Set, and Array-like objects such as arguments or NodeList
Note: make sure that you do not confuse for..of statement with for..in statement

			for (variable of iterable) {
				 // statements
			}
		

Examples


			let numbers = [1,2,3,4];

			for (const num of numbers){
				console.log(num);
			}
		

			const str = 'abc'

			for(const l of str){
				console.log(l);
			}
		

The Class Syntax

The Class Syntax

Class definitions:

Classes in JS are "special functions" and we can define them using the expression or declaration syntax
All code inside the class construct is automatically interpreted in strict mode.

			// class declaration
			class Person{
				// method definitions
			}
		

			// class expression
			let Person = class {
				// method definitions
			}
		

Constructor

The constructor method is a special method for creating and initializing an object created with a class
It's called automatically whenever a new object is created
There can only be one special method with the name "constructor" in a class

			class Person{
				constructor(name, age){
					this.name = name;
					this.age = age;
				}
			}

			let p1 = new Person('Pesho', 23);
			console.dir(p1);
		

Methods

We can define methods in class declaration using the shorter ES6 method definition syntax.
Note, that methods are created in the class prototype.

			class Person{
				constructor(name, age){
					this.name = name;
					this.age = age;
				}

				greet(){
					console.log(`I'm ${this.name}, ${this.age} years old.`);
				}
			}

			let p1 = new Person('Pesho', 23);
			console.dir(p1);
		

Static methods and properties

We can define a static method for a class, using the keyword static inside the class body
Static methods aren't called on instances of the class. Instead, they're called on the class itself
These are often utility functions, such as functions to create or clone objects.

			class Person{
				constructor(name, age){
					this.name = name;
					this.age = age;
					Person.counter();
				}
				// static method
				static counter(){
					Person.count+=1;
					console.log(`${Person.count} objects were created.`);
				}

				greet(){
					console.log(`I'm ${this.name}, ${this.age} years old.`);
				}
			}
			// static (class) property
			Person.count = 0;

			let p1 = new Person('Pesho', 23);
			let p2 = new Person('Maria', 28);
		

Field declarations

The new field declaration syntax allows us to declare fields up-front, outside the constructor, setting some default value if we need.
Using the fields declaration class definitions become more self-documenting, and the fields are always present.

			class Person{
				name="Anonymous";
				age;

				constructor(name, age){
					this.name = name;
					this.age = age;
				}

				greet(){
					console.log(`I'm ${this.name}, ${this.age} years old.`);
				}
			}

			let p1 = new Person('Pesho', 23);
			let p2 = new Person();
			p1.greet();
			p2.greet();
		

Getters and Setters

The get syntax binds an object property to a function that will be called when that property is looked up.
The set syntax binds an object property to a function to be called when there is an attempt to set that property.

			class Person{
				constructor(name){
					this._name = name || "Anonymous";
				}
				get name(){
					console.log(`getting name at: ${new Date()}`);
					return this._name;
				}

				set name(name){
					console.log(`setting new name at: ${new Date()}`);
					this._name = name;
				}

				greet(){
					console.log(`I'm ${this.name}`); // here we use the getter
				}
			}

			let p1 = new Person('Pesho', 23);
			p1.name = 'Peter'; // here we use the setter
			p1.greet();
		

Sub-classing (inheritance) with extends and super keywords

To create a class as a child of another class we can use the extends keyword in class definition
The super keyword allows to call the parent constructor of the super class. Note that when the supper keyword is used to call the parent's constructor it must be used before the this keyword is used.
The super keyword can be used to access parent properties as well (like calling parent methods), using the super.prop or super[expr] syntax

			class Person{
				constructor(name, age){
					this.name = name;
					this.age = age;
				}

				greet(){
					console.log(`I'm ${this.name}, ${this.age} years old.`);
				}
			}

			class Developer extends Person{
				constructor(name, age, skills){
					// call the parent constructor:
					super(name, age)
					this.skills = skills
				}

				greet(){
					// call the parent greet() method
					super.greet();
					console.log(`My skills are: ${this.skills.join()}`);
				}
			}

			let dev1 = new Developer('Pesho', 23, ['JS','React','Vue'])
			dev1.greet()
		

Exercises

Exercises

Task1: Developer_Manager_Person with Class Syntax- task description

Refactor your solution of Developer_Manager_Person Advanced Task using the Class Syntax
Your new code must produce the same results for same input data, as in the old solution.

Task2: Sort array of strings

Reference: Array.prototype.sort() @mdn

Task3: Sort array of objects by key

In this task, you must supply a compare function to sort() method.
Reference: Array.prototype.sort() - Description @mdn

These slides are based on

customised version of

Hakimel's reveal.js

framework