Novedades de ECMA Script 2015
En este tutorial vamos a repasar las novedades de ECMA Script 2015 (ES6). Es la sexta edición de la especificación del lenguaje ECMAScript. La próxima versión aún está en progreso, así que aprovechamos para refrescar las novedades que nos trajo ECMA Script 2015.
Las funcionalidades más destacadas son módulos, clases, ámbitos de bloque, iteradores, generadores, funciones de flecha, promesas y patrones de desestructuración entre otras.
Variables en ámbitos de bloques
La asignación de variables con let permite que estas variables tengan un ámbito local (por ejemplo dentro de un if) y no sean consideradas variables globales.
Una característica de Javascript anterior es que las variables son movidas al inicio del bloque que las contiene. Es lo que en inglés se denomina "hoisting". Hay que tener en cuenta que en las sentencias for o if, las variables definidas allí no crean un nuevo "scope", sino que son elevadas hacia el inicio del bloque que las contiene.
Las variables definidas con let se pueden volver a asignar un nuevo valor, pero no se pueden volver a declarar, al menos no dentro del mismo bloque de código.
Otro modo de declarar una variable es con la sentencia const. Sirve básicamente para las constantes. De este modo la variable no se puede volver a asignar un nuevo valor. También tienen scope del bloque que las contiene (incluso un for o un if). No son globales.
Funciones y valores por defecto
Una nueva característica que se ha añadido es a la hora de pasar parámetros a las funciones. Anteriormente si una función esperaba un array y no se pasaba ningún valor podía fallar. Ahora se puede especificar un valor por defecto, por ejemplo: conduce(coches = [])
.
En el caso de que el parámetro esperado sea un objeto, se puede definir un valor por defecto con los corchetes, por ejemplo: conduce(coches = {})
.
De este modo evitamos el error pero desconocemos que parámetros contiene el objeto. Para ello se pueden usar los parámetros con nombre: conduce({marca,modelo,color})
.
Luego dentro de la función estos parámetros se acceden como variables locales.
Estos parámetros con nombre pueden tener valores por defecto, undefined, o valores asignados.
Por ejemplo: conduce({marca,modelo,color}={})
.
Otra característica curiosa son las funciones con un número variable de parámetros. Es lo que llaman "rest parameter". Antes se utilizaba la palabra arguments, pero ahora se puede usar ...param. Esto es un array de toda la vida, y se puede recorrer para acceder a los parámetros.
Para invocar pues una función con los elementos de un array se puede realizar como conduce(...coches)
donde cada coche sería un parámetro individual diferente, no un array único como parámetro.
Otra cosa interesante es que se añade la función fecha => para conservar el scope (ámbito) de una variable. Esto es útil en la definición de funciones de callback.
Por ejemplo la función: mifunc (param1, function(p) { ...})
se puede escribir como: mifunc (param1, (p)=> {... })
.
Objetos y Strings
Se puede definir un objeto con sus propiedades directamente: let miobj = { prop1, prop2 };
Los métodos que se definian antes como una función ahora se puede omitir la palabra function:
antes
esAzul: function() { .... }
ahora
esAzul() { .... }
Una nueva funcionalidad son los "template strings", que permiten asignar cadenas multilinea de un modo más fácil.
Por ejemplo: let cadena = `Hola ${variable},
nueva linea,
otra linea,
fin`;
Esta funcionalidad es bastante similar a la que existe en bash shell para Linux.
Un modo de asignar valores por defecto en una función es con el operador lógico OR.
Por ejemplo si tenemos una función que configura un coche:
function configuraCoche(marca, modelo, motores = {}) {
let tipo = motores.tipo || 'gasolina';
let cilindrada = motores.cilindrada || '1.0';
let cv = motores.cv || 90;
}
pero con la nueva especificación podemos definirlo mejor como:
function configuraCoche(marca, modelo, motores = {}) {
let defaults = { tipo: 'gasolina', cilindrada: '1.0' , cv:| 90 };
}
Para copiar propiedades de un objeto a otro se puede usar Object.assign (objeto destino, objeto fuente);
O si queremos conservar los valores del objeto destino: Object.assign ({}, objeto fuente, objeto fuente);
Esto puede ser útil cuando definimos funciones que tienen unos valores por defecto, y pasamos las opciones por parámetro:
function define(coche, motores = {}) {
let defaults = { tipo: 'gasolina', cilindrada: '1.0' , cv:| 90 };
let settings = Object.assign( {}, defaults, options };
}
Arrays
Se puede simplificar el acceso a los elementos del array con la desestructuración.
let coches = ["Ibiza", "Leon", "Toledo"];
let [coche1, coche2, coche3] = coches;
console.log(coche1, coche2, coche3);
En el caso de los bucles hay un nuevo tipo denominado for of:
let coches = ["Ibiza", "Leon", "Toledo"];
for (let coche of coches) {
console.log(coche);
}
Hay una nueva función miarray.find (condición a true) para encontrar elementos dentro de un array.
Maps
Actualmente sólo se pueden usar cadenas como las claves de los mapas. Con la nueva especificación podemos usar objetos.
let mimapa = new Map();
mimapa.set(obj, valor); //para asignar el valor
mimapa.get(obj); //para leer el valor
Adicionalmente también se puede iterar con la sentencia for of que hemos visto antes.
for (let [clave, valor] of mimapa) {
console.log([clave, valor]);
}
Hay un nuevo tipo de Mapa, optimizado para el consumo de memoria que permite utilizar objetos como claves, no tipos primitivos (string, boolean) se denomina WeakMap.
Pero estos tipos de mapas no se pueden iterar con for of.
Se suelen usar para optimizar la memoria, ya que las claves se limpian cuando ya no se usan, cuando el recolector de basura gestiona ese objeto.
Sets
Cuando queremos un array que contenga valores únicos podemos usar un Set.
let miset = new Set();
miset.add("aaa");
miset.add("aaa"); //se ignora
miset.add("bbb");
También se pueden iterar con el for of
for (let cadena of miset){
console.log(cadena)
}
tiene los métodos has(obj) para saber si el Set contiene un objeto y delete(obj) para eliminarlo.
También existe la versión WeakSet por si sólo queremos asignar objetos dentro. Y una vez no se referencian, el recolector de basura los elimina. Son para optimizar la memoria también.
let a = new WeakSet();
a.add("cad"); //error una cadena no es un objeto
a.add({ "id":4} );
como los WeakMap, estos tampoco se pueden iterar, y no tienen métodos para leer los valores, pero si tienen el método has(obj).
Clases
Hasta ahora se podían crear clases con la sentencia function y prototype.
function Coche(marca, modelo) {
this.marca=marca;
this.modelo=modelo;
}
Coche.prototype.precio = function() { ....};
Ahora con la nueva especificación se puede hacer con la sentencia class:
class Coche {
constructor(marca,modelo){
this.marca=marca;
this.modelo=modelo;
}
precio() { ...}
}
Como convención si se pone un guión bajo _ delante del nombre de un método se indica que no se debería llamar desde fuera de la clase.
Es como si fuera privado.
Podemos usar la herencia de classes con la sentencia extends. Así las clases que extienden pueden definir el comportamiento definido en las clases padres sobrescribiendo un método del padre también.
Con super.metodo puedo invocar metodos de la clase padre, y con super() invoco el constructor de la clase padre.
Módulos
Ahora se pueden usar nombres de espacios para separar variables y funciones.
Por ejemplo definimos el fichero milib.js
con export default function(nombre) { .. };
o export function lala(nombre) {...};
y luego para importarla:
import { lala } from './milib';
Es útil para crear un módulo de constantes.
export const LALA=3;
y se importan:
import {LALA} from './constantes';
incluso podemos exportar clases:
export default class Miclase {...};
y la importamos con:
import Miclase from './misclases';
let c = new Miclase();
o con nombres:
class Miclase {...};
export {Miclase};
y la importamos:
import {Miclase} from './misclases';
Promesas
Esto es bastante útil para realizar acciones asíncronas, dada la naturaleza de la web, es muy importante programar correctamente.
Simplifica el uso de callbacks cuando se usan anidados y tenemos bastantes.
Promise constructor=new Promise(resolve, reject);
Incorpora los métodos resolve() y reject()
se usa con:
.then(function() {...})
.catch(function(err) {..})
Iteradores
Se puede implementar un iterador en los objetos.
obj[Symbol.iterator] = function () {
let props = Object.keys(this);
let count = 0;
let isDone = false;
let next = () => {
if (count >= props.length) {
isdone = true;
}
return {done:isDone, value: this[props[count++]] };
}
return {next};
};
Generadores
Nuevas funciones con objetos iterables.
Se usa el *metodo
y yield retorna obj iterado.
Así el ejemplo anterior sería:
= function *() {
for(let p of props) {
yield this[p];
}
En conclusión, con las nuevas funcionalidades y características de ECMA Script 2015 se ha dado un paso importante a la hora de simplificar la programación de Javascript, tanto en la parte cliente (en el acceso al DOM) como en la parte servidora (con node js).