- Variables
- Temporal Dead Zone (TDZ)
- Destructuring
- [ES6 property value shorthand](#es6-property-value-shorthandhttpsponyfoocomarticleses6-object-literal-features-in-depth)
- Template strings
- Classes
- Arrow functions
- Modules
- Rest parameters and Spread operator
- Objects
- Method definitions in object literals
- Merging objects
- Default parameters
let
-
Used to declare variables
-
It is block scoped E.g
function order(x, y) {
if(x > y) {
let tmp = x;
x = y;
y = tmp;
}
console.log(tmp === x); //ReferenceError: tmp is not defined
return [x, y];
}-
It is mutable (the value can be changed after it has been initialized)
-
When used in a loop
letcreates a new binding for each loop iteration E.g
// ES5
var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
(function(index) {
setTimeout(function() {
console.log(arr[index]);
}, 1000);
})(i);
}
// => 1, 2, 3
// ES6
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
setTimeout(() => console.log(arr[i]), 1000);
}
// => 1, 2, 3
const arr = [];
for (let i = 0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [0,1,2]
const arr = [];
for (let i of [0, 1, 2]) {
arr.push(() => i);
}
arr.map(x => x()); // [0,1,2]- In the code block above (ES6) each
irefers to the binding of one specific iteration and preserves the value that was current at the time. Therefore each arrow function returns a different value
const
- This works similar to let, one major difference is that it must be immediately initialized E.g
const foo; //SyntaxError: missing = in const declaration
const bar = 123;
bar = 456; //TypeError: 'bar' is read-only-
Another major difference is that
constvalues are read-only as it is used to define constant values within your code. -
This means that it creates an immutable binding (the value can't be re-bound after it has been initialized)
-
Using
constit is possible to shadow variables within a function E.g
function func() {
const foo = 5;
if() {
const foo = 10; // shadows outer `foo`
console.log(foo); // 10
}
console.log(foo); // 5
}constcan also be used to declare objects but an important thing to note is that usingconstprevents modification of the binding and not of the value itself. E.g
const foo = {
bar: 'baz'
};
foo.bar = 'foobar' // {bar: foobar}
foo = { // Uncaught TypeError: Assignment to constant variable
bar: 'foobar'
};-
In the code above
foo.bar= 'foobar'changes what theconst(foo) contains but doesn't change the value that it is bound to. So that line will be valid -
The line right below that :
foo = {bar: 'foobar'}tries to reassign a value tofoo(thus attempting to change its binding) and so an error will be thrown -
constprevents modification of the binding, not modification of the bound value -
As opposed to
var, variables declared vialetandconstdon't become properties of the global object
- Refers to the region before a variable (
constlet) is defined. This means that if you try to access a variable in the TDZ it will throw aReferenceErrorinstead of returningundefinedlike a variable declared withvarwould E.g
console.log(test1); // undefined
var test1 = 'foo';
console.log(test2); // ReferenceError: cannot access uninitialized variable
const test2 = 'foo';
console.log(test3); // ReferenceError: cannot access uninitialized variable
let test3 = 'foo';-
You can make use of ES6 Destructuring to extract properties and methods from an object
-
A benefit of this concept is that you can now reference that property or method without using dot notation syntax E.g:
//ES5:
var foo = {
bar: 1,
baz: 2
};
console.log(foo.bar); // 1
console.log(foo.baz); // 2
//ES6:
//object
const foo = {
bar: 1,
baz: 2
};
const { bar, baz } = foo;
console.log(bar); //1
console.log(baz); //2
//array
const letters = ['A', 'B', 'C'];
const [ firstLetter ] = letters;
console.log(firstLetter); // A
const [,, thirdLetter] = ['A', 'B', 'C'];
console.log(thirdLetter); // C
//loop
const arr1 = ['a', 'b'];
for (const [ index, element ] of arr1.entries()) {
console.log(index, element); // 0 a 1 b
}
const arr2 = [
{name: 'Jane', age: 41},
{name: 'John', age: 31}
];
for (const { name, age } of arr2) {
console.log(name, age); // Jane 41 John 31
}
//library
import React, { Component, PropTypes } from 'react';
import { each, omit } from 'lodash';- One caveat with using destructuring is that they can be used to either declare variables or assign to them, but not both
- Similar to destructuring this pattern allows reference to an objects' value using a variable if both the name of the variable and the object key are the same
const foo = 2;
const obj = {
bar: 1,
foo
}
obj.foo // 2
this.setState({ videos }) === this.setSate({ videos: videos});//ES5:
function getCar(make, model, value ) {
return {
make: make ,
model: model,
value: value
};
}
//ES6:
function getcar(make, model, value) {
return {make, model, value};
}- ES6 template literals can be used to concatenate strings and values E.g:
const VideoDetail = ({video}) => {
// const video = props.video;
const videoId = video.id.videoId;
const url = `https://www.youtube.com/embed/${videoId}`;
return (
<iframe className='embed-responsive-item' src={url}></iframe>
);
}//ES5:
function Playlist () {
this.mediaList = [];
this.nowPlayingIndex = 0;
}
Playlist.prototype = {
constructor : Playlist,
add: function (media) {
this.mediaList.push(media);
},
play: function () {
var currentMedia = this.mediaList[this.nowPlayingIndex];
console.log(this.nowPlayingIndex);
currentMedia.play();
},
stop: function () {
var currentMedia = this.mediaList[this.nowPlayingIndex];
currentMedia.stop();
}
}
var playlist = new Playlist;
playButton.addEventListener ('click', function(e) {
playlist.play();
})
function Country(name, traveled) {
this.name = name ? name : 'United Kingdom';
this.traveled = Boolean(traveled);
}
Country.prototype.travel = function() {
this.traveled = true;
};
var france = new Country('France', false);
france.travel();
var unitedkingdom = new Country;
//ES6:
class Playlist {
constructor() {
this.mediaList = [];
this.nowPlayingIndex = 0;
}
play() {
var currentMedia = this.mediaList[this.nowPlayingIndex];
console.log(this.nowPlayingIndex);
currentMedia.play();
}
stop() {
var currentMedia = this.mediaList[this.nowPlayingIndex];
currentMedia.stop();
}
}
const playlist = new Playlist();
class PlaylistMod extends Playlist {
constructor() {
super() // reference to the constructor in `Playlist` class
}
next() {
this.stop();
this.nowPlayingIndex++;
if (this.nowPlayingIndex === this.mediaList.length) {
this.nowPlayingIndex = 0;
}
this.play()
}
prev() {
this.stop();
this.nowPlayingIndex--;
if (this.nowPlayingIndex === -1) {
this.nowPlayingIndex = this.mediaList.length - 1;
}
this.play()
}
}
const playlistMod = new PlaylistMod();
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
toString() {
return super.toString() + ' in ' + this.color;
}
}
const cp = new ColorPoint(25, 8, 'green');
cp.toString(); // '(25, 8) in green'
cp instanceof ColorPoint; // true
cp instanceof Point; // true
typeof Point; // function
class Country {
constructor(name, traveled) {
this.name = name;
this.traveled = false;
}
travel() {
this.traveled = true;
}
}
var france = new Country('France', false);
france.travel();-
Arrow functions don't expose the
argumentsobject -
It automatically binds the lexical context to
thiswithin nested functions E.g:
//ES5:
var sum = function(a, b) {
return a + b;
}
var numbers = [0, 1, 2];
var increment = numbers.map(function(i) {return i++}); // [1, 2, 3]
var module = {
foo: 'bar',
baz: function() {
setTimeout(function() {
console.log(this.foo)
}.bind(this), 1000);
}
}
function UiComponent() {
var self = this;
var button = document.getElementById('theButton');
button.addEventListener('click', function() {
console.log('CLICK');
self.handleClick();
});
}
UiComponent.prototype.handleClick = function() {
...
};
const box = document.getElementById('box');
box.addEventListener('click', function() {
self = this;
this.classList.toggle('opening');
setTimeout(function() {
self.classList.toggle('open');
}, 500);
});
//ES6:
const sum = (a, b) => {
return a + b;
}
const sum = (a, b) => a + 2
const increment = (a) => a++
const arr = [0, 1, 2];
const arrIncrement = arr.map(i => i++); //[1, 2, 3];
const module = {
foo: 'bar',
baz: function() {
setTimeout(() => {
console.log(this.foo)
}, 1000);
}
}
const UiComponent = () => {
const button = document.getElementById('theButton');
button.addEventListener('click', function() {
console.log('CLICK');
this.handleClick();
});
}
const box = document.getElementById('box');
box.addEventListener('click', function() {
this.classList.toggle('opening');
setTimeout(() => {
this.classList.toggle('open');
}, 500);
});
const race = '100m dash';
const winners = ['hunter cash', 'singa song', 'imda bos'];
const result = winners.map((name, i) => ({ // wrap object in parens to indicate an object literal is being returned
name,
race,
position: i + 1
}));
const ages = [23, 44, 60, 80, 90, 30, 70, 65];
const old = ages.filter((age) => age >= 60);//module_1.js
module.exports.foo = function() {
};
module.exports.bar = function() {
};
//module_2.js
import module_1 from './module_1';
//or
import { foo as fooo, bar } from './module_1';-
Rest properties (or parameters) are used to store the remaining own enumerable property keys that haven't been extracted by the destructuring pattern
-
The keys can then be stored into a new object
-
If the parameter can't find any matching elements it matches its operand (...foo) against the empty array
-
The spread operator is similar, it copies own enumerable properties from a given object into a new created one E.g
//ES6:
//rest
let { x, y, ...z } = {x: 1, y: 2, a: 3, b: 4};
console.log(x); // 1
console.log(y); // 2
console.log(z); // {a: 3, b: 4}
const [x, y, ...z] = ['a']; // x = 'a'; y = undefined; z = []
const [x, ...[y, z]] = ['a', 'b', 'c']; // x = 'a'; y = 'b'; z = 'c'
//spread
let n = { x, y, ...z};
console.log(n); // {x: 1, y: 2, a: 3, b: 4} -
The rest parameter is used to extract data while the spread operator is used to insert data into an object
-
More examples:
//ES5:
function logAllArgs() {
for (var i =0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
var arr1 = ['a', 'b'];
var arr2 = ['c', 'd'];
arr1.push.apply(arr1, arr2); // ['a', 'b', 'c', 'd']
var arr3 = ['a', 'b'];
var arr4 = ['c'];
var arr5 = ['d', 'e'];
console.log(arr3.concat(arr4, arr5)); // ['a', 'b', 'c', 'd', 'e']
function checkSubstrings(string) {
for (var i = 1; i < arguments.length; i++) {
if (string.indexOf(arguments[i]) === -1) {
return false;
}
}
return true;
};
function foo(param1, param1) {
if (arguments.length < 2) {
throw new Error('This function expects at least two arguments');
}
return arguments;
};
//ES6:
function logAllArgs(...args) {
for (const arg of args) {
console.log(arg);
}
}
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];
arr1.push(...arr2); // ['a', 'b', 'c', 'd']
const arr3 = ['a', 'b'];
const arr4 = ['c'];
const arr5 = ['d', 'e'];
console.log([...arr3, ...arr4, ...arr5]); // ['a', 'b', 'c', 'd', 'e']
function checkSubstrings(string, ...keys) {
for (var key of keys) {
if(string.indexOf(key) === -1) {
return false;
}
}
return true;
}
function foo(...params) {
if (params.length < 2) {
throw new Error('This function expects at least two arguments');
}
return params;
};// return an object
const personInfo = (name, age, jobTitle) => ({
name,
age,
jobTitle
});
// assign the object to a variable
const hodor = personInfo('hodor', 27, 'door keeper');
// log the object
for (const prop in hodor) console.log(`${prop}: ${hodor[prop]}`);//ES5:
var obj = {
foo: function() {
...
},
bar: function() {
this.foo();
}
}
//ES6:
const obj = {
foo() {
...
},
bar() {
this.foo();
}
}-
Object.assign(target, sources); -
The
Object.assign()method can be used to merge the contents of one object into another. -
The
targetrefers to the object that will receive the content, it could either be an empty literal ({}) or an existing object. -
The source(s) refers to the object(s) whose content will be extracted and merged.
-
Object.assign()overwrites the content of thetargetif the target contains the same property E.g
const obj1 = {name: 'Daisy', age: 30};
const obj2 = {name: 'Charles'};
Object.assign(obj1, obj2);
console.log(obj1); // {name: "Charles", age: 30}
console.log(obj2); // {name: "Charles"}- In ES5 we can simulate default parameters for functions using the logical
ORoperator (||)
// ES5
function foo(param1, param2){
if (param1 === undefined) {
param1 = 10;
}
if (param2 === undefined) {
param2 = 10;
}
console.log(param1, param2);
}
foo(5, 5); // 5 5
foo(5); // 5 10
foo(); // 10 10
foo(0, null); // 0, null- In ES6 we no longer need to check for
undefinedvalues within our function with default parameters
//ES6
function foo(a = 10, b = 10) {
console.log(a, b);
}
foo(5); // 5 10
foo(0, null); // 0 null- Pass a function (
getParam()) as the default parameter inmultiply()
function getParam() {
const number = prompt('Enter a number');
return parseInt(number);
}
function multiply(param1, param2 = getParam()) {
return param1 * param2;
}