Skip to content

Instantly share code, notes, and snippets.

@gabmontes
Last active September 13, 2015 15:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gabmontes/9b72b871fc7862243f75 to your computer and use it in GitHub Desktop.
Save gabmontes/9b72b871fc7862243f75 to your computer and use it in GitHub Desktop.

Repaso de JavaScript

Objetivos

  • Repasar conceptos de JavaScript que lo hacen diferente de otros lenguajes para dejar una base firme de conocimientos y evitar caer en problemas típicos que aparecen al llegar desde otros lenguajes estructurados o con un paradigma clásico de orientación a objetos.
  • Presentar y comprender patrones usados muy frecuentemente en JavaScript para poder resolver problemas típicos sin rápidamente y de una manera óptima y elegante.

Características

JavaScript es un lenguaje de programación que tiene las siguientes características principales:

  • imperativo: su sintaxis deriva de la línea del lenguaje C y C++
  • funcional: las funciones se pueden manipular como cualquier otra entidad dentro de un programa, incluso pueden ser pasadas a funciones y devueltas como resultado de la ejecución de una función
  • dinámico: existen 6 tipos de datos que se chequean y convierten dinámicamente: Boolean, Number, String, Object, undefined y null
  • orientado a objetos: los objetos son uno de los tipos de dato fundamentales
  • herencia basada en prototipos: cada objeto tiene referencias a un objeto base o prototipo del cual hereda propiedades

El modelo de programación, haber estado diseñado originalmente para correr en navegadores, es asíncrono, con un solo hilo de ejecución y orientado a eventos.

Consideraciones con los tipos de datos

Boolean

El tipo booleano está compuesto por los valores true y false. Hay que tener presente que, al ser un lenguaje dinámico, los tipos se convierten automáticamente según las operaciones involucradas.

Cuando una expresión debe resolverse al tipo Boolean, los valores 0, "", null y undefined van a convertirse en false. Cualquier otro valor, incluso {} y [] serán convertidos a true.

Number

Todos los números son representados internamente como de punto flotante de doble precisión. Por eso:

0.1 + 0.2 // devuelve 0.30000000000000004

String

Todas las cadenas de texto son de este tipo. No existe el concepto de "caracter" de otros lenguajes.

Object

Todos los objetos son conjuntos asociativos. Incluso las funciones son objetos.

undefined

Es el valor de las propiedades no definidas de un objeto. También es lo que devuelve una función que no devuelve nada explícitamente.

null

Es un objeto que representa al objeto nulo. Es distinto de {}, que es un objeto sin propiedades.

Herencia por prototipos

A diferencia de los modelos de objetos tradicionales con clases, instancias y herencia simple o múltiple a través de clases o interfaces, en JavaSctipt todos los objetos son instancias y tienen una referencia a un objeto base, llamado "prototipo", del cual heredan propiedades.

Programación funcional

Es un estilo de programación en el cual se basa en el uso de funciones para transformar entradas en salidas, a diferencia de los estilos imperativos tradicionales de lenguajes estructurados en donde el modelo de programación se basa en cambios de estado del propio proceso.

var usuarios = ["Juan", "Pedro", "Héctor"];

// estilo imperativo
// el estado del proceso se mantiene en la variable i
for (var i = 0; i < usuarios.length; i++) {
    procesar(usuarios[i]);
}

// estilo funcional
usuarios.forEach(procesar);

Los objetos globales String y Array tienen muchas funciones que permiten utilizar este estilo.

Ejercicios

Ejercicio 1:

Dado un array de números cualquiera, por ejemplo [1, 4, 2, 0, 6, 2], desarrollar una función que devuelva la cantidad de veces que un elemento es 0. Se la debería usar así:

var lista = [1, 4, 2, 0, 6, 2];
contarCeros(lista); // devuelve 1

No usar for dentro de la función sino las funciones propias del objeto Array.

Ejercicio 2:

Extender la función anterior para que evalúe cualquier condición. Por ejemplo:

function esCero(valor) {
    return valor === 0;
}
contarSi(esCero, lista); // devuelve 1

Ejercicio 3:

Reimplementar contarCeros utilizando contarSi.

Ejercicio 4:

Resolver el ejercicio anterior sin utilizar Array.filter.

Alcance, hoisting y clausuras

Las variables tienen alcance dentro de la función que son declaradas, salvo las variables que se crean en el contexto global.

var variableGlobal = "global";

function verificarAlcance() {
    var variableLocal = "local";

    console.log(variableGlobal); // global
    console.log(variableLocal); // local
}

verificarAlcance();
console.log(variableGlobal); // global
console.log(variableLocal); // ReferenceError!

Otra particularidad a tener en cuenta es el hoisting (izado) de las variables, que significa que son creadas (no inicializadas) al comienzo de la ejecución de la función, más allá de que estén declaradas en cualquier lado del cuerpo de la misma.

