Qué es

  • ECMAScript es el nombre del estándar internacional que define JavaScript.
  • Definido por un comité técnico (TC-39 ) de ecma international.
  • Identificado como Ecma-262 y ISO/IEC 16262.
  • No es parte de la W3C.
Edición Publicación Cambios
1 1997 Primera edición.
2 1998 Cambios editorales para mentener la especificación completa alineada con el estándar internacional ISO/IEC 16262.
3 1999 Se agregaron expresiones regulares, mejor manejo de strings, nuevo control de declaraciones, manejo de excepciones con try/catch, definición más estricta de errores, formato para la salida numérica y otras mejoras.
4 Abandonado La cuarta edición fue abandonada debido a diferencias políticas respecto a la complejidad del lenguaje. Muchas características propuestas para la cuarta edición fueron completamente abandonadas; algunas fueron propuestas para la edición ECMAScript Harmony.
5 2009 Agrega el modo estricto strict mode, un subconjunto destinado a proporcionar una mejor comprobación de errores y evitar constructores propensos a errores. Aclara varias ambigüedades de la tercera edición, y afina el comportamiento de las implementaciones del "mundo real" que difieren consistentemente desde esa especificación. Agrega algunas nuevas características, como getters y setters, librería para el soporte de JSON, y una más completa reflexión sobre las propiedades de los objetos.
5.1 2011 Está completamente alineada con la tercera edición del estándar internacional ISO/IEC 16262:2011.
Apartir del 2015 las actualizaciones son continuas teniendo una versión anual.
6 2015 ES2015 aka ES6.
7 2016 ES2016 aka ES7.
8 2017 ES2017 aka ES8.
9 2018 ES2018 aka ES9.
10 2019 ES2019 aka ES10.
ESNext 2020 A partir del 2020 las nuevas actualizaciones al estándar simplemente se bautizarán como ESNext.

🔼 Regresar


Babel

Es un compilador de JavaScript, te permite usar el JavaScript del futuro, HOY.

Instalación de paquetes:

npm install -D babel-cli babel-core babel-preset-env

Crear el archivo de configuarción .babelrc

{
  "presets": ["env"],
  "plugins": []
}

Crear el script necesario para compilar ES con Babel en el archivo .package.json:

{
  "name": "taller-es",
  "version": "1.0.0",
  "description": "Aprendiendo ECMAScript",
  "main": "index.js",
  "scripts": {
    "es6": "babel src --watch --out-dir dist"
  },
  "devDependencies": {
    "babel-cli": "^6.24.1",
    "babel-core": "^6.25.0",
    "babel-preset-env": "^1.6.0"
  }
}

Ejecutar el script en la terminal:

npm run es6

🔼 Regresar


Variables y Constantes

Variables de bloque

En ES se agrega una nueva forma de definir variables usando la palabra let, se diferencia de var en que el scope de una variable definida con let es, el bloque en el cual se encuentra la variable y no la función.

let x = "Hola kEnAi";

if (true) {
  let x = "Hola Jon";
  console.log(x); // Imprime en consola Hola Jon
}

console.log(x); // Imprime en consola Hola kEnAi

for (let i = 0; i < 5; i++) {
  console.log(i); // Imprime del 0 al 4
}

console.log(i); // Imprime Uncaught ReferenceError: i is not defined

Constantes

Una constantes es un tipo INMUTABLE, NO puede cambiar una vez definida, se usa la palabra const en lugar de var, al igual que let su scope es de bloque, son tipos de sólo lectura y se le debe asignar un valor en el momento de su declaración. Son referencias inmutables, pero sus valores no necesariamente.

const DIEZ = 10;
DIEZ = 5;
console.log(DIEZ); // Imprime Uncaught TypeError: Assignment to constant variable

const hola = 'hola mundo';
hola = 'hola mundo'; // Imprime Uncaught TypeError: Assignment to constant variable

const PI;
PI = 3.141592653589793; //Imprime Missing initializer in const declaration

const obj = {};
obj.prop = 'x';
console.log(obj); //Imprime { prop: 'x' }
obj.prop = 'y';
console.log(obj); //Imprime { prop: 'y' }

const D = document;
console.log(D); //Imprime el objeto document
console.log(D.documentElement); //Imprime el elemento <html>

🔼 Regresar


Nivel de Bloques

En ES5 los ámbitos de declaración (scope) estaban diseñados a nivel de funciones, con ES6 podemos declarar funciones a nivel de bloque.

En ES6, como en muchos otros lenguajes de programación, el bloque se define entre llaves y genera un nuevo scope (block scope).

//Sin bloques
function f() {
  return 1;
}

console.log(f()); //Imprime 2

function f() {
  return 2;
}

console.log(f()); //Imprime 2

console.log(f()); //Imprime 2

//Con bloques
function f() {
  return 1;
}

{
  console.log(f()); //Imprime 2

  function f() {
    return 2;
  }

  console.log(f()); //Imprime 2
}

console.log(f()); //Imprime 1

🔼 Regresar


Plantillas de Cadenas

Los template string son una forma más fácil de crear:

  • Cadenas con variables dentro (interpolación).
  • Generar cadenas multilínea.
  • Ejecutar expresiones, funciones y etiquetados.
let saludo = `Hola soy un Template String`;
console.log(saludo); //Imprime Hola soy un Template String

//strings multilínea
let mensaje = `No es quien seas en el interior,
tus actos son los que te definen...
Batman`;
console.log(mensaje);
/*
Imprime
No es quien seas en el interior,
tus actos son los que te definen...
Batman
*/

