ES6 / ES2015
ES6 / ES2015
Características principales de JS
¿Qué es ES6?
ECMAScript v6 (abreviado como ES6 o ES2015)
es el nuevo estándar de JavaScript desde Junio de 2015.
En 1997 se crea un comité técnico (TC39) para estadarizar JavaScript por la ECMA .
Se diseña el estándar del DOM (Document Object Model) para evitar incompatibilidades entre navegadores.
A partir de entonces los estándares de JavaScript se rigen por ECMAScript
ES6 pasa a ser la mayor revisión en más de 15 años ya que actualiza y mejora funcionalidades, sintaxis y semántica que Javascript lleva arrastrando desde su versión 3.
Harmony
ES6 ó ES2015
Versionado: finalmente ES6 se acaba llamando ES2015 porque se decide sacar una versión anual. Sigue un proceso de estandarización en el que la introducción de nuevas características se divide en etapas (0-4).
Todo va avanzando sin dependencia entre las propuestas durante un año y cuando acaba ese año todo lo que esté en la etapa 4 es lo que se libera para esa versión.
- Stage 0: borrador, primer nivel de propuesta, idea. (Strawman)
- Stage 1: Se decide que se va a trabajar en ella (Proposal)
- Stage 2: Especificación inicial (Draft)
- Stages 3: Especificaciones completas e implementaciones iniciales en navegador. (Candidate)
- Stage 4: la propuesta está lista para ser incluida en la release anual que será el nuevo estándar. (Finished)
Tabla de compatibilidades
Realmente no nos vamos a preocupar de si trabajamos con 2015, 2016 o 2017 sino si la característica que queremos utilizar es compatible con el navegador.
Esto no significa que no puedas comenzar a escribir en ES6 desde hoy mismo. Veremos cómo
Transpiladores
Como el nivel de soporte del nuevo estándar no es el mismo en todos los navegadores, y todos soportan mucho mejor ES5, han surgido unas herramientas para convertir nuestro código Javascript ES6 a ES5
Los transpiladores son programas capaces de traducir el código de un lenguaje para otro, o de una versión para otra.
No compilan, solo traducen.
translator + compiler
Principales Novedades ES6
let / const vs. var
ES5 -> La única forma de declarar variables era con la palabra reservada 'var'.
Las variables declaradas así podían ser de ámbito (scope):
Había un caso especial. Si declarábamos una variable sin 'var' ni nada que la precediera dentro de una función, esta variable pasaba a tener ámbito global.
(function-level scope)
function miFuncion() {
console.log(miVar); // miVar es undefined (está declarada, pero no tiene valor asignado)
if (true) {
var miVar = "Hola mundo";
}
console.log(miVar); // miVar es 'Hola mundo' (está declarada y tiene valor)
};
var
(function-level scope)
En ES5 cuando definíamos una variable en el interior (scope) de una función, el intérprete interno pasaba a declararla al comienzo de su contexto de ejecución (la eleva);
hoisting
var x = 5;
(function () {
console.log("x:", x); //se espera 5
var x = 10;
console.log("x:", x); //se espera 10
}());
var x = 5;
(function () {
var x; // <- la variable es elevada
console.log("x:", x); //-> undefined
x = 10;
console.log("x:", x); //-> 10
}());
Cuando creamos una función utilizando la sintaxis function declaration, la declaración y la definición de la función son elevadas, sin embargo cuando utilizamos la sintaxis para function expression, sólo se eleva la variable, pero no su valor.
console.log("fn1", fn1()); //que imprime?
console.log("fn2", fn2()); //que imprime?
//function expression
var fn1 = function() { return 1; };
//function declaration
function fn2() { return 2; }
var fn1; // <- la variable es elevada
function fn2() { return 2; } // <- declaración
// y definición son elevadas
console.log("fn1", fn1()); //throw TypeError:
// undefined is not a function
console.log("fn2", fn2()); //2
//function expression
fn1 = function() { return 1; };
hoisting
Con ES6 aparece una nueva forma de declarar variables, utilizando la palabra reservada let, siendo su scope a nivel de bloque (todo lo que queda entre dos llaves {})
(block-level scope)
let / const
for (const i of [0, 1, 2...]) {
console.log(i);
}
// en un for...of se crea un bucle para iterar a través de los elementos de objetos iterables
// (Array, Map, Set, el objeto arguments,...) podríamos declarar la variable iteradora con 'const'
// porque a cada vuelta, se está creando un nuevo scope y distintas constantes 'i'
También es novedad poder crear constantes, usando const, que son solo read-only e inmutables a lo largo del programa. Y como con let, también tienen scope a nivel de bloque. Las constantes además deben inicializarse.
TIP: Nuestro código debería tener más declaraciones 'const' que 'let'
(block-level scope)
let / const: consejos
Intenta declarar siempre tus variables antes de necesitar usarlas, en ES6 no hay hoisting y si no la tienes declarada antes de su uso obtendrás un 'Reference Error'.
Si necesitas tener hoisting en una función, recuerda que solo se mantiene para las declaradas en forma de declaración, no para las expresiones (funciones anónimas) ni para las arrow functions que veremos a continuación.
// Declaration: // Expression: // Expression with arrow:
function foo() {} const foo = function () {}; let foo = () => {};
arrow function ()=>
Funciones anónimas escritas de una forma más compacta que además no generan su propio this. Por lo que estaremos haciendo referencia al bloque en el que está definida.
var sum = function (a,b){
return a + b;
}
let sum = (a, b) => a + b;
TIP: Si solo le pasamos 1 parámetro a la función no se necesitan los paréntesis.
Si no pasamos parámetros, se ponen los paréntesis vacíos ().
Si tenemos que ejecutar más de una sentencia, las pondremos entre llaves y usaremos la palabra reservada 'return' para el retorno de la función
lambdas
arrow function ()=>
lambdas
let getAda = () => ({name: 'Adajs'});
(() => {
console.log('Ok');
})()
(function(){
console.log('Ok')
})();
algo más sobre el this
El this en Javascript se comporta de modo diferente a otros lenguajes.
En Javascript todo son objetos, incluidas las funciones y métodos.
Cada función define su propio valor del this (un nuevo objeto en el caso de un constructor, undefined en llamadas a funciones en modo estricto, el objeto contextual si la función se llama como un "método de un objeto", etc).
En general,
Su valor, lejos de ser intuitivo, se asocia a cada uno de los ámbitos/scopes desde el que se invoca la función.
algo más sobre el this
function Persona() {
// El constructor Persona() define `this` como una instancia de sí mismo.
this.edad = 0;
setInterval(function crecer() {
// En modo no estricto, la función crecer() define `this`
// como el objeto global, el cual es diferente al objeto `this`
// definido por el constructor Persona().
this.edad++;
}, 1000);
}
var p = new Persona();
_this_ dentro de un handler apunta al elemento que ha provocado el evento
_this_ dentro de un método de un objeto apunta al objeto
en ES6, con las arrows functions, el this siempre apunta al objeto padre. Si queremos apuntar al elemento que ha provocado el evento lo podemos hacer con event.target
Con las arrow functions, () =>
la asociación entre el contexto y el valor del this, se produce de forma nativa,
por lo que el código funciona como se espera.
Parámetros por defecto
Permiten que los parámetros de la función sean inicializados con valores por defecto, por si no se le pasan valores o los valores pasados son undefined.
En JavaScript, los parámetros de funciones son por defecto undefined. En algunas situaciones puede ser útil colocar un valor por defecto diferente.
function setBackgroundColor(element, color = 'pink') {
element.style.backgroundColor = color;
}
setBackgroundColor(myDiv); // color configurado a 'pink'
setBackgroundColor(myDiv2, undefined); // color también configurado a 'pink'
setBackgroundColor(miSpan, 'blue'); // color configurado a 'blue'
Parámetros por defecto
TIP: A tener en cuenta que si invocamos al objeto arguments de la función nos retornará el número exacto de parámetros pasados, no los default.
var myFunc = function ( x = 10, y = 20, z = 30 ) {
var sum = x + y + z;
return sum + ' (' + arguments.length + ' arguments passed)';
};
console.info( myFunc( 1, 2, 3 ) ); // 6 (3 arguments passed)
console.info( myFunc( 1, 2, undefined ) ); // 33 (3 arguments passed)
console.info( myFunc( 1, undefined ) ); // 51 (2 arguments passed)
Parámetros Rest
Una de las nuevas funcionalidades es poder agrupar los argumentos que llegan a nuestra función en un array.
Es muy útil para cuando no sabemos exactamente cuántos parámetros le vamos a pasar a nuestras funciones por ejemplo.
Está estrechamente relacionado con el objeto arguments, aunque presenta algunas diferencias.
La forma de implementarse recuerda a Ruby
function ( , ...)
Parámetros Rest
Esta función, con sus peculiares tres puntos delante del tercer argumento, estarían indicándole al intérprete que ese valor debe ser un array compuesto por los parámetros que lleguen desde la llamada siguiendo la siguiente lógica:
let myFunc = function ( foo, bar, ...theArgs ) {
console.info( 'foo: ', foo );
console.info( 'bar: ', bar );
console.info( 'theArgs: ', theArgs );
}
myFunc( 'myParam1', 'myParam2', 'myParam3', 'myParam4', 'myParam5' );
// Retorna:
// foo: myParam1
// bar: myParam2
// theArgs: ["myParam3", "myParam4", "myParam5"]
Parámetros Rest
Destructuring
Expresión que nos permite descomponer un array u objeto
para asignarlo a un conjunto de variables.
Si vamos a descomponer las propiedades de un objeto, es importante que las variables a las que van a parar tengan el mismo nombre que las propiedades que queremos asignar.
let foo = ["uno", "dos", "tres"];
// sin destructuring
let uno = foo[0];
let dos = foo[1];
let tres = foo[2]; // asignación en tres lineas
// con destructuring
let [uno, dos, tres] = foo; // asignación en una sola linea
Destructuring
Gracias a destructuring assignment, las funciones pueden retornar multiples valores. Aunque en JavaScript siempre ha sido posible regresar un arreglo de una función, esto agrega más flexibilidad.
function f() {
return [1, 2];
}
let a, b;
[a, b] = f();
console.log("A es " + a + " ,B es " + b);
// A es 1, B es 2
swap
Qué creéis que está pasando aquí?
var a = 1;
var b = 3;
[a, b] = [b, a];
Estamos cambiando el orden de las variables. Después de ejecutarlo
b será igual a 1 y a igual a 3.
Sin destructuring assignment, cambiar el orden de dos variables
requiere una variable temporal
Spread Operator
Nos permite pasar un array de elementos a una función, convirtiendo cada uno de los elementos en un argumento.
Se podría pensar en que es la versión inversa de los parámetros rest.
Lo podemos usar junto con el new para instanciar.
function f(x, y, z) { }
let args = [0, 1, 2];
f(...args); //f(0, 1, 2);
let parts1 = ['shoulder', 'knees'];
let parts2 = ['chest', 'waist'];
let lyrics = ['head', ...parts1, ...parts2, 'and', 'toes'];
//lyrics = ['head', 'shoulder', 'knees', 'chest', 'waist', 'and', 'toes']
Template Strings
Con ellas, tenemos una nueva forma de escribir strings en JavaScript.
Es bastante cómoda y legible.
Nos permite tener cadenas multilínea, sin tener que usar '\n' e interpolar variables evitando que tengamos que concatenarlas usando el '+'.
Nos pueden servir para construir templates (plantillas html dinámicas).
Se le indican al intérprete de Javascript usando los acentos
graves `` (backticks).
Para meter valores de variables o resultados de fs. puedes usar ${var/función}
ejemplos de Template Strings
let nombre = 'Adas';
let mensaje = `1. Ésta es una línea normal
2. Hola ${nombre}!`;
console.log(mensaje);
// 1. Esta es una línea normal
// 2. Hola Adas!
let getNombres = () => 'Pepa y Pepe';
let texto = `Los nombres son: ${getNombres()}
La suma de 5 + 7 da ${5+7}`;
console.log(texto);
// Los nombres son: Pepa y Pepe
// La suma de 5 + 7 da 12
const data = {
nombre: "Google Pixel L",
imagen: "http://example.com/miImagen.png",
precio: 699,
};
const miProducto = `<article>
<h1>${data.nombre}</h1>
<img src="${data.imagen}" />
<span>${data.precio} €</span>
</article>`;
console.log(miProducto);
/* "<article>
<h1>Google Pixel L</h1>
<img src=http://example.com/miImagen.png />
<span>699 € </span>
</article>"
*/
Fácil, no?
Promesas
Promesas
Las promesas nos permiten manejar operaciones asíncronas de forma síncrona
Una promesa representa el resultado a una operación que estará disponible en algún momento en el futuro
Las promesas se utilizan en JavaScript desde hace años gracias a librerías como Q, Bluebird, RSVP, o incluso jQuery
Promesas
Al crear una promesa, el constructor nos proporciona dos handlers o funciones callback como parámetros
new Promise((resolve, reject) => {
if (/* operación satisfactoria */) {
resolve(/* resultado */);
} else { // operación fallida
reject(/* razón */);
}
})
Promesas
Una promesa tendrá en todo momento uno de estos 3 estados:
El resultado de una promesa es inmutable.
La promesa garantiza que siempre obtendremos su resultado, independientemente de cuando lo consultemos (tanto si está pendiente como si ya se resolvió)
Consumir promesas
Para obtener el resultado de una promesa deberemos registrarle handlers mediante los métodos then y catch, que son encadenables.
El método then acepta dos parámetros:
El método catch tan sólo acepta un parámetro, que es la función que se ejecutará cuando la promesa se rechace
Consumir promesas
function sumaConEspera (a, b) {
return new Promise((resolve, reject) => {
if (a === null || b === null) {
return reject('Booooh!');
}
setTimeout(() => {
resolve(a + b);
}, 500)
})
}
sumaConEspera(4, 5)
.then(console.log) // la consola mostrará '9' tras 0.5 segundos
.catch(console.log) // nunca se ejecutará
sumaConEspera(null, 5)
.then(console.log) // nunca se ejecutará
.catch(console.log) // la consola mostrará 'Booooh!'
Consumir promesas
const p = new Promise((resolve, reject) => {
const random = Math.round(Math.random() * 10)
if (random >= 5) resolve(random)
else reject('suspendido!')
}
p.then(resultado => console.log(resultado),
error => console.log(error));
p.then(resultado => console.log(resultado))
.catch(error => console.log(error))
[...]
p.then(null,
error => console.log(error));
p.catch(error => console.log(error));
Operaciones asíncronas con promesas
function httpRequest (url) {
const headers = new Headers();
headers.append('Content-Type', 'application/json');
return new Promise((resolve, reject) => {
fetch(url, {
method: 'GET',
headers
})
.then(response => {
resolve(response.json())
})
.catch(reject)
})
}
httpRequest('https://jsonplaceholder.typicode.com/users/1')
.then(data => console.log(data.name)) // la consola mostrará 'Leanne Graham'
.catch(console.error)
Promise.all()
Cuando necesitamos trabajar con los resultados de diversas operaciones asíncronas, tenemos que esperar a que todas ellas se hayan procesado para proseguir.
Promise.all() recibe un array de promesas, devolviendo a su vez una nueva promesa que se resolverá cuando todas las anteriores se hayan resuelto; si una sola de las promesas es rechazada, también lo será Promise.all()
Promise.all()
const urls = [
'https://jsonplaceholder.typicode.com/users/1',
'https://jsonplaceholder.typicode.com/users/2',
'https://jsonplaceholder.typicode.com/users/3'
]
const promesas = urls.map(httpRequest) // el método que hemos definido anteriormente
const promesaDefinitiva = Promise.all(promesas)
promesaDefinitiva
.then(resultados => {
resultados.forEach(res => console.log(res.name))
/* la consola mostrará
Leanne Graham
Ervin Howell
Clementine Bauch
*/
})
.catch(console.error)
Clases
Las clases de JavaScript són, en esencia, azúcar sintáctico sobre la ya existente herencia basada en prototipos.
Con la palabra reservada class ES6 introduce una forma mucho más sencilla y una sintaxis más clara para definir objetos y trabajar con herencia.
class Rectangulo {
constructor(alto, ancho) {
this.alto = alto;
this.ancho = ancho;
}
calcArea () {
return this.alto * this.ancho;
}
}
function Rectangulo (alto, ancho) {
this.alto = alto;
this.ancho = ancho;
}
Rectangle.prototype.calcArea = function () {
return this.alto * this.ancho;
}
Constructor
La clase puede tener un método constructor, que se ejecuta para crear e inicializar una instancia de la misma.
Utilizamos la palabra reservada new para crear instancias de una clase.
class Rectangulo {
constructor(alto, ancho) {
this.alto = alto;
this.ancho = ancho;
}
}
const p = new Rectangulo(4, 5);
console.log(p.alto); // la consola mostrará '4'
console.log(p.ancho); // la consola mostrará '5'
Definición de métodos
Los métodos son las funciones que expone la clase a todas sus instancias.
class Rectangulo {
constructor(alto, ancho) {
this.alto = alto;
this.ancho = ancho;
}
calcArea () {
return this.alto * this.ancho;
}
}
const r = new Rectangulo(4, 5);
console.log(r.calcArea()); // la consola mostrará '20'
Definición de métodos
La nueva sintaxis de clase de ES6 no permite definir métodos ni variables privados, a diferencia de la sintaxis clásica, que se aprovecha de las clausuras para conseguirlo.
var Rectangulo = (function () {
function Rectangulo (alto, ancho) {
var perimetro = alto*2 + ancho*2;
this.alto = alto;
this.ancho = ancho;
function area () {
return this.alto * this.ancho;
}
this.calcArea = area;
this.calcPerimetro = function () {
return perimetro;
}
}
return Rectangulo;
} ())
var r = new Rectangulo(4, 5);
console.log(r.calcArea()); // la consola mostrará '20'
console.log(r.calcPerimetro()); // la consola mostrará '18'
getters y setters
Los métodos get y set nos permiten usar la notación estándar de acceso a propiedades para lectura y escritura (como llamar a una función sin tener que usar los paréntesis), permitiendo a la vez personalizar la forma en que la propiedad es recuperada y mutada.
class Cuadraro {
constructor(lado) {
this.lado;
}
get area () {
return this.lado * this.lado;
}
}
const r = new Cuadrado(5);
console.log(r.area); // la consola mostrará '25'
class Cuadrado {
constructor(lado) {
this.lado = lado;
}
set area (a) {
return this.lado = Math.sqrt(a);
}
}
const c = new Cuadrado(5);
console.log(c.lado); // la consola mostrará '5'
c.area = 9;
console.log(c.lado); // la consola mostrará '3'
Métodos estáticos
La palabra reservada static sirve para definir métodos estáticos en una clase.
Los métodos estáticos se llaman sobre su clase sin necesidad de instanciarla, y por lo tanto no pueden acceder a las propiedades ni métodos de la misma. Del mismo modo una instancia de una clase no puede acceder a sus métodos estáticos.
class Rectangulo {
constructor(alto, ancho) {
this.alto = alto;
this.ancho = ancho;
}
static area (alto, ancho) {
return alto * ancho;
}
}
console.log(Rectangulo.area(4, 5)); // la consola mostrará '20'
const r = new Rectangulo(4, 5);
console.log(r.area()); // ERROR!
Los métodos estáticos se suelen utilizar para crear "funciones de utilidad" en aplicaciones
Herencia
class Animal {
constructor (nombre) {
this.nombre = nombre;
}
hablar () {
console.log(`${this.nombre} hace ruido`);
}
}
class Perro extends Animal {
hablar () {
console.log(`${this.nombre} ladra`);
}
}
var p = new Perro('Pocho');
p.hablar(); // 'Pocho ladra'
La palabra reservada extends se utiliza en la definición de clases para crear una clase como "hija" de otra clase. Por eso decimos que la clase hija "hereda" los métodos y propiedades de su clase padre.
La clase hija puede extender a su clase padre añadiendo nuevas propiedades y métodos, así como sobreescribir los métodos originales de su clase padre.
Super
class Animal {
constructor (nombre) {
this.nombre = nombre;
}
hablar () {
console.log(`${this.nombre} hace ruido`);
}
}
class Leon extends Animal {
constructor (nombre) {
super(nombre);
this.colmillos = 4;
}
hablar () {
super.hablar();
console.log(`${this.nombre} ruge`);
}
}
var p = new Leon('Simba');
p.hablar();
// 'Simba hace ruido'
// 'Simba ruge'
Toda instancia de una clase hija puede acceder y llamar a los métodos de su clase padre mediante la palabra reservada super.
Además, si una clase hija define su propio constructor, debe obligatoriamente llamar en el mismo al método constructor de su clase padre, también mediante super.
ejemplos de herencia
class Persona {
constructor (nombre, fuerza = false) {
this.nombre = nombre;
this.sienteFuerza= fuerza;
}
caminar () {
console.log(`${this.nombre} camina`)
}
correr () {
console.log(`${this.nombre} corre`)
}
}
class Jedi extends Persona {
constructor (nombre, status = 'Padawan') {
super(nombre, true);
this.status = status;
}
trucoMental () {
console.log(`${this.nombre} realiza truco mental`)
}
superSalto () {
console.log(`${this.nombre} realiza super salto`)
}
}
const Finn = new Persona('Finn');
const Leia = new Persona('Leia', true);
const Luke = new Jedi('Luke', 'Maestro');
const Rey = new Jedi('Rey');
console.log(Leia.sienteFuerza); // 'true'
console.log(Luke.status); // 'Maestro'
console.log(Rey.sienteFuerza); // 'true'
console.log(Rey.status); // 'Padawan'
console.log(Finn.status); // 'undefined'
Finn.caminar(); // 'Finn camina'
Luke.correr(); // 'Luke corre'
Rey.trucoMental(); // 'Rey realiza truco mental'
Leia.superSalto(); // ERROR!
Módulos
Un módulo es una unidad de código auto contenido en un fichero.
Puede exponer una interfaz pública mediante export.
Puede utilizar código expuesto por otro módulo mediante import.
// fichero 'operaciones.js'
export function sumar (a, b) {
return a + b;
}
export function restar (a, b) {
return a - b;
}
// fichero 'calculadora.js'
import { sumar } from './operaciones';
function calcular (a, b, op) {
switch (op) {
case 'SUMAR':
return sumar(a, b);
[...]
}
[...]
}
¿Por qué usar módulos?
Módulos en JavaScript
Sintaxis complicada
La implementación más popular de este estándar es RequireJS
Diseñado para carga asíncrona
Utilizado principalmente en navegadores web (frontend)
(Asynchronous Module Definition)
Módulos en ES6
La sintaxis de los módulos en ES6 permite dos tipos de exportaciones
// fichero 'circulo.js'
export const pi = 3.14159265;
export function area (radio) {
return pi * radio * radio;
}
// fichero 'main.js'
import { pi, area } from './circulo'
// fichero 'cuadrado.js'
export default function area (lado) {
return lado * lado;
}
// fichero 'main.js'
import area from './cuadrado'
Módulos en ES6
Puedo importar todas las funciones de un módulo y accederlas como propiedades de un objeto
// fichero 'circulo.js'
export const pi = 3.14159265;
export function area (radio) {
return pi * radio * radio;
}
export function diametro (radio) {
return radio * 2;
}
// fichero 'main.js'
import * as circ from './circulo'
function calcular (radio) {
const area = circ.area(radio);
const diametro = circ.diametro(radio);
console.log(`Área: ${area}`);
console.log(`Diámetro: ${diametro}`);
}
Algunos ejemplos más...
// fichero 'foo.js'
export const func1 = () => {
console.log('soy func1')
}
export { func1 as default }
export const func2 = () => {
console.log('soy func2')
}
const func3 = () => {
console.log('soy func3')
}
export { func3 as foo }
// fichero 'bar.js'
import func1, { func2 } from './foo';
[...]
import { func1, func2, foo } from './foo';
[...]
import { foo as func3 } from './foo';
[...]
import * as foo from './foo';
foo.func1(); // 'soy func1'
foo.func2(); // 'soy func2'
foo.func3(); // ERROR!
foo.foo(); // 'soy func3'
Y éste, a grandes rasgos, es nuestro paseo por las novedades de
GRACIAS!!!
Ahora ya podéis volar solas