function pruebaHoisting() {
    console.log(nombre); // undefined
    var nombre = "Juan";
    console.log(nombre); // Juan
}
pruebaHoisting();

Se pueden crear funciones dentro de funciones y estas funciones internas tienen acceso a las variables de la función externa, aún luego de haber salido de dicha función.

function crearPrefijo(prefijo) {
    var str = prefijo + " ";

    return function (nombre) {
        return str + nombre;
    }
}

var prefijarDr = crearPrefijo("Dr.");
prefijarDr("Gonzalez"); // Dr. Gonzalez

var prefijarSr = crearPrefijo("Sr.");
prefijarSr("Pérez"); // Dr. Pérez

Estas funciones internas mantienen una referencia a un objeto donde están declaradas todas las variables de la función externa, llamada clausura o closure. A su vez, la función externa también mantiene una referencia similar hasta llegar al objeto global, generándose una cadena de clausuras o closure chain.

ES5 y el modo estricto

ECMAScript 5, inicialmente 3.1, tuvo foco en seguridad y mejora de las librerías base, siempre pensando en mantener compatibilidad. Los puntos más destacados son:

  • Nuevas funciones del objeto Object: create(), defineProperty()
  • Nuevas funciones de strings: trim()
  • Nuevas funciones de arrays: filter(), forEach(), indexOf(), map(), reduce()
  • Nuevas funciones del objeto Date: now()
  • Nuevas funciones del objeto JSON: parse(), stringify()
  • Métodos de acceso (get y set)
  • Modo estricto o strict mode

En el modo estricto está prohibida la creación de variables globales sin el uso de var, la duplicación de propiedades en la definición de un objeto o parámetros de función, extiende la lista de palabras reservadas que no se pueden usar como propiedades o nombres de función, entre otros cambios.

function nombreCompleto(prefijo, nombre, nombre) {
    "use strict";
    return prefijo + " " + nombre + " " + apellido; // SyntaxError!
}

nombreCompleto("Dr.", "Juan", "Pérez");

Patrón "módulo"

Es muy utilizado en JavaScript y permite aislar estados y funciones del resto del código. La construcción más sencilla se basa en funciones anónimas que se ejecutan inmediátamente (IIEF, Immediately-Invoked Function Expression) que devuelven un objeto conteniendo la interfaz pública del módulo.

var modulo = (function () {

    // variables privadas
    var clavePublica = "secreto normal";
    var clavePrivada = "secreto de Estado";

    // interfaz pública
    return {
        obtenerClave: function () {
            return clavePublica;
        }
    };
})();

modulo.obtenerClave(); // secreto normal

Ejercicios

Ejercicio 1

Crear un módulo que contenga en un array privado una lista de nomrbes de usuario. Este módulo debe exponer solo una función para obtener la lista:

var usuarios = (function () {
    // ...
})();

usuarios.obtenerTodos(); // ["Juan", "Ana", "Pedro"]

Ejercicio 2

Extender el módulo con una función para agregar usuarios y otra para obtener la cantidad de usuarios.

Ejercicio 3

Extender el módulo para poder eliminar usuarios de la lista.

Callbacks y promesas

En el modelo de programación orientada a eventos y asíncrona, es necesario el uso de callbacks, funciones que son llamadas una vez que la tarea asíncrona es completada:

// simula una consulta asíncrona a la base de datos
function obtenerUsuarios(callback) {
    setTimeout(function () {
        var usuarios = ["Juan", "Ana", "Pedro"];
        callback(usuarios);
    }, 250); // demora 250 ms
}

obtenerUsuarios(function (usuarios) {
    console.log(usuarios); // ["Juan", "Ana", "Pedro"]
});

Cuando se trabaja con Node.js, la convención es que las funciones callback siempre reciben como primer parámetro una variable que representa el estado de error de la operación:

obtenerUsuariosNode(function (err, usuarios) {
    if (err) {
        console.log(err.message);
        return;
    }
    console.log(usuarios); // ["Juan", "Ana", "Pedro"]
});

Cuando es necesario encadenar operaciones asíncronas, el código puede volverse difícil de mantener:

// agregar un permiso de "borrar" a usuarios
obtenerUsuarios(function (usuarios) {
    usuarios.forEach(function (usuario) {
        obtenerRoles(usuario, function (roles) {
            grabarRoles(usuario, roles.push("borrar"));
        });
    });
});

Asimismo, puede volverse muy complicado el manejor de errores en pasos intermedios.

// agregar un permiso de "borrar" a usuarios considerando errores
obtenerUsuarios(function (err, usuarios) {
    if (err) {
        console.log(err.message);
        return;
    }
    usuarios.forEach(function (err, usuario) {
        if (err) {
            console.log(err.message);
            return;
        }
        obtenerRoles(usuario, function (err, roles) {
            if (err) {
                console.log(err.message);
                return;
            }
            grabarRoles(usuario, roles.push("borrar"), function (err) {
                if (err) {
                    console.log(err.message);
                    return;
                }
            });
        });
    });
});

