Funciones


Introducción

Lo mejor que nos ofrece JavaScript son las funciones pero esta implementación es casi correcta en su totalidad. Pero como cabe esperar de JavaScript, no lo hace del todo bien. Una función encierra un conjunto de instrucciones. Las funciones son la unidad modular fundamental de JavaScript, se utilizan para la reutilización de códigos, la ocultación de información y la composición. Las funciones se utilizan para especificar el comportamiento de los objetos. En general, el arte de la programación es la factorización de un conjunto de requisitos en un conjunto de funciones y estructuras de datos.

 

Tabla de contenidos:

Objetos de una Función

Las funciones en JavaScript son objetos. Los objetos son colecciones de pares nombre/valor que tienen un vínculo oculto hacia el objeto prototipo. Los objetos producidos a partir de objetos literales están vinculados al Object.prototype. Los objetos de función están vinculados a Function.prototype (que a si mismo esta vinculado a Object.prototype). Cada función también es creada con dos propiedades adicionales ocultas: el contexto de la función y el código que implementa el comportamiento de la función.

Cada objeto de la función también es creada con una propiedad prototype. Su valor es un objeto con una propiedad constructora cuyo valor es la función. Esto es distinto del enlace oculto de Function.prototype. Luego veremos la complegidad de esto.

Dado que las funciones son objetos, y se pueden utilizar como cualquier otro valor. Las funciones pueden ser almacenadas en variables, objetos y matrices. Las funciones pueden ser pasadas como argumentos a otras funciones, y también las funciones pueden ser retornadas desde una funciones. Además, dado que las funciones son objetos, las funciones pueden tener métodos.

El asunto especial sobre las funciones es que se pueden invocar.

Literal de una Función

Los objetos de función se crean con literales de función: 

// Se crea la variable sumar y en ella se almacena una función
// la cual suma dos números.
var sumar = function (a, b) {
    return a + b;
};

sumar(5, 6);   // retorna 11

Un función literal consta de cuatro partes.

  • La primera parte es la palabra reservada function.
  • La segunda parte es opcional y corresponde al nombre que le damos a la función. La función puede utilizar este nombre para llamarse a sí misma de forma recursiva. Este nombre también puede ser utilizado por los depuradores y herramientas de desarrollo para identificar a la función. Si la función no tiene un nombre, como se muestra en el ejemplo anterior, se dice que la función es anónima.
  • La tercera parte es el conjunto de parámetros de la función, envueltos en paréntesis. Dentro de los paréntesis, tenemos un conjunto de cero o más nombres de parámetros, separados por comas. Estos nombres se definen como variables de la función. A diferencia de las variables comunes, en lugar de ser inicializadas como undefined, se inicializaran con los argumentos proporcionados cuando se invoque a la función.
  • La cuarta parte es un conjunto de instrucciones envueltas entre llaves { .... }. Estas declaraciones son el cuerpo de la función y se ejecutan cuando se invoca la función.

Una función literal puede aparecer en cualquier lugar donde pueda aparecer una expresión. Las funciones pueden ser definidas dentro de otras funciones. Una función interna también tiene acceso a sus parámetros y variables, y goza de acceso a los parámetros y variables de las funciones que se anidan en su interior. El objeto de la función creada por una función literal contiene un enlace al contexto exterior. Esto se denomina closure y esto es una enorme fuente de poder expresivo.

Invocación

Cuando invocamos a una función se suspende la ejecución de la función actual, se pasa el control y los parámetros a la nueva función, osea, a la función que se ha invocado. Además de los parámetros declarados, cada función recibe dos parámetros adicionales: this y arguments.

El parámetro this es muy importante en la programación orientada a objetos, y su valor se determina dependiendo del patrón de invocación que elijamos.

Hay 4 (cuatro) patrones de invocación en JavaScript:

  • el patrón de invocación como método
  • el patrón de invocación como función
  • el patrón de invocación como constructor
  • el patrón de invocación con apply.

Los patrones difieren dependiendo de como se inicializa el parámetro de bonificación this

El operador de invocación es un par de paréntesis ( ) que sigue a cualquier expresión que produzca un valor de la función. Los paréntesis pueden contener cero o más expresiones separadas por comas. Cada expresión produce un valor de argumento. A cada uno de los valores de los argumentos se asignará a los nombres de los parámetros de la función. No hay ningún error de ejecución cuando el número de argumentos y el número de parámetros no coinciden.

Si hay demasiados valores de argumentos, los valores de los argumentos adicionales serán ignorados. Si hay muy pocos valores de los argumentos, el valor undefined sustituirá a los valores faltantes.

No hay ninguna comprobación de tipo en los valores de los argumentos: Se puede pasar cualquier tipo de valor como parámetro.

El patrón de invocación como Método

