- 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
let
creates 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
i
refers 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
const
values 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
const
it 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
}
const
can also be used to declare objects but an important thing to note is that usingconst
prevents 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 -
const
prevents modification of the binding, not modification of the bound value -
As opposed to
var
, variables declared vialet
andconst
don't become properties of the global object
- Refers to the region before a variable (
const
let
) is defined. This means that if you try to access a variable in the TDZ it will throw aReferenceError
instead of returningundefined
like a variable declared withvar
would 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
arguments
object -
It automatically binds the lexical context to
this
within 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
target
refers 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 thetarget
if 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
OR
operator (||
)
// 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
undefined
values 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;
}