Aprende ECMAScript 2015 si no lo has hecho ya

ECMAScript 2015 (también conocido como ES.next, Harmony, ECMAScript 6 o ES6) es la sexta edición del lenguaje ECMAScript y fue aprobada y publicada por el ECMA General Assembly el 17 de junio de 2015. Al ser JavaScript una implementación del estándar ECMAScript, si eres desarrollador JavaScript, deberías interesarte por el nuevo estándar.

No estamos hablando de algo que se implementará en el futuro, sino que es algo que está aprobado hace más de dos años y que puedes empezar a utilizar en la mayoría de tus proyectos desde hace bastante tiempo.

ECMAScript 2015

¿Por qué aprender ECMAScript 2015?

Quizás te estés preguntando por qué deberías aprender ECMAScript 2015 desde ahora si el código JavaScript que escribes actualmente es (y será) compatible con los navegadores por muchos años. Pues bien, en mi opinión debes aprenderlo, no solo para empezar a escribir un código que en muchos aspectos será más robusto, extensible y preparado para el futuro, sino también porque muchos frameworks basados en JavaScript están adoptando el nuevo estándar (dígase React, Redux o Angular por poner algunos ejemplos) y en un futuro se te hará más difícil adoptar un nuevo framework si no dominas muchas de las características del lenguaje que utilizan.

Si te estás preguntando cómo podrías escribir código en el nuevo estándar y que sea soportado por la mayoría de los navegadores, te diré que la mayoría de los navegadores implementan ya una buena parte del nuevo estándar, y las cosas que no son soportadas, pueden ser solventadas con polyfills o con herramientas más robustas como Babel, que te ayudan a transpilar tu código de ECMAScript 2015 a un estándar anterior.

En este artículo intentaré reunir algunas de las principales mejoras que ha introducido ECMAScript 2015, creo que mostrando estas mejoras te convencerás de que no es muy difícil adoptar el nuevo estándar.

Nuevos métodos

ECMAScript 2015 ha añadido nuevos métodos a los objetos Object, Array, Math y Number así como nuevos métodos en cadenas de texto.

Método assign del objeto Object

El método assign del objeto Object copia los valores de todas las propiedades enumerables de uno o más objetos a un objeto de destino, su implementación es muy sencilla:

var datos = {id: 100, total: 2500};
var colores = {color: "#FFFFFF", background: "#CCCCCC"};
var estilos = {height: "200px", width: "100px", color: "#FF0000"};
Object.assign(datos, colores, estilos);
console.log(datos);
// {id: 100, total: 2500, color: "#FF0000", background: "#CCCCCC", height: "200px", width: "100px"}

Nótese cómo la propiedad color del objeto colores es sobrescrita por la del objeto estilos.

Método findIndex y find del objeto Array

Estos dos métodos trabajan de manera similar (ninguno de los dos transforma el Array). El primero, findIndex, se utiliza para encontrar el primer índice en el Array que cumpla con una condición específica. Mientras que el segundo método, find, se utiliza para encontrar el primer elemento en sí que cumpla dicha condición. La forma de chequear cada elemento del Array para verificar si cumple la condición, es mediante una función que es enviada al método:

var array = [1, 6, 2, 7, 3, 8, 4, 9, 5];

var fi = array.findIndex(function (n) {
    return n * n > 40;
});
console.log(fi); // 3 (la posición 3 corresponde al número 7. 7^2 = 49)

var f = array.find(function (n) {
    return n % 4 === 0;
});
console.log(f); // 8 (es el primer número divisible entre cuatro en el Array)
Nuevos métodos del objeto Math

El objeto Math cuenta ahora con nuevos métodos:

Math.trunc(12.5); // 12
Math.trunc(-12.5); // -12

Math.sign(10); // 1
Math.sign(-10); // -1
Math.sign(0); // 0
Math.sign(-0); // -0
Math.sign(NaN); // NaN
Nuevos métodos del objeto Number

El objeto Number cuenta ahora con nuevos métodos que nos permitirán trabajar con números de una manera más poetente.

var big = Math.pow(10, 9);

Number.isSafeInteger(big); // true
Number.isSafeInteger(big * big); // false

Number.isNaN(10); // false
Number.isNaN(NaN); // true
Number.isNaN(0/0); // true
Number.isNaN("cien"); // false
Number.isNaN("100"); // false
Number.isNaN({}); // false
Number.isNaN(undefined); // false
Number.isNaN("NaN"); // false

Number.isFinite(-Infinity); // false
Number.isFinite(Infinity); // false
Number.isFinite(big); // true
Number.isFinite(NaN); // false
Nuevos métodos en cadenas de texto

