Introducción
El comando with es uno de los más discutidos del lenguaje Javascript. Para la mayoría de desarrolladores, su uso se considera una mala práctica; sin embargo, para aquellos que vienen de otros lenguajes, resulta una estructura natural con la que se sienten cómodos y de ahí que nos la encontremos frecuentemente en los repositorios públicos.
Veamos cómo funciona esta función y algunos casos de uso frecuentes.
La función with
La sentencia with establece el objeto por defecto [default] para un conjunto de sentencias. JavaScript busca por cualesquiera de los nombres no calificados dentro de un conjunto de sentencias para determinar si los nombres son propiedades del objeto por defecto.
Una sentencia with se parece a lo siguiente:
with (objecto) { sentencias } |
Ejemplo:
La siguiente sentencia with especifica que el objeto Math es el objeto por defecto. Las sentencias seguidas de la sentencia with se refieren a la propiedad PI y los métodos cos y sin, sin especificar un objeto. JavaScript asume el objeto Math para estas referencias.
var a, x, y; var r = 10; with (Math) { a = PI * r * r; x = r * cos(PI); y = r * sin(PI/2); } |
Otro escenario en el que puede resultar útil es cuando tratamos por ejemplo de asignar una serie de atributos a un conjunto de elementos DOM preseleccionado:
with(document.getElementById('el').style) { backgroundColor = '#000'; color = '#fff'; width = '200px'; padding = '20px'; } |
NOTA: El código anterior representa una estructura similar a la que utiliza jQuery para dar estilo a los elementos:
$("#el").css({ backgroundColor : '#000', color : '#fff', width : '200px', padding : '20px', }); |
La controversia
Principalmente, existen tres razones por las que los programadores Javascript más experimentados evitan usar with: posible comportamientos inesperados, rendimiento y seguridad.
Comportamiento no deseado
Ya que with no permite añadir nuevas propiedades a un objeto, si intentamos hacerlo el resultado será la creación de nuevas variables globales independientes:
with(document.getElementById('el').style) { colors = '#fff'; //oops we mispelled this property } |
En el ejemplo anterior, como la propiedad colors no existe dentro del objeto, se crea una nueva variable global que nada tiene que ver con el mismo. Este tipo de errores difíciles de rastrear, pueden provocar la inestabilidad de un sistema.
Rendimiento
Cuando se trata de iterar por objetos grandes, la penalización de esta función es importante.
Tomemos el siguiente ejemplo:
function foo(arg) { with(arg) { console.log("arg: "+arg) } } |
El principal inconveniente aquí es que no podemos optimizar el acceso a arg porque no sabemos si se referirá a una variable real o a una propiedad del objeto a recorrer. De hecho, esto podría variar en cada una de las llamadas.
Un test de rendimiento simple, nos demuestra la dificultad que el intérprete tiene para operar con esta función:
var obj = { hello: "world" }, value; function without_with () { for ( var i = 0; i < 9999; i++ ) { value = obj.hello; } } function with_with() { with(obj) { for ( var i = 0; i < 9999; i++ ) { value = hello; } } } |
Los resultados son claros:
NOTA: Datos obtenidos por @roflwtfbbq
Función | Llamadas | % | Tiempo | Avg |
---|---|---|---|---|
with_with | 10 | 91.08% | 96.549ms | 9.655ms |
without_with | 10 | 8.92% | 9.453ms | 0.945ms |
Seguridad
Este es el motivo principal por el que Brendan Eich ha desaconsejado su uso y el porqué ha sido despreciado para futuras versiones del estándar ECMAScript: igual que en el caso anterior, no podemos determinar a qué está refiriendo el identificador de la función echando un vistazo al código ya que se trata de un entorno léxico.
Douglas Crockford también se ha pronunciado al respecto argumentando que: «fue un comando bien intencionado, pero el lenguaje sería mejor si no lo tuviéramos«.
Alternativas
El uso de with además de desaconsejado, no está permitido bajo el modo estricto:
function foo() { "use strict"; with({}); } // SyntaxError: strict mode code may not contain 'with' statements |
La mejor forma de evitar su uso es creando una variable intermedia temporal para acceder a las propiedades de un objeto:
var b = foo.bar.baz; console.log( "Hello " +b.first + " " + b.last ); |
Si no queremos que dicha variable sea accesible desde el resto del código, es decir, que sea pública, podemos restringirla encerrándola dentro de una función autoejecutable a la que pasamos como argumento el objeto a recorrer:
(function(b) { console.log( "Hello " +b.first + " " +b.last ); }(foo.bar.baz)); |
Con esto, no hay ambigüedad posible en cuanto a qué tipo de valor hace referencia b. Eliminamos así tanto los comportamientos inesperados como los riesgos de seguridad.
Conclusión
La función with en Javascript, a diferencia de su uso en otros lenguajes, está desaconsejada por motivos de seguridad y rendimiento. Actualmente ya no forma parte del borrador para la próxima especificación y su uso está prohibido bajo el modo estricto del ES5.
Reemplazar su funcionalidad es tan sencillo como el asignar una variable intermedia para que sea ésta la que identifique al objeto que se prentende recorrer, eliminado así toda posible ambigüedad en sus términos y mejorando el rendimiento general del sistema.
Más Información
Mozilla MDN, «Sentencias de manipulación de objetos»
Yahoo UI Blog, «with Statement Considered Harmful»
Axel Rauschmayer, «JavaScript’s with statement and why it’s deprecated«
No estoy seguro de que todo sea negativo respecto al uso de «with».
También tendrá su lado bueno. Para muestra, este post en el blog de Andrea Giammarchi:
http://webreflection.blogspot.com/2009/12/with-some-good-example.html
Lo que no me parece nada bien es que, al usar javascript estricto, no se pueda usar esta sentencia.
Es como prohibir los destornilladores porque una vez alguien se lo metió en un ojo y lo perdió. El ojo, claro.
El rendimiento imagino que depende del motor de javascript.
Y de las ganas que hayan tenido de optimizarlo: Al tenerle tan poco cariño, no se habrán molestado mucho en esta parte, supongo.
Un saludo!
Estoy de acuerdo en que usar with como ‘shortcut’ para acceder a determinadas variables escribiendo código es como matar moscas a cañonazos. Pero ese no es el uso ‘real’ de with. En el enlace que han puesto más arriba de webreflection se puede ver que sirve para muchas otras cosas. Mi ejemplo particular es http://jsfiddle.net/vgjZ5/ (que no se puede realizar de ninguna otra manera sin usar with).
¡Gracias por el artículo!
La verdad es que no suelo trabajar con with, y ahora que he leído esto la erradicaré completamente de mis códigos.
Un saludo.