sábado, 19 de enero de 2013

Debounced Functions

John Hann, en una entrada en su blog propone un concepto muy interesante que llama debouncing. El termino no tiene una traducción exacta en español, sería algo así como eliminar rebotes. Es un término sacado de los circuitos electromecánicos. Cuando un contacto se cierra se producen micro-rebotes (bounce significa 'rebotar') que hacen que el circuito esté cerrado/abierto varias veces antes de que se cierre definitivamente. Estas múltiples señales deben eliminarse para enviar una sola, de circuito cerrado cuando el estado es estable.

Se envía una sola señal cuando el estado se ha estabilizado


En el mundo JavaScript el concepto se aplica para funciones que queremos que se ejecuten una sóla vez aunque el evento que las dispara se lance cientos de veces en un determinado periodo.

Un ejemplo sencillo de uso, sacado del blog de Paul Irish, sería un callback para el evento resize. Firefox lanza sólo un evento cuando el usuario ha terminado de redimensionar la ventana, pero los demás navegadores lanzan eventos continuamente mientras la ventana se está redimensionado. Para ejecutar la función sólo una vez en todos los casos, podemos llamar a un debounced callback  que sólo se ejecutará una vez.

Básicamente se trata de que durante un determinado lapso de tiempo (configurable) se absorban todas las llamadas a la función y se ejecute sólo una. La función sólo se lanza cuando las llamadas se han estabilizado durante este lapso de tiempo, es decir una vez que ha pasado el tiempo fijado y no se ha producido una nueva llamada.

Es muy útil para eventos relacionados con el arrastre del ratón, que se disparan continuamente mientras el ratón se está moviendo.

Podemos crear una utilidad que nos devuelva una versión 'debounced' de una función cualquiera. Esta sería la función, sacada del ejemplo de Paul Irish que comentaba antes:
  var debounce = function (func, threshold, execAsap) {
      var timeout;
      return function debounced () {
          var obj = this, args = arguments;
          function delayed () {
              if (!execAsap)
                  func.apply(obj, args);
              timeout = null; 
          };
 
          if (timeout)
              clearTimeout(timeout);
          else if (execAsap)
              func.apply(obj, args);
 
          timeout = setTimeout(delayed, threshold || 100); 
      };
  }
Los parámetros son los siguientes:
  • func: La función original que queremos convertir en 'debounced' 
  • threshold: El lapso de tiempo, en ms, durante el que tienen que estabilizarse las llamadas ( no producirse ninguna nueva). Es opcional, 100ms es el valor por defecto. 
  • execAsap: Opcional. Por defecto la función se ejecuta al final del periodo de estabilización, con este parámetro a *true* la función se ejecuta inmediatamente, antes del periodo de estabilización

Si, por ejemplo, tenemos una función showMessage y queremos usarla como debounced callback para el evento resize, podemos hacer:

$(window).resize( debounce( showMessage) );


lunes, 7 de enero de 2013

Lo peor de JavaScript

El libro de Douglas Crockford "JavaScript, the good parts" incluye un apéndice con las que son, a su juicio, las peores partes del lenguaje. Este es un resumen de esos puntos negros que conviene conocer y evitar (algunos se han solucionado en ECMAScript 5).

Variables globales

Las variables globales son variables visibles en cualquier contexto y el problema más grave de JavaScript es su dependencia de ellas. Al no existir un compilador, la única manera de enlazar unos scripts con otros es mediate el uso del contexto global.

Este tipo de variables pueden ser modificadas por cualquier script en cualquier momento, incluyendo una librería externa que puede utilizar el mismo nombre de variable que nosotros para otro propósito.

Para agravar el problema, cualquier variable que se declare sin var, en cualquier contexto, se convierte automáticamente en global.

myVar = "hello";   //myVar es una variable global

Contexto

El contexto (scope) de una variable en JavaScript es la función en la que está declarada (o el global si no está dentro de ninguna).



function test() {
  var one = 1;
  
  // var one is visible inside the function

}

// var one is not visible here

En la gran mayoría de los lenguages de programación, las variables tienen visibilidad sólo dentro del bloque en el que se crean ( un bloque se delimita entre llaves ). La sintaxis de JavaScript permite definir bloques, pero no proporciona un contexto de bloque para sus variables.


if ( valid ) {
    var myVar = "this is valid";
    …
    //esto es un bloque
    //myVar está declarada en este bloque
    //pero será visible fuera de él
    //No tiene un contexto de bloque

}  
  // myVar es también visible aqui

Inserción automática de punto y coma

El punto y coma al final de una sentencia no es necesario en JavaScript, el intérprete lo inserta automáticamente. Esto parece cómodo pero en realidad puede dar lugar a errores muy difíciles de encontrar al colocar el ';' en lugares incorrectos.