ECMAScript 2015 incluye nuevos métodos muy interesantes para trabajar con las cadenas de texto. Aunque algunos de estos métodos se podían emular hasta ahora usando indexOf y lastIndexOf, ahora contamos con métodos nativos para esos propósitos:

var cadena = "ECMAScript 2015 en xprimiendo";

cadena.startsWith("ECMAScript"); // true
cadena.startsWith("ECMAScript", 5); // false
cadena.startsWith("2015", 11); // true

cadena.endsWith("xprimiendo"); // true
cadena.endsWith("xprimiendo", 10); // false
cadena.endsWith("2015", 15); // true

cadena.includes("en "); // true
cadena.includes("en ", 17); // false
cadena.includes("primi", 10); // true

cadena.repeat(2); // ECMAScript 2015 en xprimiendoECMAScript 2015 en xprimiendo

Variables de ámbito de bloque

Hasta ahora, la manera de declarar una varible en JavaScript era con la sentencia var. Todo el que domina este lenguaje sabe que el ámbito de una variable declarada de esta manera es la función que la contiene (exceptuando las variables declaradas fuera de función alguna que son de alcance global).

ECMAScript 2015 introduce let y const, las cuales se comportan de manera diferente a la manera tradicional que teníamos de declarar una variable.

Uso de let

Analizemos el siguiente código:

var es5 = true;

if (es5) {
    var mensaje = "ES5 es true";
}

console.log(mensaje);

En el código anterior, lo mismo es5 que mensaje son variables declaradas fuera de una función, por lo que su ámbito será global. La variable mensaje, sin importar que se haya declarado dentro del bloque de una condición, sigue siendo accesible desde fuera de dicho bloque. Con ECMAScript 2015 esta situación cambia drásticamente:

let es6 = true;

if (es6) {
    let mensaje = "ES6 es true";
}

console.log(mensaje);

El anterior código lanzará un error, ya que se está intentando acceder a una variable no definida. La variable mensaje se ha declarado dentro del bloque de una condición usando let, por lo tanto su ámbito estará circunscrito a dicho bloque, fuera del bloque esta variable no estará definida.

Observemos este otro ejemplo:

for (var i = 0; i < 10; i++) {

    setTimeout(function () {
        console.log(i);
    }, 100);

}

El anterior código crea un ciclo de 0 hasta 9 y en cada iteración crea un setTimeout con una función que lo que hace es simplemente lanzar el valor de la variable i en la consola. Quien no tenga mucha experiencia en JavaScript podría pensar que el resultado final serían números del 0 al 9 impresos en la consola. Pero quien conozca bien este lenguaje y el alcance de las variables, sabrá que la variable i es global y solo cambia su valor en cada iteración del ciclo, por lo que al culminar todos los setTimeout el valor de la misma será el último valor adquirido, en este caso 10. Por lo tanto, el resultado será el número 10 impreso en la consola 10 veces.

Veamos cómo se comporta el mismo código anterior usando let para declarar la variable i:

for (let i = 0; i < 10; i++) {

    setTimeout(function () {
        console.log(i);
    }, 100);

}

En el anterior código, al haberse declarado la variable i con let, el alcance de la misma se circunscribe al bloque conformado por el ciclo. Por lo tanto, en cada iteración se utiliza una instancia diferente de la variable i, dando como resultado números del 0 al 9 impresos en la consola.

Uso de const

Con respecto a let, las variables declaradas con const tienen también un ámbito de bloque. Pero al ser constantes, su principal característica es que no se pueden redeclarar ni se les puede reasignar otro valor. Tampoco pueden compartir nombre con otra función o con otra variable en su mismo ámbito. Por ejemplo, analizemos el siguiente código:

const MAX_SPEED = 100;

/* CUALQUIERA DE LAS SIGUIENTES LINEAS LANZARÁ UN ERROR */

MAX_SPEED = 120; // Uncaught TypeError: Assignment to constant variable.

MAX_SPEED += 20; // Uncaught TypeError: Assignment to constant variable

let MAX_SPEED = 120; // Uncaught SyntaxError: Identifier 'MAX_SPEED' has already been declared

function MAX_SPEED () { return 120; } // Uncaught SyntaxError: Identifier 'MAX_SPEED' has already been declared

Como podemos observar, el uso de constantes nos pueden ayudar bastante cuando queremos crear una referencia a la variable de solo lectura. Esto hará nuestro código más robusto e impedirá que algun error o cambio futuro en el código, redeclare o reasigne otro valor a esas variables.