//variables en strings (interpolación)
let nombre = "Jonathan";
console.log(`Hola ${nombre}`); //Imprime Hola Jonathan

//ejecutar expresiones
console.log(`Hola ${nombre}, tienes ${30 + 2} años`); //Imprime Hola Jonathan, tienes 32 años

//ejecutar funciones
let estaciones = ["Primavera", "Verano", "Otoño", "Invierno"],
  ol = `<ol>
    ${estaciones
      .map(function (estacion) {
        return `<li>${estacion}</li>`;
      })
      .join("")}
  </ol>`;

console.log(ol); //Imprime <ol><li>Primavera</li><li>Verano</li><li>Otoño</li><li>Invierno</li></ol>

//función de etiquetado
const etiqueta = function (cadena, variable) {
  console.log(cadena); //Imprime ["Hola ", "", raw: Array[2]]
  console.log(variable); //Imprime Ulises
  console.log(cadena[0] + variable); //Imprime Hola Ulises
};

let otroNombre = "Ulises";

etiqueta`Hola ${otroNombre}`;

🔼 Regresar


Funciones flecha

Es una nueva forma de definir funciones, hay distintas variantes en la sintaxis:

Función de un solo parámetro

Al crear una arrow function de un solo parámetro no es necesario escribír los paréntesis, tampoco es necesario escribír las llaves, esto se puede cuando la función es de una sola línea y devuelve un valor.

//Antes
var saludo = function (nombre) {
  return "Hola " + nombre;
};
console.log(saludo("Jonathan")); //Imprime Hola Jonathan

//Ahora
let saludo = (nombre) => `Hola ${nombre}`;
console.log(saludo("Jonathan")); //Imprime Hola Jonathan

Función de varios parámetros

Cuando la función tenga más de un parámetro es necesario envolver el nombre de estos entre paréntesis.

//Antes
var sumar = function (a, b) {
  return a + b;
};
console.log(sumar(10, 9)); //Imprime 19

//Ahora
let sumar = (a, b) => a + b;
console.log(sumar(10, 9)); //Imprime 19

Función sin parámetros

Cuando la función no reciba parámetros también son necesarios los paréntesis.

//Antes
var saludo = function () {
  return "Hola a tod@s";
};
console.log(saludo()); //Imprime Hola a tod@s

//Ahora
let saludo = () => `Hola a tod@s`;
console.log(saludo()); //Imprime Hola a tod@s

Función con cuerpo

Cuando la función tiene más de una línea (o no devuelve ningún valor) es necesario utilizar las llaves.

//Antes
var fecha = new Date(),
  hora = fecha.getHours();

var saludo = function (hr) {
  if (hr <= 5) {
    return "No me jodas!!!";
  } else if (hr >= 6 && hr <= 11) {
    return "Buenos días!!!";
  } else if (hr >= 12 && hr <= 18) {
    return "Buenas tardes!!!";
  } else {
    return "Buenas noches!!!";
  }
};

console.log(saludo(hora)); //Imprime el saludo dependiendo la hora del día

//Ahora
let fecha = new Date(),
  hora = fecha.getHours();

let saludo = (hr) => {
  if (hr <= 5) {
    return "No me jodas!!!";
  } else if (hr >= 6 && hr <= 11) {
    return "Buenos días!!!";
  } else if (hr >= 12 && hr <= 18) {
    return "Buenas tardes!!!";
  } else {
    return "Buenas noches!!!";
  }
};

console.log(saludo(hora)); //Imprime el saludo dependiendo la hora del día

//Antes
var numeros = [1, 2, 3, 4];

numeros.forEach(function (num) {
  console.log(num); //Imprime el número en turno
  console.log(num * 10); //Imprime el número en turno por 10
});

//Ahora
let numeros = [1, 2, 3, 4];

numeros.forEach((num) => {
  console.log(num); //Imprime el número en turno
  console.log(num * 10); //Imprime el número en turno por 10
});

Contexto Léxico de this

Las arrow functions tienen la capacidad de capturar el objeto this del contexto donde la arrow se ejecuta y así utilizarlo dentro de su bloque de sentencias.

//El problema de `this` Antes
function Persona(nombre) {
  //El constructor Persona() define `this` como una instancia de él mismo
  this.nombre = nombre;
  this.edad = 0;

  setInterval(function () {
    //La función anónima define `this` como una instancia de ella misma
    this.edad++;
  }, 1000);
}

var jon = new Persona("Jonathan");
console.log(jon); //Imprime la edad en 0 por cada segundo que pasa

//La solución al problema de `this` Antes
function Persona(nombre) {
  //Se declara una variable self (algunos prefieren that) para guardar el `this` del constructor Persona()
  var self = this;

  self.nombre = nombre;
  self.edad = 0;

  setInterval(function () {
    //La función anónima define su propio `this` pero el valor que aumenta es edad del `this` de Persona()
    self.edad++;
  }, 1000);
}

var jon = new Persona("Jonathan");
console.log(jon); //Imprime el valor de edad más uno por cada segundo que pasa

//La solución al problema de `this` Ahora
function Persona(nombre) {
  //El constructor Persona() define `this` como una instancia de él mismo
  this.nombre = nombre;
  this.edad = 0;

  setInterval(() => {
    //`this` hace referencia al objeto Persona()
    this.edad++;
  }, 1000);
}

const jon = new Persona("Jonathan");
console.log(jon); //Imprime el valor de edad más uno por cada segundo que pasa
console.log(jon.edad); //Imprime la edad

🔼 Regresar


Objetos literales

Atajos en la escritura de atributos y métodos:

//Antes
var nombre = "kEnAi",
  edad = 4;

var perro = {
  nombre: nombre,
  edad: edad,
  ladrar: function () {
    alert("guau guau!!!");
  },
};

console.log(perro); //Imprime Object {nombre: "kEnAi", edad: 4}
perro.ladrar(); //Manda alerta

//Ahora
let nombre = "kEnAi",
  edad = 4;

const perro = {
  nombre,
  edad,
  ladrar() {
    alert("guau guau!!!");
  },
};

console.log(perro); //Imprime Object {nombre: "kEnAi", edad: 4}
perro.ladrar(); //Manda alerta

Nombres de atributos y métodos calculados (o computados):

let nombreAtributo = "nombre",
  nombreOtroAtributo = "ad",
  nombreMetodo = "ladrar";

const perro = {
  [nombreAtributo]: "kEnAi",
  [`ed${nombreOtroAtributo}`]: 4,
  [nombreMetodo]() {
    alert("guau guau!!!");
  },
};

console.log(perro); //Imprime Object {nombre: "kEnAi", edad: 4}
perro.ladrar(); //Manda alerta

🔼 Regresar


Destructuración

Nuevas formas de asignar valores a Arreglos y Objetos.

//Destructuración de Arreglos
let numeros = [1, 2, 3];

//sin destructuración
let uno = numeros[0],
  dos = numeros[1],
  tres = numeros[2];

console.log(numeros, uno, dos, tres); //Imprime [1, 2, 3] 1 2 3

//con destructuración
let [one, two, three] = numeros;

console.log(numeros, one, two, three); //Imprime [1, 2, 3] 1 2 3

//Destructuración de Objetos
let persona = { nombre: "Jonathan", apellido: "MirCha" };
let { nombre, apellido } = persona;

console.log(persona); //Imprime Object {nombre: "Jonathan", apellido: "MirCha"}
console.log(nombre); //Imprime Jonathan
console.log(apellido); //Imprime MirCha

let datos = { correo: "jonmircha@gmail.com", telefono: 5566778899 };

let { correo: email, telefono: phone } = datos;

console.log(datos); //Imprime Object {correo: "jonmircha@gmail.com", telefono: 5566778899}
console.log(email); //Imprime jonmircha@gmail.com
console.log(phone); //Imprime 5566778899

🔼 Regresar


Parámetros por defecto

Ahora es completamente posible definir un valor por defecto a los parámetros de nuestras funciones al igual que en otros lenguajes de programación.

//Antes
function pais(nombre) {
  nombre = nombre || "Terrestre";
  console.log(nombre);
}

pais(); //Imprime Terrestre
pais("México"); //Imprime México

//Ahora
function pais(nombre = "Terrestre") {
  console.log(nombre);
}

pais(); //Imprime Terrestre
pais("México"); //Imprime México

🔼 Regresar


Parámetros Rest

Los parámetros Rest son una forma de utilizar parámetros virtualmente infinitos, se definen agregando ... adelante del nombre del parámetro rest, éste tiene que ser siempre el último parámetro de la función.

function sumar(a, b, ...c) {
  let resultado = a + b;

  c.forEach((n) => {
    resultado += n;
  });

  return console.log(resultado);
}

sumar(1, 2); //Imprime 3
sumar(1, 2, 3); //Imprime 6
sumar(1, 2, 3, 4); //Imprime 10
sumar(1, 2, 3, 4, 5); //Imprime 15

🔼 Regresar


Operador de propagación

Permite que una expresión sea expandida en situaciones donde se esperan múltiples argumentos o elementos.

let arr1 = [1, 2, 3, 4],
  arr2 = [5, 6, 7, 8];

console.log(arr1); //Imprime [1, 2, 3, 4]
console.log(...arr1); //Imprime 1 2 3 4

arr1.push(...arr2);
console.log(arr1); //Imprime [1, 2, 3, 4, 5, 6, 7, 8]

let superiores = ["hombros", "brazos", "tronco"],
  inferiores = ["pelvis", "piernas", "rodillas"],
  cuerpo = ["cabeza", ...superiores, ...inferiores, "pies"];

console.log(cuerpo); //Imprime ["cabeza", "hombros", "brazos", "tronco", "pelvis", "piernas", "rodillas", "pies"]

console.log(...cuerpo); //Imprime cabeza hombros brazos tronco pelvis piernas rodillas pies

🔼 Regresar


Clases

En ES se incorporan al lenguaje clases para poder hacer Programación Orientada a Objetos más facilmente (sin prototipos), soportan herencia, polimorfismo, superclases, instancias, métodos estáticos, constructores, setters y getters.

Definición de clase, constructor e instancia de objetos:

class Animal {
  //el constructor es un método especial que se ejecuta en el momento de instanciar la clase
  constructor(nombre, edad, genero) {
    this.nombre = nombre;
    this.edad = edad;
    this.genero = genero;
  }

  //métodos públicos de la clase
  comunicar() {
    console.log("Me comunico con sonidos");
  }

  comer() {
    console.log("Ingiero alimentos");
  }

  respirar() {
    console.log("Respiro oxígeno");
  }

  reproducir() {
    console.log("Me reproduzco sexualmente");
  }
}

let lucy = new Animal("Lucy", 20, "Hembra");
console.log(lucy); //Imprime Animal {nombre: "Lucy", edad: 20, genero: "Hembra"}
lucy.comunicar(); //Imprime Me comunico con sonidos
lucy.comer(); //Imprime Ingiero alimentos
lucy.respirar(); //Imprime Respiro oxígeno
lucy.reproducir(); //Imprime Me reproduzco sexualmente

Herencia, polimorfismo, métodos estáticos, setters y getters:

//con la palabra extends la clase Humano hereda de Animal
class Humano extends Animal {
  //el constructor es un método especial que se ejecuta en el momento de instanciar la clase
  constructor(nombre, edad, genero) {
    //con el método super() se manda a llamar el constructor de la clase padre
    super(nombre, edad, genero);
    this.razonar = true;
    this._nacionalidad = "Terrestre";
  }

  //un método estático se pueden ejecutar sin necesidad de instanciar la clase
  static saludar() {
    console.log("Hola soy un Humano");
  }

  //Los setters y getters son métodos especiales que nos permiten establecer y obtener los valores de atributos de nuestra clase
  set nacionalidad(pais) {
    this._nacionalidad = pais;
  }

  get nacionalidad() {
    return this._nacionalidad;
  }

  //métodos públicos de la clase redefinidos gracias al polimorfismo
  comunicar() {
    console.log("Me comunico hablando");
  }

  comer() {
    console.log("Como de todo, soy omnívoro");
  }

  respirar() {
    console.log("Respiro oxígeno con ayuda de mis pulmones");
  }

  reproducir() {
    console.log("Me reproduzco sexualmente, soy mamífero y vivíparo");
  }

  pensar() {
    console.log("Pienso por que tengo intelecto");
  }
}

Humano.saludar(); //Imprime Hola soy un Humano
let jon = new Humano("Jonathan", 32, "Macho");
console.log(jon); //Imprime Humano {nombre: "Jonathan", edad: 32, genero: "Macho", razonar: true, _nacionalidad: "Terrestre"}
jon.comunicar(); //Imprime Me comunico hablando
jon.comer(); //Imprime Como de todo, soy omnívoro
jon.respirar(); //Imprime Respiro oxígeno con ayuda de mis pulmones
jon.reproducir(); //Imprime Me reproduzco sexualmente, soy mamífero y vivíparo
jon.pensar(); //Imprime Pienso por que tengo intelecto
jon.nacionalidad = "México";
console.log(jon.nacionalidad); //Imprime México
console.log(jon); //Imprime Humano {nombre: "Jonathan", edad: 32, genero: "Macho", razonar: true, _nacionalidad: "México"}

🔼 Regresar


Módulos

Antes de ES6, utilizamos bibliotecas como Browserify para crear módulos en el lado del cliente (navegadores), y require en el servidor (Node.js). Con ES, ahora podemos utilizar directamente módulos de todos los tipos (AMD, CommonJS y ECMAScript).

Más información:

Exportando en formato CommonJS

module.exports = 1;
module.exports = { foo: "bar" };
module.exports = ["foo", "bar"];
module.exports = function bar() {};

Exportando en formato ECMAScript

Por nombres:

export let nombre = 'Jonathan';
export let edad  = 33;​​

Por lista de objetos:

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

function restar(a, b) {
  return a - b;
}

export { sumar, restar };

Usando export individualmente:

export function sumar(a, b) {
  return a + b;
}

export function restar(a, b) {
  return a - b;
}

Usando default:

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

function restar( a, b ) {
  return a - b;
}

let operaciones = {
  sumar,
  restar
}

export default operaciones;

//otra forma
export { operaciones as default };

Mejores Prácticas: Utiliza siempre el método export default al final de módulos ECMAScript, esto dejará claro lo que se está exportando y lo que no. En los módulos CommonJS suele exportarse un sólo valor u objeto. Siguiendo con este paradigma, hacemos que nuestro código sea fácil, legible y que podamos combinar entre módulos CommonJS y ECMAScript.

Importando en formato CommonJS

//considerando que existe una carpeta libs y dentro un archivo llamado rutas con extensión .js o .json
const rutas = require("./libs/rutas");

Importando en ECMAScript

Archivo completo:

import "react";
import "./libs/operaciones";

Por nombre de las importaciones:

import React from "react";
import { sumar, restar } from "./libs/operaciones";

También se pueden renombrar las importaciones:

import { sumar as mas, restar as menos } from "./libs/operaciones";

Importación por espacio de nombres:

import * as aritmetica from "./libs/operaciones";

Importar una lista de valores de un módulo:

import * as aritmetica from "./libs/operaciones";

const { sumar, restar } = aritmetica;

Al importar un módulo exportado usando la sintaxis Commonjs (como React) podemos hacer lo siguiente:

import React from "react";
const { Component, PropTypes } = React;

Simplificando:

import React, { Component, PropTypes } from "react";

Nota: Los valores que se exportan son enlaces, no referencias. Por lo tanto, cambiar el enlace de una variable en un módulo, afectará su valor. Evita cambiar la interfaz pública de módulos exportados.

🔼 Regresar


Promesas

Es una manera alternativa a las callbacks para modelar asincronía.

  • Construcción explícita del flujo de ejecución.
  • Separación en bloques consecutivos.
  • Manejo de errores más controlado.
  • Combinación de diferentes flujos asíncronos.
  • Más información.

Promesas en el navegador

function adivinarNumero() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let n = Math.floor(Math.random() * 10);

      return n >= 1 && n <= 5
        ? resolve(`Adivinaste el número: ${n}`)
        : reject(new Error(`No adivinaste el número: ${n}`));
    }, 1000);
  });
}

adivinarNumero()
  .then((num) => console.log(num))
  .catch((error) => console.log(error));

Promesas en el servidor

const fs = require("fs"),
  file = "./nombres.txt",
  newFile = "./nombres_promises_es6.txt";

let promise = new Promise((resolve, reject) => {
  fs.access(file, fs.F_OK, (err) => {
    return err ? reject(new Error("El archivo no existe")) : resolve(true);
  });
});

promise
  .then((dataPromise) => {
    console.log("El archivo existe");

    return new Promise((resolve, reject) => {
      fs.readFile(file, (err, data) => {
        return err
          ? reject(new Error("El archivo no se pudo leer"))
          : resolve(data);
      });
    });
  })
  .then((dataPromise) => {
    console.log("El archivo se ha leído exitosamente");

    return new Promise((resolve, reject) => {
      fs.writeFile(newFile, dataPromise, (err) => {
        return err
          ? reject(new Error("El archivo no se pudo copiar"))
          : resolve("El archivo se ha copiado con éxito");
      });
    });
  })
  .then((dataPromise) => {
    console.log(dataPromise);
  })
  .catch((err) => {
    console.log(err.message);
  });

🔼 Regresar


Iteradores

Un Iterador es un mecanismo que tienen los lenguajes de programación para recorrer secuencialmente distintas estructuras de datos.

Para que un objeto sea iterable en JavaScript es necesario que:

  • Implemente el tipo Symbol.iterator.
  • Implemente la función next que devuelve un objeto con dos valores:
    1. done que indica si ha terminado de iterar y
    2. value que devuelve el valor actual.

Ejemplos de Iteradores:

console.log(typeof String.prototype[Symbol.iterator]); // Imprime function
console.log(typeof Array.prototype[Symbol.iterator]); // Imprime function
console.log(typeof Map.prototype[Symbol.iterator]); // Imprime function
console.log(typeof Set.prototype[Symbol.iterator]); // Imprime function
console.log(typeof Object.prototype[Symbol.iterator]); // Imprime undefined

Recorriendo iteradores con el bucle for...of, que permite recorrer objetos iterables:

//Antes
var anArray = ["Hola", 1, true, {}],
  aString = "Hola soy iterable";

for (var i = 0; i < anArray.length; i++) {
  console.log(anArray[i]);
}

for (var i = 0; i < aString.length; i++) {
  console.log(aString[i]);
}

//Ahora
let anArray = ["Hola", 1, true, {}],
  aString = "Hola soy iterable";

for (let item of anArray) {
  console.log(item); //Imprime cada elemento del arreglo
}

for (let character of aString) {
  console.log(character); //Imprime cada caracter de la cadena de texto
}

🔼 Regresar


Símbolos

Un Símbolo es un tipo de datos único e inmutable, puede ser utilizado como un identificador para las propiedades de los objetos.

Son útiles cuando queremos agregar métodos o atributos propios a objetos nativos del lenguaje o a los de alguna librería de terceros para evitar que, si existiése algún método o atributo con el nombre del que queremos crear evitar que el original se sobre escriba con esto se evita un antipatrón.

Cuando se recorran las propiedades de un objeto, las que sean definidas como símbolo no aparecerán.

Recorriendo los elementos de un Objeto:

let anObject = {
    name: "Jonathan",
    age: 36,
    email: "jonmircha@gmail.com",
  },
  //Un símbolo se instancia sin new, porque no es un objeto y por tal no tiene constructor
  email = Symbol("email");

//Asignación de un símbolo en un objeto
anObject[email] = "hola@jonmircha.com";

for (let item of anObject) {
  console.log(item); //Uncaught TypeError: anObject[Symbol.iterator] is not a function
}

for (let item in anObject) {
  console.log(item); //Imprime name, age, email
}

console.log(Object.keys(anObject)); // Imprime ["name", "age", "email"]
console.log(Object.getOwnPropertyNames(anObject)); // Imprime ["name", "age", "email"]
console.log(Object.getOwnPropertySymbols(anObject)); // Imprime [Symbol(email)]

console.log(
  anObject, //Imprime Object {name: "Jonathan", age: 36, email: "jonmircha@gmail.com", Symbol(email): "hola@jonmircha.com"}
  anObject.name, //Imprime "Jonathan"
  anObject.age, //Imprime 36
  anObject.email, //Imprime "jonmircha@gmail.com"
  anObject[email] //Imprime "hola@jonmircha.com"
);

Convirtiéndo un Objeto en Iterable:

let iterable = {
  0: "Jonathan",
  1: 36,
  2: "jonmircha@gmail.com",
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator],
};

for (let item of iterable) {
  console.log(item); //Imprime Jonathan, 36, jonmircha@gmail.com
}

🔼 Regresar


Generadores

Los generadores son un tipo especial de función que devuelve un valor y permite luego volver a entrar en la función en el mismo lugar en que se quedó, al tiempo que conserva el contexto de ejecución.

Son funciones que pueden ser pausadas y resumidas cuando llamamos a la función generador, no ejecuta el cuerpo de la función, sino que devuelve un objeto generador. El generador implementa una interfaz que le proporciona un método next(), que ejecutará el cuerpo de la función hasta encontrar un yield. En este punto, se detendrá.

El secreto del generador radica justamente en la palabra clave yield, que es un tipo especial de return que, en lugar de devolver un solo valor y salirse de la función, entrará nuevamente en esta y continuará ejecutándola hasta que se acabe o encuentre otra cláusula yield.

Para que una función se considere generador debe declararse anteponiento un asterísco function*.

Para obtener los resultados del generador lo hacemos con el método next() que devuelve un objeto de tipo:

{
  value: el valor retornado por yield
  done: indica si ha finalizado o no la ejecución del cuerpo de la función
}

Los generadores, al implementar .next(), son iterables y suelen ser una forma más sencilla de describir un iterador.

function* generador(nombre) {
  yield `Hola ${name}`;
  yield "Esta línea saldrá en la segunda ejecución";
  yield "Esta otra, en la tercera";
  if (nombre === "Jonathan") {
    yield "Esta otra, saldrá en la cuarta solo si te llamas Jonathan";
  }
}

let gen = generador("Jonathan");
console.log(gen.next()); //Imprime Object {value: "Hola Jonathan", done: false}
console.log(gen.next().value); //Imprime Esta línea saldrá la segunda ejecución
console.log(gen.next().value); //Imprime Esta otra, en la tercera
console.log(gen.next().value); //Imprime Esta otra, saldrá en la cuarta solo si te llamas Jonathan
console.log(gen.next()); //Imprime Object {value: undefined, done: true}

Más ejemplos con Generadores:

class LoremIpsum {
  constructor(text) {
    this._text = text;
  }

  *words() {
    const re = /\S+/g;
    let match;

    while ((match = re.exec(this._text))) {
      yield match[0];
    }
  }
}

const lorem = new LoremIpsum(
  "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Unde voluptatem eveniet ipsum in similique maxime sunt eaque veritatis sapiente. Fuga minus, non cumque deleniti consequatur. Odit reprehenderit non fugit cum!"
);

for (let word of lorem.words()) {
  console.log(word);
}

class Usuarios {
  constructor(gente) {
    this._gente = gente;
  }

  *alias() {
    for (let persona of this._gente) {
      yield persona.sexo === "H"
        ? `Sr. ${persona.nombre}`
        : `Sra. ${persona.nombre}`;
    }
  }
}

const gente = new Usuarios([
  { sexo: "H", nombre: "Jon" },
  { sexo: "M", nombre: "Irma" },
]);

for (let persona of gente.alias()) {
  console.log(persona);
}

🔼 Regresar


Proxies

Los proxies proporcionan una API para capturar o interceptar cualquier operación realizada sobre un objeto y para modificar cómo se comporta ese objeto. Son útiles para:

  • Intercepción.
  • Virtualización de objetos.
  • Gestión de recursos.
  • Hacer profiling y generar logs durante la depuración de una aplicación.
  • Seguridad y control de acceso.
  • Definición de "contratos" al usar objetos.
  • Más información.

La API Proxy define un constructor al que se le pasa como primer argumento el objeto que se va a capturar llamado target y como segundo argumento el handler que realizará la captura. Ejemplo:

let target = {
    /* propiedades y métodos */
  },
  handler = {
    /* funciones capturadoras */
  },
  proxy = new Proxy(target, handler);

El handler es el encargado de modificar el comportamiento original del objeto target. Este handler contiene métodos "capturadores" ( por ejemplo .get(), .set(), .apply() ) que se ejecutan al realizar la llamada correspondiente del proxy.

const persona = new Proxy(
  {},
  {
    set(obj, prop, val) {
      if (prop === "edad" && (!Number.isInteger(val) || val < 0)) {
        throw new Error(`Valor inválido para la propiedad ${prop}`);
      }
      return (obj[prop] = val);
    },
  }
);

persona.edad = 33;
console.log(persona.edad); //Imprime 33

persona.edad = -10; //Imprime Error: Valor inválido para la propiedad edad

🔼 Regresar


Reflexión

Objeto global que proporciona funciones estáticas capaces de intereceptar operaciones de JavaScript, es muy útil cuando se trabaja con proxies y de hecho, comparten muchos métodos.

La mayoría de sus métodos estáticos tienen una equivalencia en el objeto Object o Function.

Las ventajas que ofrece el uso de Reflect son:

  • Aplicación de funciones más fiable.
  • Valores de retorno más útiles.
  • Sintaxis menos verbosa.
  • Mejora la forma en la que se capturan los getters y setters.
  • Más información.

ES5 ya incluye varias funcionalidades íntimamente relacionadas con la reflexión, como por ejemplo Array.isArray() o Object.getOwnPropertyDescriptor().

ES6 introduce la API Reflection para agrupar todos estos métodos y los nuevos que se vayan definiendo.

const obj = { x: 1, y: 2 };
console.log(obj); //Imprime Object {x: 1, y: 2}

console.log(Reflect.has(obj, "z")); //Imprime false
console.log(Reflect.has(obj, "x")); //Imprime true

Reflect.deleteProperty(obj, "x");

console.log(Reflect.has(obj, "x")); //Imprime false
console.log(obj); //Imprime Object {y: 2}

🔼 Regresar


Decoradores

Permiten anotar y modificar las clases y propiedades en tiempo de diseño. Mientras que en ES5 los objetos literales admiten expresiones arbitrarias en la posición del valor, las clases de ES6 sólo admiten funciones como valores literales, un decorador restaura la capacidad de ejecutar código en tiempo de diseño, mientras se mantiene una sintaxis declarativa.

Un decorador:

  • Es una expresión.
  • Evalúa una función.
  • Toma el target, name y el descriptor del decorador como argumentos.
  • Opcionalmente retorna un descriptor del decorador para instalar en el objeto target.
  • Más Información.

Parámetros de un decorador:

  • target: El objeto al que queremos modificar su definición de propiedades.
  • name: El nombre de la propiedad a modificar.
  • descriptor: La descripción de la propiedad del objeto, que a su vez es:
    • configurable: indica si puede ser modificada.
    • enumerable: se puede usar con un for...of.
    • value: valor asociado a la propiedad.
    • writable: indica si la propiedad puede ser cambiada con una asignación.
    • get: indica si la propiedad es un getter.
    • set: indica si la propiedad es un setter.
const soloLectura = (target, name, descriptor) => {
  descriptor.writable = false;
  return descriptor;
};

class Persona {
  constructor({ nombre, apellido }) {
    this.nombre = nombre;
    this.apellido = apellido;
  }

  @soloLectura
  nombrar() {
    return `${this.nombre} ${this.apellido}`;
  }
}

const alguien = new Persona({
  nombre: "Jonathan",
  apellido: "MirCha",
});

console.log(alguien.nombrar()); //Imprime Jonathan MirCha

alguien.nombrar = () => {
  return `${this.nombre}`;
}; //Ejecutará Cannot assign to read only property 'nombrar' of object '#<Persona>'

🔼 Regresar


Funciones Asíncronas

Son una nueva característica soportada en ES, que nos permitirá realizar las mismas cosa que se pueden lograr con Generadores y Promesas pero con menos esfuerzo.

Más Información:

function createUser(name) {
  alert(`Usuario ${name} creado`);
}

function getFriends(name) {
  alert(`Obteniendo amigos de ${name}`);
  return 150;
}

async function setNewUser(name) {
  let newUser = await createUser(name),
    friends = await getFriends(name);

  if (friends !== 0) {
    alert(`${name} tienes ${friends} amigos`);
  } else {
    alert(`${name} eres un antisocial sin amigos`);
  }
}

setNewUser("Jonathan");

En Node.js:

const fs = require("fs"),
  file = "./nombres.txt",
  newFile = "./nombres_async_es6.txt";

function accessFile(file) {
  fs.access(file, fs.F_OK, (err) => {
    return err
      ? new Error("El archivo no existe")
      : console.log("El archivo existe");
  });
}

function readFile(file) {
  fs.readFile(file, (err, data) => {
    return err ? new Error("El archivo no se pudo leer") : data;
  });
}

function writeFile(newFile, data) {
  fs.writeFile(newFile, data, (err) => {
    return err
      ? new Error("El archivo no se pudo copiar")
      : console.log("El archivo se ha copiado con éxito");
  });
}

async function copyFile() {
  let af1 = await accessFile(file),
    af2 = await readFile(file),
    af3 = await writeFile(newFile, af2);
}

copyFile();

🔼 Regresar


Métodos clase String

Nuevos métodos para Cadenas de Texto.

let nombre = "Jonathan";

console.log(nombre.startsWith("jo")); //Imprime false
console.log(nombre.endsWith("an")); //Imprime true
console.log(nombre.includes("th")); //Imprime true
console.log(nombre.repeat(3)); //Imprime JonathanJonathanJonathan}

🔼 Regresar


Números octales y binarios

//octales
console.log(0o17); //Imprime 15

//binarios
console.log(0b100); //Imprime 4

🔼 Regresar


Métodos clase Math

Nuevos métodos de la Clase Matemáticas, apto sólo para ñoños 🤓:

console.log(Math.acosh(3)); //Imprime 1.7627471740390859
console.log(Math.asinh(2)); //Imprime 1.4436354751788103
console.log(Math.atanh(1)); //Imprime Infinity
console.log(Math.cbrt(4)); //Imprime 1.5874010519681996
console.log(Math.clz32(5)); //Imprime 29
console.log(Math.cosh(7)); //Imprime 548.317035155212
console.log(Math.expm1(8)); //Imprime 2979.9579870417283
console.log(Math.fround(9.56789)); //Imprime 9.567890167236328
console.log(Math.hypot(11, 23)); //Imprime 25.495097567963924
console.log(Math.imul(13, 3)); //Imprime 39
console.log(Math.log10(54)); //Imprime 1.7323937598229686
console.log(Math.log1p(34)); //Imprime 3.5553480614894135
console.log(Math.log2(100)); //Imprime 6.643856189774724
console.log(Math.sign(46)); //Imprime 1
console.log(Math.sinh(22)); //Imprime 1792456423.065796
console.log(Math.tanh(19)); //Imprime 0.9999999999999999
console.log(Math.trunc(40.56)); //Imprime 40

🔼 Regresar


Métodos clase Array

Nuevos métodos para Arreglos.

let nombre = "Jonathan";

console.log(Array.from(nombre)); //Imprime Array [ "J", "o", "n", "a", "t", "h", "a", "n" ]

console.log(Array.of(7)); //Imprime [7]
console.log(Array.of(1, 2, 3)); //Imprime [1, 2, 3]

console.log(Array(7)); //Imprime [ , , , , , , ]
console.log(Array(1, 2, 3)); //Imprime [1, 2, 3]

console.log(["a", "b", "c", "d", "e"]); //Imprime ["a", "b", "c", "d", "e"]
console.log(["a", "b", "c", "d", "e"].copyWithin(3, 0)); //Imprime ["a", "b", "c", "a", "b"]

console.log([20, 40, 100, 60, 80]); //Imprime [20, 40, 100, 60, 80]
console.log([20, 40, 100, 60, 80].find((n) => n > 50)); //Imprime 100
console.log([20, 40, 100, 60, 80].findIndex((n) => n > 50)); //Imprime 2

console.log([1, 2, 3].fill(4)); //Imprime [4, 4, 4]
console.log([1, 2, 3].fill(4, 1)); //Imprime [1, 4, 4]
console.log([1, 2, 3].fill(4, 0, 1)); //Imprime [4, 2, 3]

console.log([1, 2, 3].includes(2)); //Imprime true
console.log([1, 2, 3].includes(4)); //Imprime false

🔼 Regresar


Métodos clase Object

Nuevos métodos para Objetos.

const a = { a: 1 },
  b = { b: 2 },
  c = {};

Object.assign(c, a, b);

console.log(a); //Imprime {a: 1}
console.log(b); //Imprime {b: 2}
console.log(c); //Imprime {a: 1, b: 2}

console.log(Object.values(c)); //Imprime [1, 2]
console.log(Object.entries(c)); //Imprime [ ["a", 1], ["b", 2] ]

🔼 Regresar


Maps Sets y Weaks

ES incorpora 4 nuevas estructuras de datos, que son Map, WeakMap, Set y WeakSet. Si has trabajado con lenguajes como Java o Python ya te harás una idea de para que sirven.

Más información:

Map

El objecto Map nos permite relacionar (mapear) unos valores con otros como si fuera un diccionario, en formato clave/valor. Cualquier valor (tanto objetos como valores primitivos) puede ser usados como clave o valor.

Los Maps nos permiten saber de inmediato si existe una clave o borrar un par clave/valor concreto. Además, podemos crear Maps a partir de un array de pares:

let map = new Map();
map.set("clave", 123);

let user = { userId: 1 };
map.set(user, "Jonathan");

console.log(map); //Imprime Map {"clave" => 123, Object {userId: 1} => "Jonathan"}
console.log(map.get("clave")); //Imprime 123
console.log(map.get(user)); //Imprime Jonathan

console.log(map.size); //Imprime 2

console.log(map.has("clave")); // Imprime true
console.log(map.delete("clave")); //Imprime true
console.log(map.has("clave")); //Imprime false

map.clear();
console.log(map.size); //Imprime 0

map = new Map([
  ["user1", "Jonathan"],
  ["user2", "Irma"],
  ["user3", "kEnAi"],
]);
console.log(map.size); //Imprime 3

for (let [key, value] of map) {
  console.log(key, value);
}

//Imprime user1 Jonathan
//Imprime user2 Irma
//Imprime user3 kEnAi

console.log(map.keys()); //Imprime MapIterator {"user1", "user2", "user3"}
console.log(map.values()); //Imprime MapIterator {"Jonathan", "Irma", "kEnAi"}
console.log(map.entries()); //Imprime MapIterator {["user1", "Jonathan"], ["user2", "Irma"], ["user3", "kEnAi"]}

WeakMap

Los WeakMaps son similares a los Maps, pero con algunas diferencias:

Un WeakMap solo acepta objetos como claves, la referencia a las claves es débil, lo que significa que si no hay otras referencias al objeto que actúa como clave, el recolector de basura podrá liberarlo.

Debido a que usa referencias débiles, un WeakMap NO dispone del método .keys() para recuperar las claves, NI de propiedades o métodos relacionados con más de un elemento a la vez, como .values(), .entries(), .clear() o .size.

Tampoco podemos iterar un WeakMap con el bucle for of.

let clave = { userId: 1 },
  clave2 = { userId: 2 },
  weakmap = new WeakMap();

weakmap.set(clave, "Jonathan");
console.log(weakmap); //Imprime WeakMap {Object {userId: 1} => "Jonathan"}

console.log(weakmap.has(clave)); //Imprime true
console.log(weakmap.get(clave)); //Imprime Jonathan
console.log(weakmap.size); //Imprime undefined

weakmap.delete(clave);
console.log(weakmap.get(clave)); //Imprime undefined
console.log(weakmap); //Imprime WeakMap {}

weakmap.set(clave2, "Irma");
console.log(weakmap.get(clave2)); //Imprime Irma
console.log(weakmap); //Imprime WeakMap {Object {userId: 2} => "Irma"}

Set

Los Sets son conjuntos de elementos no repetidos, que pueden ser tanto objetos, como valores primitivos.

Tiene métodos equivalentes a un Map, con la diferencia que utilizamos .add() para añadir elementos, y que las keys y los values son lo mismo, el valor del objeto.

Del mismo modo, .entries() devuelve una pareja [value, value].

let set = new Set();

set.add("Jonathan");
set.add("Irma");
set.add("Irma");

console.log(set.size); //Imprime 2

for (let item of set) {
  console.log(item);
}

//Imprime Jonathan
//Imprime Irma

for (let item of set.entries()) {
  console.log(item);
}

//Imprime ["Jonathan", "Jonathan"]
//Imprime ["Irma", "Irma"]

console.log(set.has("Jonathan")); //Imprime true

set.delete("Jonathan");
console.log(set.has("Jonathan")); //Imprime false

console.log(set.size); //Imprime 1

set.clear();

console.log(set.size); //Imprime 0

WeakSet

Similar al WeakMap, pero con los Sets. Las dos principales diferencias de un WeakSet respecto a un Set son:

  1. Únicamente pueden contener colecciones de objetos.
  2. La referencia a los objetos es débil, por lo que si no hay otra referencia a uno de los objetos contenidos en el WeakSet, el recolector de basura lo podrá liberar. Esto implica que:
    • No hay una lista de objetos almacenados en la colección.
    • Los WeakSet no son enumerables.

Básicamente, los métodos de los que dispone un WeakSet son:

  • .add()
  • .delete()
  • .has()
let objs = ["Jonathan", "Irma", "kEnAi"],
  weakset = new WeakSet();

console.log(weakset); //Imprime WeakSet {}

weakset.add(objs);
console.log(weakset); //Imprime WeakSet {["Jonathan", "Irma", "kEnAi"]}
console.log(weakset.has(objs)); //Imprime true

weakset.delete(objs);
console.log(weakset.has(objs)); //Imprime false

🔼 Regresar