Cuando una función es almacenada como la propiedad de un objeto, la llamamos método. Cuando un método es invocado, este es ligado a ese objeto. Si la expresión de invocación contiene un refinamiento (es decir, una expresión de . punto o expresión [índice]), esta es invocada como un método:

// Creamos el objeto llamado 'miObjeto'
// luego vamos a incrementar en 1 el valor recibido

var miObjeto = {
    resultado: 0,
    incrementar: function (valor) {
                
        // Comprobamos que 'valor' sea un número
        // Incrementamos dependiendo del valor recibido
        if( typeof valor === 'number' ) {
            this.resultado += valor;
            
        // Sino es un número, entonces vamos incrementando de a 1
        }else {
            this.resultado += 1;

        }
    }
};

miObjeto.incrementar( );          // El parámetro de la función incrementar está vacío
console.log(miObjeto.resultado);  // retorna 1

miObjeto.incrementar(52);
console.log(miObjeto.resultado);  // retorna 53

Este enlace tardío hace  a las funciones que utilizan this altamente reutilizables. Los métodos que obtienen el contexto del objeto desde this se denominan métodos públicos.

El patrón de invocación como Función

Cuando la función no es la propiedad de un objeto, entonces se invoca como una función:

var resultado = sumar(11, 2);  // la suma da 13

Cuando una función es invocada con este patrón, this es ligado al objeto global osea queda inicializado con el objeto global.

Esto fue una equivocación en el diseño del lenguaje. ¿ El lenguaje fue diseñado correctamente ? ... veamos, cuando la función interna es invocada, this todavía se encuentra ligado con la variable this de la función externa. Una consecuencia de este error es que el método no puede emplear una función interna para que le ayude a hacer su trabajo porque la función interna no comparte el método de acceso al objeto ya que su this está ligado al valor equivocado.

Afortunadamente, hay una solución fácil. Si el método define una variable y a esa variable le asignamos el valor de this, la función interna tendrá acceso a this a través de esta variable; para nuestro caso y por convención utilizaremos el nombre that para nuestra solución.

Entonces, por convención, el nombre de la variable queda en that:

// Aumenta miObjeto utilizando el método 'doble'.

miObjeto.doble = function ( ) {
    var that = this;
    
    // Solución alternativa.
    var ayudante = function ( ) {
        that.resultado = sumar(that.resultado, that.resultado);
    };

    ayudante();
    
    // Invocamos a ayudante como una función.
};

miObjeto.obtenerValor = function ( ) {
    var that = this;
    return that.resultado;
};

// Invocamos a doble como un método.
miObjeto.doble( );
console.log( miObjeto.obtenerValor() );    // 106

El patrón de invocación como Contructor

JavaScript es un lenguaje de herencia de prototipos. Esto significa que los objetos pueden heredar las propiedades directamente de otros objetos. El lenguaje es de clase libre.

Este es un cambio radical de la forma actual. La mayoría de los lenguajes de hoy son clásicos. La herencia de prototipos es poderosamente expresiva, pero no se entiende ampliamente. JavaScript en sí no confía en la naturaleza de sus prototipos, por lo que ofrece una sintaxis de objeto de decisiones que es una reminiscencia de los lenguajes clásicos. Pocos programadores clásicos encuentran la herencia de prototipos como aceptable, y la inspiración clásica oscurece la sintaxis de la verdadera naturaleza prototípica del lenguaje. Siendo esto lo peor de ambos mundos.

Si una función es invocada con el prefijo new, entonces un nuevo objeto será creado con un enlace oculto con el valor de la función del miembro prototype, y this será enlazado a ese nuevo objeto. El prefijo new también cambia el comportamiento de la instrucción de retorno. Luego veremo un eso.

// Creamos una función constructora llamada Actualidad.
var Actualidad = function (cadena) {
    this.estado = cadena;
};

// Damos todas las instancias de Actualidad al método público
// llamamos a obtener_estado
Actualidad.prototype.obtener_estado = function ( ) {
    return this.estado;
};

// Hacemos una instancia de Actualidad
var miActualidad = new Actualidad("alegre");

console.log(miActualidad.obtener_estado() ); // retorna 'alegre'

Las funciones que están destinadas a utilizar el prefijo new son llamadas constructoras. Por convención, se mantienen en las variables con el nombre en mayúsculas. Si un constructor es llamado sin el prefijo new, pueden suceder cosas muy malas sin una advertencia en tiempo de compilación o en tiempo de ejecución, por esto es que la convención de la capitalización es realmente importante.

No se recomienda el uso de este tipo de funciones constructoras.  Luego veremos mejores alternativas.

El patrón de invocación con apply

Como JavaScript es un lenguaje funcional orientado a objetos, las funciones pueden tener métodos.

El método apply nos permite construir una matriz de argumentos para utilizarlo al invocar a una función. También nos permite elegir el valor de this. El método apply recibe 2 dos parámetros. El primero es el valor que debe ser destinado a this. El segundo es un array de parámetros.

//Crea un arreglo de 2 números y los agrega.
var array = [3, 4];
var total = sumar.apply(null, array);
 
// el resultado es  7
// Crea un objeto con un miembro de su estado.
var estadoDelObjeto = {
    estado: 'A-OK'
};
 
// estadoObjeto no hereda de Actualidad.prototype,
// pero podemos invocar al método obtener_estado en
// estadoObjeto aunque estadoObjeto no tenga el método obtener_estado 

 
var estado = Actualidad.prototype.obtener_estado.apply(estadoDelObjeto);
// estado es 'A-OK'

arguments

Es un parámetro adicional que está disponible en las funciones cuando son invocadas, este parámetro es la matriz arguments y da acceso a todos los argumentos que recibe una función al ser invocada, incluyendo el exceso de argumentos que no fueron asignados a los parámetros. Esto hace que sea posible escribir funciones que toman un número indeterminado de parámetros:

// Creamos una función que añade un monton de cosas.

// Observe que la definición de la variable suma dentro de
// la función no interfiere con la variable suma definida fuera de la función. 
// La función sólo ve el interior. 
var suma = function ( ) {
    var i;
    var suma = 0;

    for (i = 0; i < arguments.length; i += 1) {
        suma += arguments[i];
    }

    return suma;
};

console.log( suma(4, 8, 15, 16, 23, 42) ); // 108

Este no es un modelo particularmente útil. Luego veremos cómo podemos añadir un método similar al de una matriz. Debido a un error de diseño, arguments no es realmente una matriz. Se trata de un objeto tipo matriz. arguments tiene la propiedad length, pero carece de todos los métodos de una matriz. Veremos una consecuencia de este error de diseño al final de este artículo.

Otro ejercicio interesante es el siguiente en donde podemos ver la respuesta de la matriz

function frutas(a, b){

    // retorna todos los elementos de la matriz ['banana', 'pera', 'manzana', 'durazno']
    console.log(arguments);

    // retorna 4 que es la cantidad total de elementos de la matriz    
    console.log(arguments.length);

    // retorna 'banana'
    console.log(arguments[0]);

    // retorna 'manzana'
    console.log(arguments[2]); // 'manzana'
 
    // retorna 2 que son el total de elementos declarados en la función
    console.log(frutas.length);
 
    if(arguments.length > frutas.length)
       console.log('ey! has enviado más parametros de los que tengo declarados!');
}

// invocamos a la función 'frutas' 
frutas('banana','pera','manzana','durazno');

Return

Cuando una función es invocada, su ejecución comienza con la primera instrucción, y finaliza al llegar al cierre } del cuerpo de la función, esto hace que la función devuelta el control a la parte del programa que invocó a la función.

La sentencia return se puede utilizar para hacer que la función retorne tempranamente. Cuando return es ejecutado, la función retorna inmediatamente sin ejecutar las instrucciones restantes.

Una función siempre devuelve un valor, si el valor return no es especificado, entonces la función retorna undefined.

Si la función fue invoca con el prefijo new y el valor return no es un objeto, entonces this (el nuevo objeto) es devuelto en su lugar.

Exceptions

JavaScript proporciona un mecanismo para el manejo de excepciones. Las excepciones son incidentes inusuales (aunque no completamente inesperados) que interfieren con el flujo normal de un programa. Cuando se detecta un incidente, el programa debe lanzar una excepción:

var sumar = function (a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {

        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        };
    }

    return a + b;

}

console.log( sumar("1", 4) );


La sentencia throw interrumpe la ejecución de la función. Se le debe paser un objeto exception que contiene la propiedad name que identifica el tipo de excepción, y la propiedad message que es un mensaje descriptivo. También puedes agregar otras propiedades.

El objeto exception será entregado a la cláusula catch en una declaración de try:

// Crea una función llamada prueba que llama a una nueva
// función sumar de forma incorrecta.

var prueba = function ( ) {
    try {
        sumar("siete");
    } catch (e) {
        console.log(e.name + ': ' + e.message);
    }
}

prueba( );

Si se produce una excepción dentro de un bloque try, el control pasará a la cláusula  catch.

Una declaración try tiene un solo bloque catch que captura todas las excepciones. 

Si su manejo depende del tipo de la excepción, entonces el manejador de excepción tendrá que inspeccionar a name para determinar el tipo de la excepción.

Ampliando Tipos

JavaScript permite que los tipos básicos del lenguaje sean ampliados. Anteriormente vimos que agregando un método a Object.prototype hace al método disponible para todos los objetos. Esto también lo podemos emplear en funciones, matrices, cadenas, números, expresiones regulares, y booleanos

Por ejemplo, ampliando Function.prototype podemos hacer que un método este disponible en todas las funciones:

Function.prototype.method = function (elNombredelMetodo, laFuncion) {
    this.prototype[elNombredelMetodo] = laFuncion;
    return this;
};

Al ampliar Function.prototype con el método method, ya no tendremos que escribir el nombre de la propiedad prototype. Ahora podemos esconder esa parte fea del procedimiento.

JavaScript no tiene un tipo de entero, y a veces es necesario extraer sólo la parte entera de un número. El método que proporciona JavaScript para hacer esto es feo. Podemos arreglarlo creando nuestro propio método al que le vamos a dar el nombre entero, y lo vamos a agregar a Number.prototype. Y dependiendo del signo del número utilizaremos Math.Ceil o Math.floor, para redondearlo (solo es un ejemplo del potencial que ofrece esto):

Number.method('entero', function ( ) {
    return Math[this < 0 ? 'ceil' : 'floor'](this);
});

console.log((-10 / 3).entero( )); // retorna -3

JavaScript carece de un método que elimine los espacios al final de una cadena. Esto es un descuido pero fácil de solucionar, vamos a crear el método llamado ajustar :

String.method('ajustar', function ( ) {
    return this.replace(/^\s+|\s+$/g, '');
});

console.log('"' + "  miel     ".ajustar( ) + '"');

Nuestro método ajustar utiliza una expresión regular. Luego veremos expresiones regulares.

Al ampliar los tipos básicos, podemos hacer mejoras significativas a la expresividad del lenguaje. Debido a la naturaleza dinámica de la herencia de prototipos de JavaScript, todos los valores están dotados de inmediato con los nuevos métodos, incluso los valores que se crearon antes de la creación de los métodos.

Los prototipos de los tipos básicos son estructuras públicas, por lo que se debe tener cuidado al mezclar las bibliotecas. Una técnica defensiva consiste en añadir un método sólo si el método conocido falta:

// agregamos un condicional para verificar que no exista 
// el nombre del método que hemos escogido.
Function.prototype.method = function (elNombredelMetodo, laFuncion) {
    if (!this.prototype[elNombredelMetodo]) {
        this.prototype[elNombredeMetodo] = laFuncion;
    }
};

Otra preocupación es que la instrucción for in interactúa mal con prototipos. Anteriormente vimos un par de maneras de mitigar esto: podemos utilizar el método hasOwnProperty para descartar a las propiedades heredadas, y podemos buscar tipos específicos.

Recursividad

Una función recursiva es una función que se llama a sí misma ya sea directamente o indirectamente. La recursión es una técnica de programación potente en el que un problema se divide en un conjunto de subproblemas similares, cada solución con una solución trivial. En general, una función recursiva se llama a sí mismo para resolver sus subproblemas.

La Torre de Hanoi es un famoso rompe-cabezas. El equipo incluye tres postes y un conjunto de discos de diferentes diámetros con agujeros en sus centros. Hay que apilar todos los discos en las entradas de cada poste, los discos más pequeños deben ir sobre los discos más grandes. El objetivo es mover cada pila al poste de destino moviendo un disco a la vez, nunca se debe colocar un disco grande sobre un disco más pequeño. Este rompecabeza tiene una solución recursiva trivial.

var hanoi = function (nroDisco, origen, auxiliar, destino) {
    if (nroDisco > 0) {
        hanoi(nroDisco - 1, origen, destino, auxiliar);
        console.log('Mover disco ' + nroDisco + ' de ' + origen + ' a ' + destino);
        hanoi(nroDisco - 1, auxiliar, origen, destino);
    }
};
hanoi(3, 'Origen', 'Auxiliar', 'Destino');

Se produce esta solución en tres discos:

Mover disco 1 de Origen a Destino
Mover disco 2 de Origen a Auxiliar
Mover disco 1 de Destino a Auxiliar
Mover disco 3 de Origen a Destino
Mover disco 1 de Auxiliar a Origen
Mover disco 2 de Auxiliar a Destino
Mover disco 1 de Origen a Destino

La función hanoi mueve una pila de discos de un poste a otro, utilizando el poste auxiliar si fuera necesario. El problema se divide en tres subproblemas. En primer lugar, se descubre el disco inferior moviendolo a la subpila por encima de ella a  poste auxiliar. A continuación, puede mover el disco del fondo al poste de destino. Finalmente, se puede mover la subpila del poste auxiliar al poste de destino. El movimiento de la subpila es manejado por autodenominado recursiva para resolver los subproblemas.

La función hanoi pasa el número del disco que desea mover y los tres poste que va a utilizar. Cuando se llama a sí misma, para tratar con el disco que está por encima del disco con el que está trabajando actualmente. Finalmente, se va a llamar con un número de disco que no existe. En este caso, no hace nada. Este acto de no hacer nada nos da la confianza de que la función no es recursiva por siempre.

Las funciones recursivas pueden ser muy eficaces en la manipulación de estructuras de árbol como el Document Object Model del navegador (DOM). A cada llamada recursiva se le da una pieza más pequeña del árbol para trabajar:

// Definimos la función recorrer_el_DOM que visita a cada nodo del
// árbol en orden de la fuente HTML, comenzando por algún nodo dado.
// Esto invoca a la función pasándole un nodo cada vez. 
// recorrer_el_DOM se llama a sí misma para procesar cada uno de
// los nodos secundarios.

var recorrer_el_DOM = function recorrer(nodo, funcion) {
    funcion(nodo);
    nodo = node.firstChild;

    while (nodo) {
        recorrer(nodo, funcion);
        nodo = nodo.nextSibling;
    }
};


// Definir la función getElementsByAttribute.
// Se necesita el nombre de cadena del atributo y el valor coincidente opcional.
// Llamamos a recorrer_el_DOM, le pasamos una función que busca el nombre del
// atributo en el nodo. Los nodos coincidentes se acumulan en una matriz de resultados.

var getElementsByAttribute = function (att, value) {
    var resultado = [];

    recorrer_el_DOM(document.body, function (nodo) {
        var actual = node.nodeType === 1 && node.getAttribute(att);
        if (typeof actual === 'string' &&
        (actual === value || typeof value !== 'string')) {
            resultado.push(node);
        }
    });

    return resultado;
};

Algunos lenguajes ofrecen una optimización de recursión de cola. Esto significa que si una función devuelve el resultado de invocarse recursivamente a sí mismo, entonces la invocación se sustituye con un bucle, esto puede acelerar considerablemente las cosas. Desafortunadamente, JavaScript no proporciona actualmente la optimización de recursión de cola. Las funciones que son recursivas muy profundamente pueden fallar al agotar la pila de retorno:

// Hacer una función factorial con recursión de cola.
// Es recursiva de cola porque devuelve el resultado de llamarse a sí mismo.
// JavaScript no optimiza actualmente esta forma.

var factorial = function factorial(i, a) {
    a = a || 1;
    if (i < 2) {
        return a;
    }

    return factorial(i - 1, a * i);
};
    
console.log(factorial(4)); // retorna 24

Alcance o Ámbito

El ámbito o alcance en un lenguaje de programación es lo que controla la visibilidad y tiempo de vida de las variables y parámetros. Este es un servicio importante para el programador, ya que reduce las colisiones de nombres y proporciona una gestión automática de la memoria:

var unaCosa = function ( ) {
    var a = 3, b = 5;

    var barras = function ( ) {
        var b = 7, c = 11;
        
        // En este punto, a es 3, b es 7, y c es 11
    
        a += b + c;
  
       // En este punto, a es 21, b es 7, y c es 11
    };

    // En este punto, a es 3, b es 5, y c no esta definido

    barras( );

    // En este punto, a es 21, b es 5
};

La mayoría de los leguajes con sintaxis como C tienen un alcance de bloque. Todas las variables definidas en un bloque (una lista de los estados envueltos con llaves) no son visibles desde el exterior del bloque. Las variables definidas en un bloque se pueden liberar cuando se termina la ejecución del bloque. Esta es una buena cosa.

Desafortunadamente, JavaScript no tiene ámbito de bloque a pesar de que su sintaxis de bloque sugiere que lo hace. Esta confusión puede ser una fuente de errores.

JavaScript tiene ámbito de función. Esto significa que los parámetros y variables definidas en una función no son visibles fuera de la función, y que una variable definida en cualquier lugar dentro de una función es visible en todas partes dentro de la función.

En muchos lenguajes modernos, se recomienda que las variables se declaren lo más tarde posible, en el primer punto de uso. Esto resulta ser un mal consejo en JavaScript ya que carece de alcance. Así que para el caso d JavasScript lo mejor es declarar todas las variables a utilizar en una función en la parte superior del cuerpo de la función.

Clausura "Closure"

La buena noticia sobre el alcance es que las funciones internas tienen acceso a los parámetros y variables de las funciones que están definidas dentro (con la excepción de this y arguments). Esto es una cosa muy buena.

Nuestra función getElementsByAttribute funcionó porque se declaró una variable resultado y la función interna que había pasado a (recorrer_el_DOM) también tenía acceso a la variable resultado.

Un caso más interesante es cuando la función interna tiene un tiempo de vida más largo que su función externa.

Anteriormente, creamos miObjeto que tenía un valor y un método de incrementar. Supongamos que queremos proteger el valor a cambios no autorizados.

 

En lugar de inicializar miObjeto con un objeto literal, vamos a inicializar miObjeto para llamar a una función que devuelva un objeto literal. Esta función definirá una variable de valor. Esa variable estará siempre disponible para los métodos incrementar y obtenerValor, pero el alcance de la función la mantendrá oculta para el resto del programa:

var myObject = function ( ) {
    var value = 0;

    return {
        increment: function (inc) {
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function ( ) {
            return value;
        }
    };
}( );

 

Nosotros no estamos asignando una función a miObjeto. Estamos asignando el resultado de invocar esa función. Observe el () de la última línea. La función devuelve un objeto que contiene dos métodos, y esos métodos siguen gozando del privilegio de acceso al valor de la variable.

El constructor Actualidad el cual definimos anteriormente produce un objeto con una propiedad de estado y un método obtener_estado. Pero esto no parece muy interesante. ¿Por qué al llamar a un método getter de una propiedad se podía acceder directamente? Sería más útil si la propiedad estado fuera privada. Por lo tanto, vamos a definir un tipo diferente de función Actualidad para lograr esto:

// Creamos una función llamada actualidad. Hacemos un 
// objeto con el método obtener_estado y una propiedad
// estado privada.


var actualidad = function (estado) {
    return {
        obtener_estado: function ( ) {
            return estado;
        }
    };
};

// Hacemos una instancia de actualidad.
var miActualidad = actualidad("asombrado?");
console.log(miActualidad.obtener_estado( ));

Esta función actualidad está diseñada para ser utilizado sin el prefijo new, por lo que el nombre no se escribe con mayúscula. Cuando llamamos a actualidad, devuelve un nuevo objeto que contiene el método obtener_estado. Una referencia a ese objeto se almacena en miActualidad. El método obtener_estado todavía tiene privilegios de acceso a la propiedad de estado de actualidad a pesar de que actualidad ya ha retornado. Entonces, obtener_estado no tiene acceso a una copia del parámetro; que tiene acceso al parámetro en sí. Esto es posible porque la función tiene acceso al contexto en el que se ha creado. Esto se denomina closure.

 

Veamos un ejemplo más útil:

// Definimos una función que establece el color de un nodo del DOM a
// amarillo y luego se desvanece a blanco

var desvanece = function (elNodo) {
    var nivel = 1;
    var paso = function ( ) {
        var hex = nivel.toString(16);
        elNodo.style.backgroundColor = '#FFFF' + hex + hex;

        if (nivel < 15) {
            nivel += 1;
            setTimeout(paso, 100);
        }
    };

    setTimeout(paso, 100);
};

desvanece(document.body);

Llamamos a desvanece enviandole como parámetro document.body (el nodo creado por la etiqueta HTML <body>). Establecemos la propiedad nivel a 1 y luego definimos la función paso. Llama a setTimeout, pasandole la función paso y el tiempo de (100 millisegundos). Luego retorna desvanece al terminar.

Es importante entender que la función interna tiene acceso a las variables actuales de las funciones externas y no a las copias con el fin de evitar el siguiente problema:

// MAL EJEMPLO 
// Creamos una función que asigna funciones de controlador de eventos a un conjunto de nodos de la manera equivocada. 
// Al hacer clic en un nodo, un cuadro de alerta supuestamente muestra el número ordinal del nodo. 
// Pero siempre muestra el número de nodos en su lugar. 

var add_the_handlers = function (nodes) {
    var i;
    
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function (e) {
            alert(i);
        };
    }
};

// FIN DEL MAL EJEMPLO

La función add_the_handlers estaba destina a dar a cada manejador un número único (i). Se produce un error porque las funciones de manejador estan ligadas a la variable i, no es el valor de la variable i en el tiempo de la función fue echa.

// UN MEJOR EJEMPLO
// Hacer una función que asigna las funciones manejadoras de eventos a un
// conjunto de nodos de la manera correcta.
// Al hacer clic en un nodo, un cuadro de alerta mostrará el ordinal del nodo.

var add_the_handlers = function (nodes) {
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function (i) {
            return function (e) {
                alert(e);
            };
        }(i);
    }
};

Ahora, en lugar de asignar una función a onclick, definimos una función y de inmediato invocamos la invocamos, pasando i. Esa función devolverá una función manejadora de eventos que se enlaza con el valor de i que fue pasada, no a la i se define en add_the_handlers. Esa función devuelta se asigna a onclick.

Callbacks

Las funciones pueden hacer que sea más fácil lidiar con eventos discontinuos. Por ejemplo, supongamos que hay una secuencia que comienza con la interacción del usuario, haciendo una petición del servidor, y finalmente la visualización de la respuesta del servidor. La forma ingenua a escribir que sería:

request = prepare_the_request( );
response = send_request_synchronously(request);
display(response);

El problema con este planteamiento es que una solicitud sincrónica través de la red dejará al cliente en un estado de congelación. Si la red o el servidor es lento, la degradación en la capacidad de respuesta será inaceptable.

Un mejor enfoque es hacer una solicitud asincrónica, proporcionando una función callback 'devolución de llamada' que se invoca cuando se recibe la respuesta del servidor. Una función asíncrona retorna de inmediato, por lo que el cliente no queda bloqueado:

request = prepare_the_request( );

send_request_asynchronously(request, function (response) {
    display(response);
});

Pasamos un parámetro de función a la función send_request_asynchronously que será llamada cuando la respuesta esté disponible.

Módulo

Podemos utilizar funcionesclosures para hacer módulos. Un módulo es una función o un objeto que presenta una interfaz pero que esconde su estado y la implementación. Mediante el uso de funciones para producir módulos, podemos eliminar casi por completo el uso de variables globales, mitigando así una de las peores características de JavaScript.

Por ejemplo, supongamos que queremos ampliar la secuencia con un método deentityify. Su trabajo consiste en buscar las entidades HTML en una cadena y reemplazarlos con sus equivalentes. Es lógico mantener los nombres de las entidades y de sus equivalentes en un objeto. Pero, ¿dónde debemos guardar el objeto? Podríamos ponerlo en una variable global, pero las variables globales son malas. Podríamos definirlo en la función misma, sino que esto tiene un costo de tiempo de ejecución debido a que el literal debe ser evaluado cada vez que se invoca la función. El método ideal es ponerlo en un closure, y tal vez proporcionar un método adicional que puede agregar entidades adicionales:

String.method('deentityify', function ( ) {
    // La tabla de entidad. Asigna nombres de
    // entidad de caracteres.

    var entity = {
        quot: '"',
        lt:
        gt:
        '<',
        '>'
    };

    // Retorna el método deentityify.
    return function ( ) {

        // Este es el método deentityify. Se llama al método
        // reemplazar cadena, busca por subcadenas que comienzan
        // con '&' y terminan en ';'. Si los caracteres entre ambas se
        // encuentran en la tabla de la entidad, entonces reemplaza la
        // entidad con el caracter de la tabla. Para esto se utiliza
        // una expresión regular.

        return this.replace(/&([^&;]+);/g,
            function (a, b) {
                var r = entity[b];
                return typeof r === 'string' ? r : a;
            }
        );
    };
}( ));

 

Observa la última línea. Inmediatamente invocamos a la función que acabamos de hacer con el operador (). Esa invocación crea y retorna la función que se convierte en el método deentityify.

document.writeln(
    '<">'.deentityify( )); // <">

 

El patrón de módulo se aprovecha de ámbito de la función y el closure para crear relaciones que son vinculantes y privadas. En este ejemplo, sólo el método deentityify tiene acceso a la estructura de datos de una entidad. 

 

El patrón general de un módulo es una función que define las variables privadas y funciones; crea funciones privilegiadas que, a través del closure, tendrá acceso a las variables privadas y funciones; y que devuelve las funciones privilegiadas o los almacena en un lugar accesible.

Utilizando el patrón de módulo puede eliminar el uso de variables globales. Promueve ocultación de información y otras buenas prácticas de diseño. Es muy eficaz en la encapsulación de aplicaciones y otros singletons.

También se puede utilizar para producir objetos que son seguros. Supongamos que queremos hacer un objeto que produce un número de serie:

var serial_maker = function ( ) {

    // Produce un objeto que genera cadenas únicas.
    // Una cadena única que se compone de dos partes:
    // Un prefijo y un número de secuencia. El objeto 
    // viene con métodos para configurar el prefijo y el
    // número de la secuencia y un método gensym que produce
    // cadenas únicas.

    var prefix = '';
    var seq = 0;
    return {
        set_prefix: function (p) {
            prefix = String(p);
        },
        set_seq: function (s) {
            seq = s;
        },
        gensym: function ( ) {
            var result = prefix + seq;
            seq += 1;
           return result;
        }
    };
};

var seqer = serial_maker( );
seqer.set_prefix = ('Q';)
seqer.set_seq = (1000);
var unique = seqer.gensym( ); // es único "Q1000"

Los métodos no hacen uso de 'this' o 'that'. Como resultado, no hay manera de poner en peligro a seqer. No es posible obtener o cambiar el prefix o seq salvo lo permitido por los métodos. El objeto seqer es mutable, por lo que los métodos podrían ser reemplazados, pero que todavía no tienen acceso a sus secretos. seqer es simplemente una colección de funciones y estas funciones son las capacidades que conceden facultades específicas para utilizar o modificar el estado secreto.

Si pasamos seqer.gensym a la función de un tercero, esta función sería capaz de generar cadenas únicas, pero sería incapaz de cambiar el prefix o seq.

Cascada

Algunos métodos no tienen un valor de retorno. Por ejemplo, es habitual que los métodos que establecen o cambian el estado de un objeto no retornen nada. Si tenemos estos métodos retornarán this vez de undefined, podemos activar cascadas. En una cascada, podemos llamar a muchos métodos en el mismo objeto en secuencia en una sola declaración. Una biblioteca Ajax que permite a las cascadas nos permitiría escribir en un estilo como éste:

getElement('myBoxDiv').
    move(350, 150).
    width(100).
    height(100).
    color('red').
    border('10px outset').
    padding('4px').
    appendText("Please stand by").
    on('mousedown', function (m) {
        this.startDrag(m, this.getNinth(m));
    }).
    on('mousemove', 'drag').
    on('mouseup', 'stopDrag').
    later(2000, function ( ) {
        this.
        color('yellow').
        setHTML("Que has echo !").
        slide(400, 40, 200, 200);
    }).
    tip('Esta caja es redimensionable');

En este ejemplo, la función getElement produce un objeto que da funcionalidad al elemento DOM con id="myBoxDiv". Los métodos nos permite movernos de elemento, cambiar sus dimensiones y el estilo, y agregar el comportamiento. Cada uno de estos métodos retorna el objeto, por lo que el resultado de la invocación se puede utilizar para la siguiente invocación.

La cascada puede producir interfaces que son muy expresivos. Puede ayudar a controlar la tendencia a hacer que las interfaces que tratan de hacer demasiado a la vez.

Curry

Las funciones son valores, y podemos manipular los valores de función de una manera interesante. Currying nos permite producir una nueva función mediante la combinación de una función y un argumento:

var add1 = add.curry(1);
document.writeln(add1(6)); // 7

add1 es una función que fue creada al pasar 1 para agregar 's método de curry. La función add1 añade 1 a su argumento. JavaScript no tiene un método de curry, pero podemos arreglar eso ampliando Function.prototype:

Function.method('curry', function ( ) {
    var args = arguments, that = this;

    return function ( ) {
        return that.apply(null, args.concat(arguments));
    };
});
// Alguna cosa no esta bien ...

El método curry funciona creando un closure que mantiene a la función original y los argumentos de curry. Retorna una función que, cuando se invoca, devuelve el resultado de llamar a esa función original, pasándole todos los argumentos de la invocación de curry y la invocación actual. Se utiliza el método concat Array para concatenar los dos conjuntos de argumentos juntos.

Desafortunadamentecomo vimos anteriormente, la matriz arguments no es una matriz, por lo que no tiene el método concat. Para solucionar esto, aplicaremos el método slice en ambas matrces de los argumentos. Esto genera matrices que se comportan correctamente con el método concat:

Function.method('curry', function ( ) {
    var slice = Array.prototype.slice,
    args = slice.apply(arguments),
    that = this;
    return function ( ) {
        return that.apply(null, args.concat(slice.apply(arguments)));
    };
});

Memoization

Las funciones pueden usar objetos para recordar los resultados de las operaciones anteriores, haciendo posible evitar un trabajo innecesario. Esta optimización se llama memoization. Los objetos y matrices de JavaScript son muy conveniente para hacer esto.
Digamos que queremos una función recursiva para calcular los números de Fibonacci. Un número de Fibonacci es la suma de los dos números anteriores. En una serie Fibonacci los dos primeros números son 0 y 1:
var fibonacci = function (n) {
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};

for (var i = 0; i <= 10; i += 1) {
    console.log('// ' + i + ': ' + fibonacci(i));
}

// 0: 0
// 1: 1
// 2: 1
// 3: 2
// 4: 3
// 5: 5
// 6: 8
// 7: 13
// 8: 21
// 9: 34
// 10: 55

Esto funciona, pero se está haciendo una gran cantidad de trabajo innecesario. La función de Fibonacci se llama 453 veces. Llamamos 11 veces, y se hace llamar 442 veces en valores de computo que fueron probablemente ya recientemente calculados. Si nosotros memoizamos la función, podemos reducir significativamente su carga de trabajo.
 
Vamos a mantener nuestros resultados memoizado en una matriz memo que podemos ocultar con un closure. Cuando nuestra función es llamada, primero busca para ver si ya se conoce el resultado. Si lo hace, se puede regresar de inmediato:
var fibonacci = function ( ) {
    var memo = [0, 1];

    var fib = function (n) {

        var result = memo[n];

        if (typeof result !== 'number') {
            result = fib(n - 1) + fib(n - 2);
            memo[n] = result;
        }

    return result;
};
return fib;
}( );

Esta función devuelve los mismos resultados, pero se llama sólo 29 veces. La llamamos 11 veces. Se llamaba a sí mismo 18 veces para obtener los resultados previamente memoizados.

var fibonacci = memoizer([0, 1], function (shell, n) {
return shell(n - 1) + shell(n - 2);
});

Para la elaboración de funciones que producen otras funciones, se puede reducir significativamente la cantidad de trabajo que tenemos que hacer. Por ejemplo, para producir una función de factorial memoizing, sólo tenemos que suministrar la fórmula factorial básica:

var factorial = memoizer([1, 1], function (shell, n) {
    return n * shell(n - 1);
});

 

Añadir nuevo comentario

CAPTCHA
Esta pregunta es para comprobar si usted es un visitante humano y prevenir envíos de spam automatizado.
9 + 7 =
Resuelva este simple problema matemático y escriba la solución; por ejemplo: Para 1+3, escriba 4.