Hay que tener en cuenta que esto no significa inmutabilidad. Declarar una variable con const no permitirá que el valor de la variable sea reasignado, pero por ejemplo, si la variable declarada con este método es un objeto, este último puede ser variado sin problemas. Observemos el siguiente código:

const DATOS = {id: 100, valor: 35};
const IDS = [100, 200, 300, 400];

/* INTENTAR REASIGNAR EL VALOR (ERROR) */

DATOS = {id: 100, valor: 35, hash: '45sab3cf'}; // (index):49 Uncaught TypeError: Assignment to constant variable
IDS = [100, 200, 300, 400, 500]; // Uncaught TypeError: Assignment to constant variable

/* VARIAR EL VALOR (NINGÚN ERROR) */

DATOS.hash = '45sab3cf';
IDS.push(500);

Creación de nuevos ámbitos de bloque

Como hemos visto, al crear variables con let o const, estas se circunscriben al bloque donde han sido declaradas. Pero ¿qué entendemos como bloque? Con ECMAScript 2015 todo lo que se encuentre entre llaves es un bloque de código, por lo que la siguiente expresión {{}} es válida por sí sola. Observermos el siguiente código:

{
    let a = 10;

    {
        let a = 20;

        console.log(a); // imprimirá 20
    }

    console.log(a); // imprimirá 10
}

Los bloques de código pueden ser creados también para declarar funciones, por lo que el hoisting de las mismas se realizará dentro de dicho bloque de código. Observemos el siguiente ejemplo:

{
    console.log( calcula(2, 3) ); // imprimirá 5

    function calcula (a, b) { return a + b; }

    {
        console.log( calcula(2, 3) ); // imprimirá 6
    
  	function calcula (a, b) { return a * b; }

        console.log( calcula(5, 4) ); // imprimirá 20
    }

    console.log( calcula(5, 4) ); // imprimirá 9
}

Con la llegada de los bloques, ya no hay necesidad de utilizar las IIFE solo para crear un nuevo ámbito de código.

Plantillas de cadenas de texto

Las plantillas de cadenas de texto en ECMAScript 2015 son creadas usando acentos graves. Dichas plantillas nos brindan múltiples posibilidades al trabajar con cadenas de texto, recorramos algunas de estas:

Cadenas de texto multilínea

Una de las posibilidades que nos brinda trabajar con plantillas de cadenas de texto, es escribir texto multilínea dentro del código JavaScript, sin tener que añadir \n al final de cada línea, ni escapar la nueva línea con un caracter \ o concatener las líneas usando el operador +. Todo es tan sencillo como el siguiente ejemplo:

let mensaje = `Este texto contiene
más de una línea y para lograrlo
solo hay que usar acentos graves
en vez de comillas en nuestro código`;
Interpolación de variables y expresiones

Ya no es necesario usar el operador + si deseamos concatenar cadenas de textos con variables o expresiones. Tan solo tenemos que usar la siguiente sintaxis para situar una variable o una expresión dentro del texto: ${variable o expresión}. Observemos el siguiente ejemplo:

let coche = "Tesla Model S";
let velocidad = 180;
let horas = 2;

let mensaje = `Un ${coche} tiene una velocidad promedio
de ${velocidad}Km/h. Por lo tanto en ${horas} horas
puede recorrer ${velocidad * horas} kilómetros`;

console.log(mensaje);

El anterior código lanzará el siguiente mensaje en la consola:

Un Tesla Model S tiene una velocidad promedio
de 180Km/h. Por lo tanto en 2 horas
puede recorrer 360 kilómetros

Uso de funciones de etiquetado

Si quisiéramos usar una función para hacer un postprocesado de una plantilla de cadena de texto y devolver una nueva cadena, lo podríamos lograr usando el llamado etiquetado de plantillas. Observemos un ejemplo de una función preparada para procesar una plantilla en particular y el resultado del postprocesado:

let nombre;
let nota;
let mensaje;

// La función recibirá como primer parámetro un array
// con todas las cadenas contenidas en la plantilla
// el segundo, tercero y hasta n parámetros serán las variables
// o expresiones contenidas en la plantilla

function post (cadenas, nombre, nota) {

    let cad0 = cadenas[0]; // cadena vacía porque las plantillas utilizadas en los ejemplos comienzan por una variable
    let cad1 = cadenas[1];
    let cad2 = cadenas[2];

    let final = `porque${cad1}${nota}${cad2}`;

    if (nota < 5) {
        return `${nombre} suspendió ${final}`;
    } else if (nota < 7) {
        return `${nombre} está aprobado ${final}`;
    } else if (nota < 9) {
        return `${nombre} tiene una nota notable ${final}`;
    } else if (nota < 10) {
        return `${nombre} tiene una nota sobresaliente ${final}`;
    } else if (nota === 10) {
        return `${nombre} tiene matrícula de honor ${final}`;
    }

    return `No se reconoce la nota de ${nombre}`;

}

nombre = "Julio";
nota = 3;
mensaje = post`${nombre} obtuvo ${nota} puntos en el examen`;
console.log(mensaje);
// Julio suspendió porque obtuvo 3 puntos en el examen

nombre = "Carmen";
nota = 7.5;
mensaje = post`${nombre} sacó ${nota} de nota en el examen`;
console.log(mensaje);
// Carmen tiene una nota notable porque sacó 7.5 de nota en el examen

nombre = "José";
nota = 10;
mensaje = post`${nombre} ha obtenido ${nota} puntos en el examen final de curso`;
console.log(mensaje);
// José tiene matrícula de honor porque ha obtenido 10 puntos en el examen final de curso

Operador de propagación

El operador de propagación se representa con tres puntos. Este operador permite expandir los elementos contenidos en un Array. Los siguientes ejemplos muestran ejemplos específicos del uso de este operador:

ECMAScript 5 ECMAScript 2015
var a = [1, 2, 3];
var b = a.concat([4, 5, 6]);
console.log(b); // [1, 2, 3, 4, 5, 6]
let a = [1, 2, 3];
let b = [...a, 4, 5, 6];
console.log(b); // [1, 2, 3, 4, 5, 6]
var a = [1, 2, 3];
var c = [7, 8, 9];
var b = a.concat([4, 5, 6]).concat(c);
console.log(b); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
let a = [1, 2, 3];
let c = [7, 8, 9];
let b = [...a, 4, 5, 6, ...c];
console.log(b); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
Array.prototype.push.apply(array1, array2);
console.log(array1); // [1, 2, 3, 4, 5, 6]
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
array1.push(...array2);
console.log(array1); // [1, 2, 3, 4, 5, 6]
// Copiar los valores de un array a otro array
var array1 = [1, 2, 3];
var array2 = array1.slice();
// Copiar los valores de un array a otro array
let array1 = [1, 2, 3];
let array2 = [...array1];
function ejemplo (a, b, c) {}
var array = [1, 2, 3];
ejemplo.apply(null, array);
function ejemplo (a, b, c) {}
let array = [1, 2, 3];
ejemplo(...array);

Asignación por desestructuración

Esta expresión permite extraer independientemente los valores de objetos de tipo Array u Object para declarar con ellos variables. Los siguientes ejemplos muestran ejemplos específicos del uso de esta sintaxis:

ECMAScript 5 ECMAScript 2015
var array = [1, 2, 3];
var uno = array[0];
var dos = array[1];
var tres = array[2];
let array = [1, 2, 3];
let [uno, dos, tres] = array;
var a = "izquierdo";
var b = "derecho";
var copia = a;
a = b;
b = copia;
let a = "izquierdo";
let b = "derecho";
[a, b] = [b, a];
var animal = {
    nombre : "León",
    zona   : "África",
    genero : "Panthera"   
};

var nombre = animal.nombre;
var zona = animal.zona;
var genero = animal.genero;
let animal = {
    nombre : "León",
    zona   : "África",
    genero : "Panthera"   
};

let {nombre, zona, genero} = animal;
var animal = {
    nombre : "León",
    zonas   : ["África", "India"],
    genero : "Panthera"   
};

var nom = animal.nombre;
var z1 = animal.zonas[0];
var z2 = animal.zonas[1];
var gen = animal.genero;
let animal = {
    nombre : "León",
    zonas   : ["África", "India"],
    genero : "Panthera"   
};

let {nombre: nom, zonas: [z1, z2], genero: gen} = animal;

Mejoras en la declaración de propiedades de objetos

ECMAScript 2015 ha mejorado la manera de declarar las propiedades de los objetos, haciendo que el código sea más fácil de leer y menos repetitivo.

Sintaxis abreviada para propiedades de objetos
let a = 1;
let b = 2;
let c = 3;
let obj = {a, b, c}; // {a: 1, b: 2, c: 3}
Propiedades dinámicas en la declaración de objetos
let prefix = "prop";
let obj = {
    [`${prefix}1`]: "uno",
    [`${prefix}2`]: "dos",
    [`${prefix}3`]: "tres",
}; // {prop1: "uno", prop2: "dos", prop3: "tres"}
Sintaxis abreviada para la declaración de métodos
let obj = {
    sum (a, b) { return a + b; },
    rest (a, b) { return a - b; },
    mult (a, b) { return a * b; },
    div (a, b) { return a / b; }
}
console.log( obj.mult(2, 5) ); // 10