Por ejemplo, si queremos devolver un objeto en una función y lo escribimos así:


…
…
return
{
    result: true
};


En realidad estaremos devolviendo undefined porque el punto y coma se inserta justo detras de return. No habrá warning ni error. Para evitar estos problemas es recomendable incluir siempre los ';' cuando escribimos el código.

Palabras reservadas

JavaScript tiene más de 60 palabras reservadas de las que la mayoría no se utilizan realmente en el lenguaje. El problema es que no pueden utilizarse como nombres de variables, parámetros, objetos o funciones.

Si queremos utilizarlas como propiedades de un objeto, deben ir entre comillas y no puede utilizarse la notación '.' para referenciarlas:


var myObject = { 'final': true}; //final es una palabra reservada
myObject.final = false;          // Error
myobject['final'] = false;       // Correcto

Unicode

La implementación de Unicode en JavaScript es obsoleta. Se diseñó para representar un total de 65.563 caracteres pero actualmente Unicode soporta más de 1 millón.

El problema es que JavaScript representa siempre los caracteres con 16 bits. Cuando se necesitan más bits para representarlo ( por encima de 65.563 ) JavaScript lo considera como 2 caracteres distintos, cada uno con 16 bits.

typeof

El operador typeof tiene graves fallos que nos obligan a manejarlo con mucho cuidado. Por ejemplo, si hacemos:

typeof null;   //returns "object"

Nos devolverá "object". Un resultado que no es lógico ni es de mucha ayuda. El operador también devuelve "object" para un array.

Para expresiones regulares como:

typeof /a/;  

El resultado varía. En algunas implementaciones se devuelve "object" y en otras "function".

parseInt

la función parseInt(string, radix) se utiliza para transformar un string en un entero. El parámetro opcional radix indica la base en la que está representado el entero.

Cuando se utiliza la función sin indicar radix es facil que se produzcan errores porque cualquier string con un "0" delante se evalua como un entero en base 8.

parseInt("09"); // 0 porque el 9 no existe en base 8
parseInt("046"); // 38, es 46 en base 8 
parseInt("046", 10); // 46. Indicamos explicitamente que trabajamos en decimal

Para evitar estos errores es recomendable indicar siempre el radix. ECMAScript 5 corrige este problema considerando siempre que la base es decimal por defecto.

Operador +

El operador + está sobrecargado en JavaScript y puede sumar números o concatenar strings, dependiendo únicamente del tipo de los parámetros.

  • Si cualquiera de los operandos es un string vacio, devuelve el otro operando convertido en un string.
  • Si los dos operandos son números devuelve la suma.
  • En cualquier otro caso convierte ambos operandos en strings y los concatena.

Este comportamiento es una fuente de errores.

Números en coma flotante

JavaScript implementa el estándar IEEE 754 ( Binary Floating-point Arithmetic ) que no es adecuado para el manejo de fracciones. Produce pequeños errores debidos al redondeo.

Una forma de evitar los errortes es intentar trabajar siempre con enteros con un factor de escalado.

NaN

NaN significa "Not a Number" y es un valor definido en el estandar IEEE 754 del que hablamos antes.

Lo curioso de NaN son los siguientes comportamientos:

    typeof NaN === 'number';     //true
    NaN === NaN;                 //false
    NaN !== NaN;                 //true
    

Es decir, typeof NaN nos indica que es un número pese a que representa exactamente lo contrario. Ademas, NaN no es igual a si mismo.

Arrays

JavaScript no tiene arrays reales, con memoria consecutiva reservada para los elementos. Los implementa como objetos cuyos índices son enteros. Esto tiene dos consecuencias negativas:

  • El rendimiento es bastante peor que con arrays reales
  • El operador typeof no distingue entre objetos y arrays

Valores false

Los siguientes valores son false en JavaScript:

  • 0
  • NaN
  • ""
  • false
  • null
  • undefined

La confusa diferencia entre null y undefined puede llevar a error. Lo más grave es que undefined y NaN no son constantes, son variables globales y su valor puede modificarse causando errores en el resto de los scripts de la página.

hasOwnProperty

Este método puede utilizarse para filtrar, en un bucle for in, las propiedades de un objeto que vienen heredadas de su prototipo. El problema es precisamente que es un método y no un operador. Cualquier objeto puede reemplazarlo por su propia versión o por cualquier valor, haciendolo poco fiable.

Object

El problema en este caso es que los objetos en JavaScript nunca están realmente vacios. Heredan las propiedades de la cadena de prototipos. Si no tenemos esto siempre en cuenta podemos incurrir en errores provocados por propiedades/métodos con los que no contamos.