viernes, 17 de febrero de 2012

EVITANDO Y EXPLOTANDO LOS BACHES DE JAVASCRIPT


JavaScript es un lenguaje Gestalt.

Uno de los sentimientos hacia JavaScript que voltea entre la elegancia y el asco, sin tránsito por estados intermedios.

La clave para ver JavaScript tan elegante es la comprensión de sus defectos, y saber cómo evitar, evitar o incluso explotarlos.

Para entender el uso de los terminos evitar / corregir / explotar favor leer el libro de: Doug Crockford - Javascript: The Good Parts.


Doug tiene una toma un poco diferente y más elaborada de las partes malas y partes horribles, así que estoy compartiendo mi punto de vista sobre los cuatro temas que me han causado más dolores de cabeza en el pasado:

  • Cómo solucionar ámbito de bloque roto con With;
  • Los cuatro (! no tres) significados de esto;
  • La promoción de argumentos a una matriz; y
  • Evitando truthiness.
Cuando chocan los baches: var frente a with

Ámbito de un bloque léxico en JavaScript está roto, y piense en el uso de with considerada de forma pobre, es una buena solución a este problema.

En la mayoría de lenguajes de corchetes, los bloques delimitan el alcance léxico. Por ejemplo, en C o Java:

{ 
    int i = 13 ;
    { 
       int i = 42 ; 
       print(i) ;
    }
    print(i) ;
 }

Pero, este código imprime 42 y luego 13.

Y en Javascript:


{
    var i = 13 ;
    {
       var i = 42 ;
       console.log(i) ;
    }
    console.log(i) ;
 }
Este código imprime 42 y 42.

En JavaScript, solo las funciones introducen un nuevo ámbito de léxico y las declaraciones de las variables están implícitamente realzadas a este nivel.

Por ejemplo:

function f ()  {
  var i = 13 ;
  {
     var i = 42 ;
     print(i);
  }
  print(i) ;
}

Es equivalente a:


function f () {
  var i ;
  i = 13 ;
  {
     i = 42 ;
     print(i) ;
  }
  print(i) ;
}
Aparte: elevación bajo el capó

JavaScript lleva la elevación a los extremos.

El siguiente programa - una referencia única para x - provoca un error de referencia:


 x ; // ReferenceError: x is undefined
pero el siguiente programa está bien, porque se declaró var x:


if (false) {
   var x ;
 }
 x ; // No problem! x is declared in this scope.
Por extensión, debe ser el caso (y es) que el siguiente también es legal:


x ; // No problem! x is declared in this scope.
 if (false) {
   var x ;
 }
Función de elevación

La historia de la función de elevación es más desordenado.

El siguiente código funciona:


console.log(fact(3)) ;

 function fact(n) {
  return (n == 0) ? 1 : n*fact(n-1) ;
 }
porque la definición de hecho es izado a la parte superior del bloque.

Así, el siguiente también funciona:

 {
   console.log(fact(3)) ;
   { 
     function fact(n) {
       return (n == 0) ? 1 : n*fact(n-1) ;
     }
   }
 }
Pero, el siguiente falla.

 if (false) {
   function fact(n) {
    return (n == 0) ? 1 : n*fact(n-1) ;
   }
 }
en la mayoría de las implementaciones de JavaScript.

Las declaraciones de variables son levantados fuera de los condicionales.

Las delaraciones de función no lo son.

La fijación de bloque con el ámbito with

Para restaurar el alcance del bloque en JavaScript, trate de usar with con objetos explícitos, por ejemplo:

{ 
   var i = 13 ;
   with ({i: 42}) {
     console.log(i) ; // prints 42
   }
   console.log(i) ; // prints 13
}
Debido a que el objeto se declara explícitamente, que no intefere con el análisis estático del código, y es igualmente claro para el razonamiento humano.

Este es el único uso justificado de with.

Dave Herman de Mozilla aconseja que, la forma correcta de manejar esta situación es una función de aplicación inmediata anónima:

 {
    var i = 13 ;
    (function () { 
       var i = 42 ;
       console.log(i) ;  // prints 42
    })() ; 
    console.log(i) ; // prints 13
 }
Para la programació funcional está de acuerdo con Dave, pero debería ser más estético.

¿Qué significa this?

El significado de this depende de cómo la función actual fue declarada:

  • Directamente: f(...);
  • Indirectamente: f.call(this,...) o f.apply(this,array);
  • Como un metodo: f.call(this,...) o f.apply(this,array);
  • Como un constructor: new f(...).
Llamada directamente.

Llama directamente, this se enlaza con el objeto de ventana de nivel superior.

Dado que las variables globales son en realidad campos de este objeto, esto modifica el namespace global:

 function f () {
   this.x = 3 ;
 }
 f() ;
 alert(x) ; // alert(3)
Pero, ¿qué pasa con nodejs, donde no hay objeto de la ventana?

Ejecute este código como un experimento:

function f() {
  this.x = 10 ;
  console.log(this) ;
  console.log(this.x) ;
}

f() ;

console.log(x) ;
Esto imprime:

{}
10
10
Claramente, por defecto this que en nodejs no es un objeto vacío ordinario.

Como era de esperar, este objeto conserva sus poderes, incluso si se las devuelve:

function global() {
  return this ;
}

(global()).y = 20 ;

console.log(y) ; // prints 20
Llamado indirectamente

El comportamiento más extraño (y menudo pasado por alto) con respecto a esto viene de llamar a una función directa, y tratar de definir this con la fuerza f.call y f.apply.

Si un objeto se presenta como el primer argumento, entonces el objeto se convierte en this.

Pero, si un átomo como un número, un booleano o una cadena se pasa, this no está ligado a ese valor.

En cambio, this se une a un "átomo objetivado" - un objeto que se comporta como el tipo de átomo.

Pruebe esto en nodejs o Firebug:

function f () { return this ; }

 var myTrue = f.call(true) ;
 console.log(myTrue) ;             // prints {}
 console.log(myTrue.constructor) ; // prints [Function: Boolean] 
 console.log(typeof myTrue) ;      // prints "object"

 var myBar = f.call('bar') ;
 console.log(myBar) ;             // prints {'0': 'b','1': 'a','2': 'r'}
 console.log(myBar.constructor) ; // prints [Function: String]
 console.log(myBar.toString()) ;  // prints bar
 console.log(typeof myBar) ;      // prints "object"

 var myThree = f.call(3) ;
 console.log(myThree) ;             // prints {}
 console.log(myThree.constructor) ; // prints [Function: Number]
 console.log(myThree.valueOf()) ;   // prints 3
 console.log(typeof myThree) ;      // prints "object"
Fascinado, ¿eh?

Llamado como un método

Cuando se invoca como un método - o.f () - una función recibe el objeto o como this.

Hay dos situaciones en las que los métodos conducen a problemas: funciones parcializadas o anidadas y los métodos de primera clase.

Es fácil olvidar que cuando se anidan las funciones, la función interna recibe su propio this, aun cuando this no tenga sentido.

 o.a = 3 ;
 o.b = 4 ;
 o.c = 5 ;

 o.generateValidator = function () {
   return function () {
     if (this.a*this.a + this.b*this.b != this.c*this.c)
       throw Error("invalid right triangle") ;
   } ;
 }
La forma de evitar este problema de alcance es declarar that:

 o.a = 3 ;
 o.b = 4 ;
 o.c = 5 ;

 o.generateValidator = function () {
   var that = this ;
   return function () {
     if (that.a*that.a + that.b*that.b != that.c*that.c)
       throw Error("invalid right triangle") ;
   } ;
 }
Alguna vez me picó user accidentalmente un método en una definición de primera clase:


 engine.setTickHandler(ship.ontick) ;
ship.ontick es un método, pero una vez que se invoca, este no estará obligado a enviarlo.

Con toda probabilidad, quedará vinculado a global().

Mi solución a este problema se inspira en la noción de η-expansión del cálculo lambda:

function eta (that,methodName) {
  var f = that[methodName] ;
  return function () {
    return f.apply(that,arguments) ;
  }
}
Entonces, en lugar de escribir object.methodName para pasar un método como una función de primera clase, se usa(objeto, 'methodName').

Llamado como un constructor

Cuando una función se llama como un constructor, el valor de this es el objeto recién creado.

La omisión de new por accidente destroza el namespace global.

Si las variables globales están mutando sin explicación, trate de guardar constructores with:

 this == global() && error() ;
Fijación de los argumentos

La capacidad de aceptar un número arbitrario de argumentos en JavaScript es con frecuencia útil.

En JavaScript, los argumentos que se pasan a una función están implícitamente ligados a los argumentos de variable.

Este objeto se parece y actúa sobre todo como una matriz, pero es sólo un objeto que pasa a tener índices numéricos, además de un campo llamado longitud.

La mayoría de los programadores no descubren esto hasta que los muerde.

Por ejemplo, con:

function f() {
  return arguments;
}
Una llamada a f(1,2,3), retorna:

{ '0': 1,
  '1': 2,
  '2': 3  }
Mejor que [ 1, 2, 3 ].

Los métodos habituales-como indexOf - están desaparecidos.

Hay un par de maneras para promover argumentos para una matriz real. El método adoptado por muchos frameworks de JavaScript es utilizar el método de corte:

 function f() {
   arguments = Array.prototype.slice.call(arguments) ;
   return arguments ;
 }
En implementaciones no IE de JavaScript, es posible volver a asignar directamente al objeto prototipo para el prototipo de las matrices:

 function f() {
   arguments.__proto__ = Array.prototype ;
   return arguments ;
 }

Evitando truthiness
Hay poco de verdad a la verdad en JavaScript.

Muchos valores se califican como falso en un condicional.


               false0undefinednullNaN; y ''.

A primera vista, parece que == entiende esto, teniendo en cuenta que:

 0 == false

los rendimientos son reales.

Sin embargo, null == false y'' == false son falsas.

Los operadores == y != Intento de coacción operandos de diferentes tipos.

Por ejemplo, "\t\t '== false, sin embargo, '\t\t' es verdadero en un condicional.

En teoría, es mejor utilizar === y !==, Que no trate de coerción.

Sin embargo, todavía hay un valor x tal que x != x and x !== X.

Ese valor es NaN.

Si las cuestiones de igualdad, es necesario utilizar una función auxiliar:

function equal(a,b) {
  if (a === b)
    return true ;
  if (isNaN(a) && isNaN(b))
    return true ;
  return false
}
Traducción de la pagina: http://matt.might.net/articles/javascript-warts/



No hay comentarios:

Publicar un comentario