Un patrón de desarrollo que facilita la forma en la que se escribe el código asíncrono y, al mismo tiempo, el manejo de errores es el uso de promesas. Una promesa es un objeto que representa una ejecución de una operación asíncrona y puede estar en uno de tres estados: "pendiente", "completa" o "rechazada". En estos últimos dos casos, se ejecutará una función de continuación definida durante la creación de la promesa.

// simula una consulta asíncrona a la base de datos usando promesas
function obtenerUsuarios() {
    return new Promise(function (fulfill) {
        setTimeout(function () {
            var usuarios = ["Juan", "Ana", "Pedro"];
            fulfill(usuarios);
        }, 250); // demora 250 ms
    });
}

obtenerUsuarios().then(function (usuarios) {
    console.log(usuarios); // ["Juan", "Ana", "Pedro"]
});

Todas las promesas tienen una propiedad then que recibe la función que se va a ejecutar al resolverse dicha promesa. Incluso es posible encadenar promesas:

function aMayusculas(palabra) {
    return palabra.toUpperCase();
}

obtenerUsuarios().then(function (usuarios) {
    return usuarios.map(aMayusculas);
}).then(function (usuarios) {
    console.log(usuarios); // ["JUAN", "ANA", "PEDRO"]
});

Incluso es posible definir funciones a realizarse sobre promesas que ya han sido resueltas anteriormente:

function aMinusculas(palabra) {
    return palabra.toLowerCase();
}

var promesa = obtenerUsuarios().then(function (usuarios) {
    console.log("promesa resuelta!");
    return usuarios.map(aMinusculas);
});

promesa.then(function (usuarios) {
    return usuarios.map(aMinusculas);
}).then(function (usuarios) {
    console.log(usuarios); // ["JUAN", "ANA", "PEDRO"]
});

El ejemplo anterior de roles y permisos, de estar implementado con promesas, se podría escribir así:

// agregar un permiso de "borrar" a usuarios con promesas
obtenerUsuarios.then(function (usuarios) {
    usuarios.forEach(function (usuario) {
        obtenerRoles(usuario).then(function (roles) {
            grabarRoles(usuario, roles.push("borrar"));
        });
    });
});

El manejo de errores se realiza pasando a then una segunda función que se ejecutará en caso de error. En ese caso, la cadena de promesas se corta.

// agregar un permiso de "borrar" a usuarios con promesas
obtenerUsuarios.then(function (usuarios) {
    // procesarlos
}, function (err) {
    // manejar el error
});

Es conveniente revisar la compatibilidad de los diferentes navegadores o entornos como Node.js respecto del soporte nativo de promesas. En caso de querer dar soporte en ambientes que todavía no las tienen implementadas, es posible utilizar librerías como bluebird, Q, rsvp.js, etc. las que implementan la especificación Promises/A+, una de las más aceptadas.

Ejercicios

Ejercicio 1

Las siguientes funciones simulan operaciones asíncronas de acceso a datos de un sistema:

function obtenerUsuario(nombre) {
    return new Promise(function (fulfill, reject) {
        if (nombre === "Juan") {
            fulfill({
                rol: "usuario"
            });
        } else if (nombre === "Pedro") {
            fulfill({
                rol: "invitado"
            });
        } else {
            reject(new Error("Usuario desconocido"));
        }
    });
}
function registrarAcceso(nombre, rol) {
    return new Promise(function (fulfill, reject) {
        console.log("Usuario " + nombre + " ingresa como " + rol);
        fulfill();
    })
}
function solicitarRegistro() {
    console.log("Debe registrarse");
}
function notificarError(mensaje) {
    console.log("Error: " + mensaje);
}

Realizar una función login() que reciba un nombre de usuario, obtenga sus datos y registre su acceso. Utilizar las funciones de solicitud de registro y notificacion de errores para los casos que sea necesario.

function login(nombre) {
    // ...
}
login("Pedro");
// Usuario Pedro ingresa como invitado
login("Juan");
// Usuario Juan ingresa como usuario
login("Pablo");
// Error: Usuario desconocido
// Debe registrarse

Ejercicio 2

Extender la función login para que pueda encadenarse una operación adicional en la cadena de promesas, llamando a la siguiente función en caso de un ingreso exitoso:

function registrarEstadísticas(datos) {
    console.log("Estadísticas agregadas: " + datos.nombre + ", " + datos.rol + ", " + Date(datos.hora));
}

function login(nombre) {
    // ...
}
login("Pedro").then(registrarEstadísticas)
// Usuario Pedro ingresa como invitado
// Estadísticas agregadas: Pedro, invitado, Sep 05 2015

Si este ejercicio se realiza en un browser, utilizar Google Chromeu otro que posea soporte nativo de promesas. Si se realiza en Node.js, incluir algún módulo de promesas como los nombrados anteriormente.

Recursos complementarios

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment