Listado personal de anotaciones, trucos, recordatorios, utilidades o ejemplos interesantes para JavaScript.
- Declaración de variables
- Operadores
- Condicionales
- Funciones, parámetros y argumentos
- Clases en ES6
- Clases en ES2019
- Singleton (ES6 clase)
- Tratamiento de arrays
- Tratamiento de strings
- Regular expressions
- Objetos
- Namespaces
- Closures
- Proxies
- Set
- Map
- Debugging y console
- De callback hell a promesas
- Promesas
- Async-Await
- setTimeout
- DOM
- Declarar múltiples variables de manera más compacta:
var i, j, k;
var l = m = n = 0;
var a = 1, b = 2;
- Operador lógico OR (evalua de izquierda a derecha las expresiones y retorna la primera de ellas evaluable como "truthy". Si todas se evaluan como "falsy", retorna false. Todos los valores son "truthy", excepto: false, 0, "", null, undefined, y NaN)
var foo = false || '🙂'; // '🙂'
var bar = 3 || false; // 3
var foobar = null || 0 || undefined || "NyanCat"; // "NyanCat"
- Operador ternario: ?:
var now = new Date();
var greeting = "Good" + ((now.getHours() > 17) ? " evening." : " day.");
console.log(greeting);
- Operador spread:
let foo = ['foo 1', ' foo 2'];
let bar = ['bar 1', 'bar 2', ...foo, 'bar 3'];
console.log(bar); // ["bar 1", "bar 2", "foo 1", " foo 2", "bar 3"]
- Doble uso del operador ! (not):
// La doble negación retorna un booleano dependiendo de la "truthiness" de la expresión.
// Retornará false cuando el valor sea: false, 0, "", null, undefined, y NaN.
// Si no es ninguno de estos casos, entonces devolverá true.
// Tiene sentido usarlo para evaluar expresiones que no son booleanas.
let foo;
if (!!foo) { // 'Result is false' Dado que el valor de foo es null.
console.log('Result is true');
} else {
console.log('Result is false');
}
// Observa los siguientes ejemplos:
!!0 // false
!!1 // true
!!"" // false (string vacÃo es falsy)
!!window.biz // false (si la variable biz no está declarada, se evalua como falsy)
!!undefined // false (undefined es falsy)
!!null // false (null is falsy)
!!{} // true (an empty object is truthy)
!![] // true (an empty array is truthy. PHP programmers beware!)
!!NaN // false
- Nullish coalescing operator (??):
// Node.js 14 required (ES2020)
0 ?? 'default' // 0
'' ?? 'default' // ''
false ?? 'default' // false
NaN ?? 'default' // NaN
null ?? 'default' // 'default'
undefined ?? 'default' // 'default'
undefined ?? null // null
null ?? undefined // undefined
- Optional chaining operator (?.):
// Node.js 14 required (ES2020)
const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};
const firstName = object?.names?.first; // 'Alice'
const home = object?.address?.home; // undefined
// En combinación con nullish coalescing operator:
const middleName = object?.names?.middle ?? '(no middle name)'; // '(no middle name)'
const user = {};
alert(user.address.street); // Error!
alert( user && user.address && user.address.street ); // undefined (no error)
alert( user?.address?.street ); // undefined (no error)
// the evaluation stops immediately after: car?.
const car = null;
alert( car?.motor.valves.anything ); // undefined
- Condicional if comparando múltiples criterios:
const fruit = 'strawberry';
// Creamos un array con aquellos criterios a los que comparar
const criteria = ['apple', 'strawberry', 'cranberries'];
if (criteria.includes(fruit)) {
// true
}
- Parámetros vs argumentos. Un parámetro es una variable en la declaración de la función. Un parámetro es el valor que le pasas a una función en el momento de ser llamada:
function sayHi(name) { // 👈 parameter
}
sayHi('samantha'); // 👈 argument
- arguments (array de argumentos que se le pasan a una función):
function printArgumentsES6(...arg){
arg.forEach((a) => console.log(a));
}
printArgumentsES6(3712,7123);
function printArgumentsES6ForOf(){
for(let argument of arguments){
console.log(argument);
}
}
printArgumentsES6ForOf(3712,7123);
function food(...arg){
const [egg, cheese] = args;
}
food('🥚', '🧀');
- Uso de destructuring para mejorar la legibilidad del siginificado de los parámetros que mandamos a una función:
function createMenu({ title, body, buttonText, cancellable=false }) {
// ...
// FÃjate que establecemos un valor por defecto al argumento "cancellable"
}
createMenu({
title: 'my title',
body: 'my body',
buttonText: 'send form',
cancellable: true
}); // observa que asà es totalmente comprensible el significado de los parámetros. A esto lo llamamos "named parameters".
// Sin embargo, no hubieramos entendido nada asÃ: createMenu('my title', 'my body', 'send form', true);
// ¿SabrÃamos entender que significa el true en la segunda llamada? Probablemente no!
// Podemos hacer lo siguiente si todos los parámetros son opcionales:
function createUser({ username = 'Anonymous' } = {}) {
// ...
}
// de esta manera podemos llamar a la función asÃ:
createUser();
// Observa que si nuestras funciones también retornan objetos, estás usando el patrón RORO (receive object, return object).
- Self Invoking Functions, también llamadas IIFE (Immediately Invoked Function Expression):
var hello;
(function (){
hello = function(){
return "Hi boy!";
};
})();
console.log( hello() ); // "Hi boy!"
var result = (function () {
var name = "Barry";
return name;
})();
console.log(result); // El contenido de la variable result es el resultado de la ejecución de la función.
// En este caso: "Barry"
- Factory Functions (una "Factory Function" es una función que crea objetos y los retorna):
// Ejemplo de Factory Function
const cat = () => {
const sound = 'miau';
let color = 'white';
// definimos getters and setters para acceder a las propiedades
const setColor = (newColor) => {
color = newColor;
}
const getColor = (newColor) => {
return color;
}
return {
talk: () => sound,
setColor,
getColor
}
}
const smurfy = cat(); // No debes usar "new"!
console.log( smurfy.talk() ); // miau
// console.log( smurfy instanceof cat ); // TypeError
console.log( smurfy.getColor() ); // 'white'
smurfy.setColor('black');
console.log( smurfy.getColor() ); // 'black'
// Otro ejemplo potenciando el uso de "closures"
const makeCounter = function() {
// propiedades privadas
let _count = 0;
// métodos privados
const changeBy = (val) => {
_count += val;
}
// definimos los métodos publicos
return Object.freeze({
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
getValue: function() {
return _count;
}
})
};
const Counter1 = makeCounter();
const Counter2 = makeCounter();
console.log(Counter1.getValue()); // 0
Counter1.increment();
Counter1.increment();
console.log(Counter1.getValue()); // 2
Counter1.decrement();
console.log(Counter1.getValue()); // 1
console.log(Counter2.getValue()); // 0
Otro ejemplo de Factory Functions más avanzado:
// Ejemplo avanzado de Factory Function:
const dataConnection = (data = {}) => {
const settings = data; // Guardo los parámetros que me han pasado al llamar a la factory function.
// De esta manera puedo definir propiedades del nuevo objeto.
return Object.freeze({
// Object.freeze evita la eliminación y adición de propiedades, modificación del prototype, etc.
getSettings: () => settings,
modifySettings: (addData = {}) => {
return Object.assign(settings, addData);
// Añadimos al objeto 'settings' nuevas propiedades
// o bien modificamos el valor de las existentes.
}
});
}
const connection = dataConnection( { ip: '127.0.0.1', port: '8080' } );
// Le paso como parámetro un objeto
console.log( connection.getSettings() ); // {ip: "127.0.0.1", port: "8080"}
console.log( connection.getSettings().ip ); // 127.0.0.1
connection.method = 'http'; // Esto NO funcionará!
// Gracias a 'Object.freeze' no puedo añadir propiedades al objeto.
// Sólo veré un error si uso: 'use strict';
//connection.settings.method = 'http'; // Esto NO funcionará! TypeError
connection.modifySettings( { method: 'http' } );
// Usamos un método definido en el objeto para modificar los valores que contiene el objeto.
console.log( connection.getSettings() ); // {ip: "127.0.0.1", port: "8080", method: "http"}
connection.modifySettings( { ip: '192.168.2.11' } );
// Podemos sobreescribir los valores del objeto 'settings'.
console.log( connection.getSettings() ); // {ip: "192.168.2.11", port: "8080", method: "http"}
- Ejemplo de clase en ES6:
class Person {
constructor(name) {
// constructor es un método opcional. Si la clase no tiene propiedades puedes omitirlo
this.name = name;
}
salute() {
console.log(`Hello, my name is ${this.name}`);
}
}
let john_doe = new Person("John Doe");
john_doe.salute(); // Hello, my name is John Doe
console.log(john_doe instanceof Person); // true
console.log(john_doe instanceof Object); // true
console.log( typeof john_doe); // object
console.log( typeof Person); // function !!
// (Cuidado, podrÃamos pensar que no retornará "class" pero nos retorna "function")
console.log( typeof Person.prototype.salute); // function !!
// (Cuidado, podrÃamos pensar que no retornará "method" pero nos retorna "function")
- Ejemplo de Chaining Methods (métodos encadenados):
class Car {
constructor(brand, model, color) {
this.brand = brand;
this.model = model;
this.color = color;
}
setBrand(brand) {
this.brand = brand;
return this; // Returning this for chaining
}
setModel(model) {
this.model = model;
return this; // Returning this for chaining
}
setColor(color) {
this.color = color;
return this; // Returning this for chaining
}
getCarInfo() {
return this; // Returning this for chaining
}
}
const car = new Car('Ford','F-150','red').setColor('pink').setModel('Fiesta'); // Chaining methods!
console.log( car.getCarInfo() ); // {brand: "Ford", model: "Fiesta", color: "pink"}
- Métodos estáticos (nos permite usar esos métodos sin generar la instancia de la clase):
class Utilities {
static generateRandomInteger() {
// Esto es un método estático.
// Permite ser llamado sin necesidad de instanciar la clase.
return Math.floor(Math.random() * 11); // retorna número entero aleatorio entre 0 y 10
}
static coinToss() {
return Math.random() > .5; // retorna "true" o "false" de manera aleatoria
}
static capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1); // retorna un string poniendo en mayúscula la primera letra
}
static delay(miliseconds, callback) {
setTimeout(callback, miliseconds);
}
static pause(miliseconds) {
const start = Date.now();
let now = start;
while (now - start < miliseconds) {
now = Date.now();
}
}
static wait(timeout) {
return new Promise(resolve => setTimeout(resolve, timeout));
}
static generateToken() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-$';
let rand, i;
let bits = 64;
let result = '';
while (bits > 0) {
rand = Math.floor(Math.random() * 0x100000000); // 32-bit integer
// base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.
for (i = 26; i > 0 && bits > 0; i -= 6, bits -= 6) result += chars[0x3F & rand >>> i];
}
return result;
}
}
let num = Utilities.generateRandomInteger(); // Llamamos directamente al método estático
console.log(num); // 2
console.log(Utilities.coinToss()); // true | false
console.log(Utilities.capitalize('batman')); // 'Batman'
Utilities.delay(3000, function () {
return console.log('delayed output');
}); // 'delayed output', tres segundos después
Utilities.generateToken(); // 'DyvlJ_mhQ8X'
Utilities.pause(2000); // Stop the execution of JavaScript for the specified time
// wait() example:
Utilities.wait(5000).then(() => doSomething());
// or even...
await Utilities.wait(5000);
doSomething();
- Herencia:
class Rectangle {
constructor(len, wid) {
this.len = len;
this.wid = wid;
}
getArea() {
return this.len * this.wid;
}
getInfo () {
return 'I am a naughty rectangle!';
}
}
class Square extends Rectangle { // extendemos la clase
constructor(len) {
super(len, len); // Invocamos al constructor de la clase padre
}
// PodrÃa hacer referencia al método del padre, pero no es necesario!
// Puedo omitirlo, ya que el método es heredado automáticamente.
// getArea() {
// return super.getArea(); // FÃjate que asà invoco a un determinado método del padre.
// }
// Sobreescribir un método
getInfo () {
return 'I am a cheeky square!';
}
}
let square = new Square(4);
console.log(square.getArea()); // 16. Observa que este método ha sido hererado de la clase padre
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true. Recuerda que Square extiende la clase Rectangle
console.log(square.getInfo()); // I am a cheeky square!
- Ejemplo de clase en ES2019:
class MyES2019Class {
// ya no es necesario el constructor para declarar las propiedades públicas (pero puedes usarlo si lo necesitas).
x = 1; // public property
#counter = 0; // private property
static z = 3; // static property
incB() {
this.#counter++;
}
getCounter () {
return this.#counter
}
}
let foo = new MyES2019Class();
foo.incB(); // runs OK
foo.#counter = 7; // error - private property cannot be modified outside class
foo.#counter; // error - private property cannot be read outside class
foo.counter; // undefined
foo.getCounter(); // 1
- Ejemplo de singleton pattern usando clases de ES6:
class Item {
constructor(){
if (!Item.instance) {
this._data = [];
Item.instance = this;
}
return Object.freeze(Item.instance);
}
// Methods
add(item){
this._data.push(item);
}
get(id){
return this._data.find(d => d.id === id);
}
}
// Puedes exportar esta clase como un módulo:
module.exports = Item;
// Impórtalo asÃ:
const Item = require('./Item');
const foo = new Item();
let first = new Item();
let second = new Item();
first.add( { id: 1, name: 'john' } );
first.add( { id: 2, name: 'louis' } );
console.log(second); // [ {id: 1, name: "john"}, {id: 2, name: "louis"} ]
console.log(second.get(2)); // { id: 2, name: 'louis' }
console.log(first===second); // true (son la misma instancia)
- Crear un array con n número de posiciones con un valor por defecto
new Array(3).fill(0); // [0,0,0]
- Clonar un array
/**
* recursiveArrayClone() Crea un clon exacto de un array. Soporta matrices multidimensionales.
*
* @code
* console.log( recursiveArrayClone( ['a', {}, [4, 0]] ) ); // ['a', {}, [4, 0]]
* @endcode
*
* @param {array} arrayToClone. El array que se quiere clonar.
* @return {array} clonedArray. Retorna un nuevo array.
*/
function recursiveArrayClone(arrayToClone){
var clonedArray = [];
for (var i = 0; i < arrayToClone.length; i++) {
if( Array.isArray(arrayToClone[i]) ){
clonedArray.push( recursiveArrayClone(arrayToClone[i]) );
}else{
clonedArray.push(arrayToClone[i]);
}
}
return clonedArray;
}
- Vaciar un array
var myArray = ['a', 'b', 'c'];
myArray.length = 0;
console.log(myArray); // []
- Quitar elementos falsy de un array
const myArray = [0, "0", "example", null, undefined, NaN, false];
let arrayFiltered = myArray.filter(Boolean);
console.log(arrayFiltered); // ["0", "example"]
- Uso de: array.join (retorna un string)
var a = ['Wind', 'Rain', 'Fire'];
a.join(); // 'Wind,Rain,Fire'
a.join(''); // 'WindRainFire'
var text = "Add the potato please, I always prefer more potato";
text = text.split("potato").join("cheese");
// Sustituye "potato" por "cheese" en todas las apariciones que haya en "text"
- Uso de: array.concat (crea un nuevo array con la concatenación de 2 o más arrays)
var arr1 = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];
var arr3 = arr1.concat(arr2); // arr3 is a new array [ "a", "b", "c", "d", "e", "f" ]
- Uso de: array.slice (crea una copia del array pero quitando, o no, algunos elementos del original)
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3); // citrus == ['Orange','Lemon']
var reduced_fruits = fruits.slice(1); // reduced_fruits == ['Orange', 'Lemon', 'Apple', 'Mango']
- Uso de: array.splice (permite añadir o eliminar elementos a un array. También es útil para partir un array en dos)
var instruments = ['guitar', 'bass', 'saxophone'];
instruments.splice(2, 0, 'drums'); // instruments == ['guitar', 'bass', 'drums','saxophone']
var planets = ['Mercury','Venus','Earth','Mars','Pluto','Jupiter','Saturn','Uranus','Neptune'];
planets.splice(4,1);
// (sorry, Pluto) planets == ['Mercury','Venus','Earth','Mars','Jupiter','Saturn','Uranus','Neptune']
array_example.splice(0, 0, "foo"); // Añade "foo" a la primera posición de un array
var myFish = ['angel', 'clown', 'mandarin', 'sturgeon', 'cherubfish'];
var removedFish = myFish.splice(2);
console.log(myFish); // ["angel", "clown"]
console.log(removedFish); // ["mandarin", "sturgeon", "cherubfish"]
- Uso de: array.indexOf (busca un determinado elemento en un array)
var a = ["foo", "bar", "biz"];
a.indexOf("bar"); // 1
a.indexOf("NyanCat"); // -1
if (a.indexOf("NyanCat") === -1) {
// element doesn't exist in array
}
- Uso de: array.filter (crea un nuevo array a partir de otro con aquellos elementos que pasan el filtro especificado)
function isBigEnough(value) {
return value >= 10;
}
var numbers = [12, 5, 8, 130, 44];
var filteredNumbers = numbers.filter(isBigEnough); // filteredNumbers is [12, 130, 44]
- Uso de: array.map (crea un nuevo array a partir de realizar una función en cada uno de los elementos del array original)
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt); // roots is now [1, 2, 3]
var nums = [1,2,3];
var doubleNums = nums.map(
function(number){
return number * 2;
}
);
// doubleNums is now [2, 4, 6]
// Trick!
['1', 2, '3', 4, '5'].map(Number); // [1, 2, 3, 4, 5]
- Uso de: array.forEach (recorre el array aplicando una función a cada elemento)
var a = ['a', 'b', 'c'];
a.forEach(function(element) {
console.log(element);
});
// Otra manera de implementarlo:
function fooFunction(currentValue,index,array) {
// ...
}
a.forEach(fooFunction, this); // Podemos nombrar la función en vez de declararla dentro del forEach.
// Adicionalmente, podemos pasarle el objeto 'this' a la función.
- Uso de: array.reduce (aplica una función a un acumulador y a cada valor de un array (de izquierda a derecha) para reducirlo a un único valor.)
cart = [ {ref: "yuh", price: 23}, {ref: "jum", price: 44} ];
var result = cart.reduce(function(accum, v){
return accum + v.price;
},0); // en este caso, el 0 es el acumulador.
console.log(result); // 67
var arrayOfBooleans = [true, false, true, true];
var totalOfTrues = arrayOfBooleans.reduce( function(accumulator, arrayElement){
return arrayElement ? ++accumulator : accumulator;
},0
);
console.log(totalOfTrues); // 3
- Uso de: string.match (crea un array a partir de comparar un string con una regular expression)
var str = "The rain in SPAIN stays mainly in the plain";
var res = str.match(/ain/gi); // ["ain","AIN","ain","ain"]
var text = "The phone number is 93 555 22 33";
var result = text.match(/\d+/); // ["93"]
var allResults = text.match(/\d+/g); // allResults is ["93", "555", "22", "33"]
var numberOfResults = text.match(/\d+/g).length; // 4
var text = "<p>Hello {{name}}, you are {{age}} years old.</p>";
var extract = text.match(/\{\{(.+?)\}\}/g);
console.log(extract); // ["{{name}}", "{{age}}"]
- Diferencias entre string.substr y string.substring (ambos métodos retornan un nuevo string a partir del original, pero observa la diferencia de resultados):
var text = "Example";
console.log(text.substr(1,5)); // "xampl"
// (a partir del Ãndice dado, retorna tantos caracteres como indiques en el segundo parámetro)
console.log(text.substring(1,5)); // "xamp"
// (a partir del Ãndice dado, retorna caracteres hasta el otro Ãndice dado)
- Uso de: string.split (genera un nuevo array a partir de trocear un string por el parámetro indicado)
var array_of_words = "A new car".split(" ");
console.log(array_of_words); // ["A", "new", "car"]
console.log( "hello".split("") ); // ["h", "e", "l", "l", "o"]
var text = "Add the potato please, I always prefer more potato";
text = text.split("potato").join("cheese");
// Sustituye "potato" por "cheese" en todas las apariciones que haya en "text"
- Uso de: regexObj.search (retorna un boolean si encuentra una coincidencia en el string dado)
var str = "The best things in life are free";
var pattern = new RegExp(/life/g);
var res = pattern.test(str); // true
- Contexto de this:
var biz = {
nombre: "DÃdac",
"nombre-apellido": "DÃdac GarcÃa", // entrecomillado por contener un guión!
// error, "this" apunta al objecto global:
saludar: 'Hola, soy ' + this.nombre,
// correcto, "this" apunta a "biz":
presentarse: function(){
return 'Mi nombre es ' + this.nombre;
},
get nombreCompleto() { return this["nombre-apellido"]}
}
console.log(biz.saludar); // 'Hola, soy undefined'
console.log(biz.presentarse()); // 'Mi nombre es Pedro'
console.log(biz.nombreCompleto); // "DÃdac GarcÃa"
console.log(biz.nombre-apellido); // Error!
console.log(biz["nombre-apellido"]); // "DÃdac GarcÃa"
delete biz["nombre-apellido"]; // Siempre retorna true! Incluso cuando no existe la propiedad a borrar
- Obtener un array a partir de las propiedades de un objeto:
var foo = {
nombre: 'Juan',
apellido: 'Ruiz'
}
var arrayOfProperties = Object.keys(foo);
console.log(arrayOfProperties); // ['nombre', 'apellido']
- Crear un array a partir de un objeto:
var biz = {
length: 2,
'0': 'biz',
'1': 'foo'
}
var arrayFromObject = Array.prototype.slice.call(biz);
console.log(arrayFromObject); // ['biz', 'foo']
console.log(arrayFromObject.length); // 2
console.log(arrayFromObject[1]); // 'foo'
- Clonar un objeto:
function deepClone(originalObject) {
const clonedObject = {};
for (let key in originalObject) {
if (typeof (originalObject[key]) != 'object') {
clonedObject[key] = originalObject[key];
} else {
clonedObject[key] = deepClone(originalObject[key]);
}
}
return clonedObject;
}
const newObject = deepClone(originalObject);
// método alternativo:
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
- Iterar un objeto:
const obj = {
id:1,
name: "gowtham",
active: true
}
for (let key in obj){
if(obj.hasOwnProperty(key)){
console.log(`${key} : ${obj[key]}`) // "name : gowtham" En la segunda iteración.
}
}
// Nota: se usa "hasOwnProperty()" para garantizar que iteramos solo las propiedades del objeto, ya que un "for in" también podrÃa iterar sobre propiedades anidadas del prototype
// Otra opción!
Object.keys(obj).forEach( key => {
console.log(`${key} : ${obj[key]}`);
});
// Si sólo nos interesan los valores
Object.values(obj).forEach( value => {
console.log(value);
});
// Esto incluye las propiedades non-enumerables
Object.getOwnPropertyNames(obj).forEach( key => {
console.log(`${key}:${obj[key]}`);
})
- Crea un objeto literal a partir de los parámetros de una URL:
// On this URL: https://example.com/index.php?foo=foo&bar=bar
Object.fromEntries([...new URL(location).searchParams]); // {foo: "foo", bar: "bar"}
Podemos agrupar funciones y valores en un namespace. Esto nos permite organizar mejor nuestro código. Existen diferentes máneras de obtener este resultado, una de ellas es usar namespaces.
- Namespaces con objetos literales:
const myApplication = {
version: '1.0',
name: 'my application',
config: {
url: '127.0.0.1',
port: 3000
},
init: function() {
// ....
}
};
- Namespaces con IIFE (immediatly invoked function expression):
const myApplication = {};
(function(){
let _protocol = 'https'; // esto es una variable "privada", no es accesible desde fuera del scope.
this.version = '1.2',
this.name = 'myApplication',
this.config = {
url: '127.0.0.1',
port: 3000,
protocol: _protocol
},
this.init = function() {
//...
}
}).apply(myApplication);
- Podemos usar closures para organizar nuestro código. Un sencillo ejemplo con IIFE functions que nos permite crear un "módulo", una interesante alternativa a un namespace:
const geoModule = (function() {
const _pi = 3.141592; // propiedad privada
const _info = 'This is a module for doing geometrical calculations';
const circleArea = (radius) => {
if (!Number(radius)) throw new Error('You must provide a radius value');
return _pi * radius * radius;
};
// definimos que métodos y que propiedades son accesibles públicamente
return Object.freeze({
calculateCircleArea: circleArea,
information: _info
})
})();
geoModule.calculateCircleArea(5); // 78.5398
geoModule.information; // 'This is a module for doing geometrical calculations'
El objeto Proxy se usa para definir un comportamiento personalizado para operaciones fundamentales (por ejemplo, para observar propiedades, cuando se asignan, enumeración, invocación de funciones, etc)
- Ejemplo de uso básico de proxies:
let product;
(function () {
let _price = 0;
product = {};
// Podemos definir unos proxies para acceder a la propiedad 'price'.
// Los proxies son muy útiles para hacer validaciones. Por ejemplo en el método 'set'. En este caso, estamos "interceptando" el acceso a la lectura y escritura de la propiedad 'price'.
Object.defineProperty(product, 'price', {
set: function (price) {
_price = price;
},
get: function () {
return _price;
}
});
})();
product.price = 100; // al hacer esto, accionamos el proxie 'set'
// En la siguiente linea accionamos el proxie 'get'
console.log(product.price); // 100
- Ejemplo básico de set:
const set = new Set([1, 2, 3, 4, 5]);
// Almacena valores únicos de cualquier tipo, incluso valores primitivos u objetos de referencia.
// Se forma a partir de un objeto iterable. Se puede crear vacÃo pasándole null.
set.has(4); // true
set.has('4'); // false. No se ha almacenado un string, si no un entero.
set.delete(5); // true
set.add('foo'); // {1, 2, 3, 4, "foo"}
const s = new Set([{a:1},{b:2},{a:1}]);
console.log(s); // {{a:1},{b:2},{a:1}}
// Observa que en este caso parece existir un objeto duplicado, en realidad son objetos distintos!
console.log(s.size); // 3
s.clear(); // VacÃa de elementos el set
- Descomponer strings usando set (recuerda que no permite valores duplicados):
let mySet = new Set('foooooooooo');
// Esto sucede porque le estamos pasando un valor iterable (en este caso, un string)
console.log(mySet); // {"f", "o"}
for (let i of mySet) {
console.log(i);
} // Una manera de iterar un set
mySet.forEach(i => console.log(i)); // Otra manera de iterar un set.
// Ojo, no funcionan: filter, map y reduce.
// Aunque puedes transformarlo en un array para usar estos métodos asÃ: [...mySet]
- Eliminar elementos duplicados en un array mediante set y spread operator:
let nums = [1, 2, 2, 2, 2, 3, 4];
function deleteDuplicated(items) {
return [ ... new Set(items) ]; // retorna un array
}
console.log( deleteDuplicated(nums) ); // [1, 2, 3, 4]
- Primeros pasos con map:
const biz = new Map();
biz.set('name', 'John');
biz.set('age', 53);
console.log(biz.size); // 2
for (let [key, value] of biz) {
console.log(`${key} => ${value}`);
// name => John
// age => 53
}
console.log(biz.get('age')); // 53
biz.delete('age'); // true
console.log(biz); // {"name" => "John"}
biz.clear(); // {}
- Más opciones interesantes con map:
const biz = new Map( [ ['name','John'], ['Surname', 'Doe'], [undefined, null] ] );
console.log(biz.has('name')); // true
console.log(biz.has('Give me a cookie')); // false
console.log(biz); // {"name" => "John", "Surname" => "Doe", undefined => null}
const arrayOfKeys = Array.from(biz.keys()); // ["name", "Surname", undefined]
biz.forEach( (value, key, originalMap) => {
if(value === null) {
originalMap.delete(key);
}
});
console.log(biz); // {"name" => "John", "Surname" => "Doe"}
biz.set('name', 'Maria'); // {"name" => "Maria", "Surname" => "Doe"}
- Uso del comando debugger (establece un "breakpoint" en el código):
// some code...
debugger;
// some code...
- La propiedad table del objeto console (retorna una tabla a partir de un objeto o un array)
var person = { firstName:"John", lastName:"Doe", age:50 };
console.table(person);
- Destructuring en el console.log
let person = { firstName:"John", lastName:"Doe", age:34 };
console.log( { person } );
- Uso de console.group() / console.groupEnd() en bucles:
for(var i = 0; i < 2; i++){
console.group("Primer nivel: ", i);
for(var k = 0; k < 2; k++){
console.group("Segundo nivel: ", k);
// Your code...
console.log("Value of i: " + i);
console.log("Value of k: " + k);
console.groupEnd();
}
console.groupEnd();
}
- Uso de console.log con estilos CSS:
var foo = "AWESOME";
console.log('%c' + foo, 'color: yellow; background-color: black;');
- Uso de console.time() para medir tiempos de ejecución:
console.time('Timer A');
for (let i = 0; i < 999999; i++) {
// Para obtener una medición fiable, quizás necesites usar un bucle para maximizar los resultados
}
console.timeLog('Timer A');
let i = 0
while (i < 999999) {
i++;
}
console.timeEnd('Timer A');
// Timer A: 4.47607421875ms
// Timer A: 9.0048828125ms
- Un pequeño truco para saber con que tipo de objeto trabajamos:
function whatTypeIs (element){
return Object.prototype.toString.call(element).slice(8,-1);
}
console.log( whatTypeIs( ["A", "B"] ) ); // "Array"
console.log( whatTypeIs( "foo" ) ); // "String"
console.log( whatTypeIs( 4 ) ); // "Number"
console.log( whatTypeIs( /[a]/g ) ); // "RegExp"
console.log( whatTypeIs( new Set() ) ); // "Set"
console.log( whatTypeIs( new Map() ) ); // "Map"
console.log( whatTypeIs( () => {} ) ); // "Function"
console.log( whatTypeIs( null ) ); // "Null"
console.log( whatTypeIs( undefined ) ); // "Undefined"
console.log( whatTypeIs( NaN ) ); // "Number"
console.log( whatTypeIs( new Date ) ); // "Date"
- JSON.stringify fáciles de leer. Puedes usar el tercer parámetro para espicificar el tipo de indentación:
const person = { firstName:"John", lastName:"Doe", age:34 };
JSON.stringify(person, null, 2); // Indentación: dos espacios
/*
{
"firstName": "John",
"lastName": "Doe",
"age": 34
}
*/
JSON.stringify(person, null, '\t'); // Indentación: tabulador
/*
{
"firstName": "John",
"lastName": "Doe",
"age": 34
}
*/
- Revisar el código de una función:
function someFunction(){
// ...
};
console.log( someFunction.toString() );
- Utiliza esto en la consola del navegador para debuggear el CSS:
$$('*').forEach((el) => el.style.border = '1px solid red');
function justAnAsyncFunction(str, cb) {
// Esto sólo simula ser algo asÃncrono
setTimeout(function(){
if(!str) {
cb({ok: false});
} else {
cb ({
ok: true,
users: ['john', 'jack']
});
}
}, 2000);
}
function retrieveUsers(cb) {
// tipico ejemplo de anidación de funciones que contienen callback (callback hell)
justAnAsyncFunction('SELECT * FROM users', function(results) {
if (results.ok) {
cb(results.users);
} else {
cb('Error');
}
});
}
retrieveUsers(console.log); // retorna ["john", "jack"] al cabo de dos segundos.
// Ahora fÃjate como reescribiendo la función "retrieveUsers" se convierte en una promesa
function retrieveUsers_promisify() {
return new Promise( (resolve, reject) => {
justAnAsyncFunction('SELECT * FROM users', function(results) {
if (results.ok) {
resolve( results.users );
} else {
reject( new Error('Error') );
}
});
});
}
// Esto nos permite llamarla e interactuar con ella como promesa!
function getUserUsingPromises(cb) {
return retrieveUsers_promisify()
.then((results) => {
cb(results);
})
.catch((error) => {
console.error(error.message);
});
}
getUserUsingPromises(console.log); // esto es una promesa que tardará dos segundos en resolverse y dará este resultado: ["john", "jack"]
console.log(await retrieveUsers_promisify()); // También puedo hacer esto! Ojo, aquà estoy deteniendo la ejecución hasta que el código asÃncrono acabe de ejecutarse
- Ejemplo de una cadena de promesas en la que la primera provee los datos para que la segunda puedan ejecutarse:
const runAsyncFunctions = async () => {
const users = await getUsers(); // Esto retornarÃa un array
Promise.all(
users.map(async user => {
const userId = await getIdFromUser(user);
const capitalizedId = await capitalizeIds(userId);
})
);
}
runAsyncFunctions(); // esto es una promesa
Recuerda que async crea una promesa.
- Uso de async-await:
// Ejemplo 1:
async function cooking() {
const a = await obtainIngredientA();
const b = await obtainIngredientB();
const recipe = await newRecipe(a,b);
return recipe;
} // async convierte esta función en una promesa
cooking().then((result) => {
console.log(result);
}).catch((e) => {
// if 'cooking' function or 'then' method throw an error...
console.error(e.message);
});
// Ejemplo 2:
const getIngredients = async () => {
try {
const butter = await getButter();
const flour = await getFlour();
const sugar = await getSugar();
const eggs = await getEggs();
return [
butter,
flour,
sugar,
eggs,
];
} catch(e) {
return e;
}
}
getIngredients().then((result) => {
if (result instanceof Error) {
// if 'getIngredients' function throw an error...
console.error(result.message);
}
// ...
}).catch((e) => {
// if 'then' method throw an error...
console.error(e.message);
});
// Ejemplo 3:
const to = (promise) => {
return promise.then((data) => [null, data]).catch((err) => [err]);
};
async function getDataFromAsyncCode() {
const [err, response] = await to(justAnAsyncFunction());
if (err) throw new Error(`Error: ${err}`);
return response;
}
getDataFromAsyncCode(); // Esto es una promesa. Debes tratar los resultados con 'then()' y 'catch()';
// También puedes hacer esto:
console.log(await getDataFromAsyncCode()); // Ojo, estas deteniendo la ejecución del código
// Ejemplo 4:
// Traditional promise chaining
getIssue()
.then(issue => getOwner(issue.ownerId))
.then(owner => sendEmail(owner.email, 'Some text'));
// Using async functions! (Considera agruparlo bajo una función async si no quieres detener la ejecución del script)
const issue = await getIssue();
const owner = await getOwner(issue.ownerId);
await sendEmail(owner.email, 'Some text');
// Ejemplo 5:
async function foo(things) {
const listOfpromises = [];
for (let thing of things) { // use "for ... of" in arrays or sets; use "for ... in" in objects
// good: all asynchronous operations are inmediately started
listOfpromises.push( asyncFooFunction(thing) );
}
// now that all the asynchronous operations are running, here we wait until they all complete
return await Promise.all(listOfpromises);
}
// Ejemplo 6 (llamadas asÃncronas en paralelo):
async function parallelApiCalls() {
const [firstResult, secondResult] = await Promise.all([fetchFirst(), fetchSecond()]); // si una falla, no se espera a que complete el resto
return [firstResult, secondResult];
}
- Averiguar si una función es asÃncrona (async):
const isAsync = fooFunction.constructor.name === "AsyncFunction";
- Truco para ejecutar un código al momento y repetir su ejecución de manera periódica:
(function loop() {
// ... Your code!
setTimeout(loop, 2000);
})();
// También puedes hacer esto:
(async function loop() {
// ... Your code!
await foo();
setTimeout(loop, 2000);
})();
- Trabajando con el DOM:
// selectores:
document.querySelector('.foo'); // obtener el primer elemento dentro del documento que coincida con el selector
document.querySelectorAll('.foo'); // Obtener una lista de elementos
// otros selectores:
const biz = document.querySelector('.biz');
const children = biz.childNodes; // obtener los hijos de un nodo
const parent = biz.parentNode; // obtener el padre de un nodo
biz.classList.add('.is-active'); // añadir una clase a un elemento
biz.classList.remove('.hidden'); // quitar una clase a un elemento
biz.classList.toggle('.dark-mode'); // alternar una clase a un elemento
biz.classList.contains('.black-text'); // comprobar si un elemento tienen una clase
// crear un elemento e insertarlo en el DOM
const newChildElement = document.createElement('p');
const newChildElement.innerText = 'Example!';
biz.appendChild(newChildElement);
// eliminar un nodo del DOM
biz.remove();