https://kangax.github.io/compat-table/es6/
A variable can be either of global or local scope. A global variable is a variable declared in the main body of the source code, outside all functions, while a local variable is one declared within the body of a function or a block. Modern versions allow nested lexical scoping.
The difference is scoping. var is scoped to the nearest function block and let is scoped to the nearest enclosing block, which can be smaller than a function block. Both are global if outside any block.
Also, variables declared with let are not accessible before they are declared in their enclosing block. As seen in the demo, this will throw a ReferenceError exception.
A signal that the identifier won’t be reassigned. Holds a pointer to a space in memory. Pointers can't be changed, but the values of the pointed object can be changed.
const person = {
name: 'Igi'
}
person.name = 'Igis' // updates the name property of the person object, but not the reference!
Let, const and class declarations are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s Lexical Binding is evaluated. In short - you can only access them after the declarations have been evaluated.
ES6 introduces a new way to write functions. When keeping the this reference to the outer environment in ES5, you can use workarounds like the .bind method or create a closure using var self = this. Arrow functions allow you to retain the scope of the caller inside the function, you don’t need to create self = this; closures or use bind.
var fn = function(){};
fn = () => {};
// E.g.
fn = (a,b) => a + b;
fn = (a,b) => {
return a + b;
}
I encountered an issue when binding an event in a class using ES6 syntax and trying to remove the event. To properly unregister an event, there needs to be a reference to the function being registered. This is achieved by reassigning the function and binding (this) inside ES6 classes.
export default class Button{
constructor(){
// The alert box
this.button = document.getElementById('button');
// The reference to this needs to be preserved via bind.
this.alertBox = this.alertBox().bind(this);
this.element.addEventListener('click', this.alertBox)
this.element.removeEventListener('click', this.alertBox)
}
alertBox(){
alert('you clicked!')
}
}
In ES6 - we can assign default values to arguments
someFuncion(someVariable = 'Default Value', somethingSimilar = someVariable) => console.log(someVariable)
someFunction() // logs Default Value
ES6 provides extensions for object literals.
let name = "Mike";
let age = 25;
let skill = "writing";
let skillField = skill;
let obj = {
name,
age,
[skillField]: 'good', // Dynamic field useful for dynamic computed property access
someFunction(){
console.log(`${this.name}`);
}
}
obj.someFunction();
obj.age;
obj['writing'];
obj[skill];
The rest parameter syntax allows us to represent an indefinite number of arguments as an array.
sum(...numbers) => {
let result = 0;
for(let i = 0; i < numbers.length; i++){
result += numbers[i];
}
return result;
}
sum(1,2,3,4); // returns 10;
Spread syntax allows an iterable such as an array expression to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
let numbers = [1,2,3,4,5];
console.log(...numbers) // splits up the array to a list of values
Math.max(...numbers);
testResult = [1,2,3];
for(let testResult of testResults){
console.log(testResult);
}
Allows writing of multi-line strings and interpolation.
`${name} Hello.`
\ can be used to escape the $ sign.
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. A copy is made so the original object is left untouched.
let stuff = [1, 'toby', 'girly', 'sam-smith', 'gary-x']
let [a,b,,x, ...d] = stuff;
// a = 1
// b = 'toby'
// girly doesn't get destructured
// d = ['sam-smith', ['gary-x']]
// defualt values can be assigned as well, e.g. let [a,b=2]
let a = 5;
let b = 10;
[b,a] = [a,b] // b = 5, a = 10
let ob = {
age: 21;
greet: ()=>{
console.log(this.age);
}
another: ()=>{
}
}
let {age, greet, another: greetF} = ob;
// greet() should work
// greetF() is an alias for another()
// age gets destructured as a variable, i.e. age
There are two important Rules, which you need to understand if you're working with ES6 Modules:
Modules are always in Strict Mode (no need to define "use strict") Modules don't have a shared, global Scope. Instead each Module has its own execution context / scope.
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 import statement.
// There are two different types of export. Each type corresponds to one of the above syntax:
// Named exports:
// exports a function declared earlier
export { myFunction };
// exports a constant
export const foo = Math.sqrt(2);
// Default exports (function):
export default function() {}
// Default exports (class):
export default class {}
// Importing
import { cube, foo as bar } from 'my-module';
import defaultClass from 'my-module';
// or better yet
import defaultClass, { cube, foo as bar } from 'my-module'
// Importing everything
import * as something from 'my-module'
JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript. JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.
class Person(name){
constructor(name){
this.name = name;
}
greet(){
alert(`Hello ${this.name}`
}
}
let person = new Person('Igi');
instanceof person // Person
person.__proto__ === Person.prototype // returns true
When inheriting, we use the extends keyword. Prototypes of the child and the inherited class won't be the same, but properties and methods will be extended / carried over.
// Person...
class Staff extends Person {
constructor(name, age){
super(name);
this.age = age;
}
greet(){
super.greet(); // calls the inherited greet function
console.log('Hello');
}
}
let igi = new Staff('Igi', 39);
igi.greet(); // will log twice.
The static keyword defines a static method for a class.
class Helper {
static logSomething(word){
console.log(`${word} something`) ;
}
}
Helper.logSomething('hey'); // should work
Encapsulation includes the idea that the data of an object should not be directly exposed. Instead, callers that want to achieve a given result are coaxed into proper usage by invoking methods (rather than accessing the data directly).
class Person {
constructor(name){
this._name = name;
}
get name(){
return this._name.toUpperCase();
}
set name(value){
if(value.length > 2){
this._name = value;
return;
}
console.log("nah");
}
}
let person = new Person('Igi');
person._name; // returns the name
person.name; // returns the name in upper case
person.name = "Hello world"; // sets the name
person._name = "Hello world"; // also sets the name
Built-in objects can be extended to introduce new functions or properties.
class ConvArray extends Array{
convert(){
// perhaps loop the array via this.forEach
}
}
console.log((new ConvArray()).convert());
Meta programming is programming where the operation targets the behavior of the program itself. In other words, it means that you're able to change (parts of) the behavior of the underyling language. The goal of meta programming is to leverage the language’s own intrinsic capabilities to make the rest of your code more descriptive, expressive, and/or flexible.
The data type "symbol" is a primitive data type having the quality that values of this type can be used to make object properties that are anonymous. This data type is used as the key for an object property when the property is intended to be private, for the internal use of a class or an object type.
A value having the data type "symbol" can be referred to as a "symbol value." In the JavaScript run-time environment, a symbol value is created by invoking the function Symbol(), which dynamically produces an anonymous, unique value. The only sensible usage is to store the symbol and then use the stored value to create an object property. A symbol value may be used as an identifier for object properties; this is the data type's only purpose.
Symbol.for('age') == Symbol.for('age');
Symbol('age') != Symbol('age');
let symb = Symbol.fod('age');
let person = {
name: 'Max',
age: 30
}
person[smyb] = 27;
console.log(person.age); points to the public object key
console.log(person[symb]); // points to the symbol
There are some built-in symbols you may utilize to overwrite default behaviors of JavaScript. Visit Well known symbols to learn more.
class Person{}
let person = new Person();
console.log(person); // logs [object Object]
Person.prototype[Symbol.toStringTag] = 'Person';
console.log(person); // logs [object Person]
Processing each of the items in a collection is a very common operation. JavaScript provides a number of ways of iterating over a collection, from simple for loops to map() and filter(). Iterators and Generators bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of for...of loops.
In computer programming, an iterator is an object that enables a programmer to traverse a container, particularly lists. Iterators probably sound more complex than they are. Basically an iterator has a function – next() – which allows you to output values step by step. Calling next prints the current state (done => false or true, depending on the amount of values left) and the current value.
let array = 123;
typeof array[Symbol.iterator] // The iterator is a function, accessible via a symbol
let it = array[Symbol.iterator]();
console.log(it.next()); // returns { done: false, value: 1 }
// console.log 3 more times to change done to true
Objects can implement iterators.
let person = {
name: 'Igi',
hobbies: ['Watching', 'Learning', 'Playing']
[Symbol.iterator]: function() {
let i = 0;
let hobbies = this.hobbies;
return {
next: function(){
let value = hobbies[i];
i++;
return {
done: i > hobbies.length ? true : false,
value: value
}
}
}
}
}
for(let hobby of person){
console.log(hobby);
} // logs 'Watching', 'Learning', 'Playing'
A function which doesn't necessarily run to the end when executed. Allows us to create logic contained in a function to yield multiple times of which the iterator can iterate over. A generator is created by adding an asterisk in front of the function name.
function *select(){
yield 'hello world';
yield 'hello me';
}
let it = select();
console.log(it.next()); // logs { done: false, value: 'hello world' }
// run next two more times to reach the end, with done being set to true.
function *gen(end){
for(let i = 0; i < end; i++){
try {
yield i;
} catch (e){
console.log(e);
}
}
}
it.next(); // returns 0
// { done: false, value: 0}
it.throw('An error occured'); // Throws an exception and gets caught. Error is logged in the console.
it.return('Something else'); // Overrides returned argument
// { done: false, value: 'Something else'}
A Promise is an object representing the eventual completion or failure of an asynchronous operation. A promise may be created using its constructor. However, most people are consumers of already-created promises returned from functions. Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function.
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Done!') // a function that accepts an argument that gets resolved, i.e. returned
reject('Failed!'); // rejects the promise and returns the passed argument
}, 1500);
});
promise.then((response) => {
console.log(response); // logs Done!
}, (error) => {
console.log(error); // if we get here, it logs Failed! This only occurs for the first then execution
}).then(response){
// do something else via chaining
}.catch((error) => {
// we can also catch errors via a chain golf, this will always run when an error happens
console.log(error);
});
Promise.all([promise1, promise2]).then((response) => {
// When all promises are resolved, we get here
// Response will be an array of the resolved values, e.g. ['tpby', 'smith']
console.log(response);
}).catch((error) => {
console.log(error);
});
Promise.race([promise1, promise2]).then((response) => {}); // only returns the first one!
class Ob1 {
constructor(){
this.a = 1;
}
}
class Ob2 {
constructor(){
this.a = 1;
}
}
let ob1 = new Ob1();
let ob2 = new Ob2();
Object.assign(ob1, ob2); // ob1 will be an instance of Ob1
let ob3 = Object.assign({}, ob1, ob2); // returns a new object instance
Object.setPrototypeOf(ob1, ob2); // sets the prototype of ob1 to ob2
Math, String, Number
Array(5) // returns an array with 5 undefined spaces
Array.of(5,3,2) // returns [5,3,2]
array.from(array, val => val * 2) // multiples all values of array by 2 and returns a new array
array.fill(100) // replaces all values of the array with 100, multiple values will map to array values
array.find(val => val >= 12) // returns all values that are greater or equal to 12
array.copyWithin(1, 2) // copies a value in an array. Copies the specified value to the specified index
array.entries() // returns an iterator (use with .next, or for let statements)
The Map object holds key-value pairs. Any value (both objects and primitive values) may be used as either a key or a value. Instead of using objects for key-value pairs, maps are available in ES6 to provide helpful functions such as set, delete, get and clear.
let cardOne = {
name: 'test 1'
}
let cardTwo = {
name: 'test 2'
}
deck.set('ab', cardOne);
deck.set('bc', cardTwo);
deck.get('ab'); // returns the cardOne object
deck.delete('ab');
deck.clear();
Holds weak references to entries in the map. Optimised for garbage collection. Not enumerable.
The Set object lets you store unique values of any type, whether primitive values or object references.
let set = new Set([1,1,1])
for (element of set){
console.log(element);
} // only returns 1!
The WeakSet object lets you store weakly held objects in a collection.
The Reflect API could be described as a collection or “central place” which houses all kind of object and function related functions (for creation, property management etc.). Some of the functionalities added on the Reflect object where available before on the Object object. But the goal for the future is, to have on central place to store all those methods – the Reflect Object/ API. Therefore, the Reflect API provides useful methods to create, manipulate and query objects and functions in your JavaScript project.
The Proxy API allows you to wrap objects, functions, whatever and trap / handle incoming property accessing, function calls etc. You may think of Proxies as filter or barrier which has to be passed and which may interrupt access on the wrapped element. For example you might wrap a Proxy around an object and set up a trap (that’s what these functions are called) to be triggered whenever something (the source code) tries to access a property of the wrapped object. The Proxy can then interrupt this access and maybe deny it, return another value, run some calculation – whatever you want.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let person = new Person('Max', 27);
let proxy = new Proxy(person, {
// Setup traps here
get: function(target, property, receiver) {
return 'Something else';
}
});
function log(message) {
console.log('Log entry created: ' + message);
}
let proxy = new Proxy(log, {
apply: function(target, thisArg, argumentsList) {
if (argumentsList[0].length < 20) {
return Reflect.apply(target, thisArg, argumentsList);
}
return false;
}
});
proxy('Hello!');
proxy('Hello, this is a very long message!');
`` In this example, the second function call would fail since the message is too long.