Funciones flecha

Las funciones flecha no son más que una nueva expresión con una sintaxis más corta que una expresión de función convencional. Este tipo de funciones son siempre anónimas, carecen de su propio this o arguments y no contienen una propiedad prototype.

Este tipo de función no puede ser usada para crear un constructor y no es la función más indicada para usar como método de un objeto ya que las referencias a this, en muchos casos no harían referencia a dicho objeto.

Para comprender mejor cómo implementar una función flecha, observemos la siguiente tabla:

ECMAScript 5 ECMAScript 2015
function () {
    return Math.random();
}
() => Math.random();
function (n) {
    return n * 2;
}
n => n * 2;
function (a, b) {
    return a + b;
}
(a, b) => a + b;
function (value) {
    return {prop: value};
}
 value => ({prop: value});
var numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var cuadrados = numeros.map(function (n) {
    return n * n;
});
var numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var cuadrados = numeros.map(n => n * n);

Al carecer las funciones flecha de su propio this, son muy útiles para tomar el this del contexto en el que están creadas. Esto evita tener que crear una variable adicional para acceder a esta propiedad o utilizar el método bind:

ECMAScript 5 ECMAScript 2015
function Pares (array) {

    this.pares = [];

    var that = this;

    array.forEach(function(n) {

        if (n % 2 === 0) {
            that.pares.push(n);
        }

    });

}

var p = new Pares([1, 2, 3, 4, 5]);
function Pares (array) {

    this.pares = [];

    array.forEach(n => {

        if (n % 2 === 0) {
            this.pares.push(n);
        }

    });

}

let p = new Pares([1, 2, 3, 4, 5]);
function Clicks (elemento) {

    this.number = 0;

    elemento.addEventListener("click", (function () {

        this.number++;
        elemento.innerText = this.number;

    }).bind(this));

}

var elemento = document.getElementById("cont");
var c = new Clicks(elemento);
function Clicks (elemento) {

    this.number = 0;

    elemento.addEventListener("click", () => {

        this.number++;
        elemento.innerText = this.number;

    });

}

let elemento = document.getElementById("cont");
let c = new Clicks(elemento);

Parámetros por defecto en las funciones

Con ECMAScript 2015 podemos declarar el valor por defecto que adquirirá el parámetro de una función si este no se envía o su valor es undefined. Esto nos evita el tener que redeclarar la variable al inicio de la función si su valor es undefined (lo cual había sido una práctica habitual cuando se quería dar valores por defecto a parámetros opcionales de una función):

function multiplicar (array, multiplo = 1) {
    return array.map(n => n * multiplo);
}

console.log( multiplicar([1, 2, 3, 4, 5], 2) ); // [2, 4, 6, 8, 10]
console.log( multiplicar([1, 2, 3, 4, 5]) ); // [1, 2, 3, 4, 5]

Parámetros rest

Los parámetros rest nos permiten tratar un número indefinido de parámetros enviados a una función como un array. Solo debemos anteponer operador de propagación al parámetro de la función y este será tratado como un Array:

function impares (...numeros) {
    return numeros.filter(n => n % 2 !== 0);
}

console.log( impares(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) ); // [1, 3, 5, 7, 9]

Utilizar los parámetros rest nos evitan tener que hacer un slice del objeto arguments si queremos acceder al resto indefinido de parámetros enviados a una función:

ECMAScript 5 ECMAScript 2015
function ejemplo (a, b){
    var rest = Array.prototype.slice.call(arguments, 2);
    // código
}
function ejemplo (a, b, ...rest){
    // código
}

Y mucho más…

Esto es solo una pequeña muestra de todas las mejoras que trae ECMAScript 2015. Podríamos escribir un infinito artículo con todas las mejoras que no hemos mencionado, entre ellas, promesas, clases, módulos, símbolos, iterables, generadores, por mencionar solo algunas de ellas. El propósito de este artículo es despertar la curiosidad de quien no se ha decidido todavía en aprender ECMAScript 2015. Ojalá lo haya logrado. Espero que este artículo te haya servido de ayuda en tu aprendizaje.

(1 votos, promedio: 5,00 de 5)
Comparte este artículo:

Puedes situar fragmentos de código dentro de etiquetas <pre></pre> y código HTML o XML entre etiquetas <xmp></xmp>.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *


El periodo de verificación de reCAPTCHA ha caducado. Por favor, recarga la página.