jueves, 29 de agosto de 2013

JavaScript: El operador 'delete'

El operador delete es un incomprendido. No sirve para borrar variables y muchas veces tratamos de utilizarlo incorrectamente. Hay dos claves muy importantes:

  • delete sirve para borrar propiedades de un objeto
  • No puedes borrar con delete ninguna variable declarada con var

La forma de usarlo es:

delete object.property

Por ejemplo:


var myObj = {
    uno: 1,
    dos: 2
};
delete myObj.uno;
console.log( myObj.uno ); // undefined

La propiedad borrada deja de existir, ya no aparecerá en las iteraciones.

delete sólo funciona sobre propiedades de objetos. No hace nada aplicado sobre variables o funciones:

var test = "no puedes borrarme";
delete test;
console.log( test ); // "no puedes borrarme"

Nota: eval(), utilizado por firebug y otras consolas de navegador, modifica la forma en la que se crean las variables, afectando al funcionamiento de delete. Estos ejemplos no pueden probarse correctamente en la consola. Es necesario crear un script para evaluarlos en una página real cargada en el navegador.

Las variables globales declaradas sin var sí pueden borrarse. Sería equivalente crearlas como una propiedad del objeto global window:


test = "Sí puedes borrarme, soy window.test";
console.log(test);           //"Sí puedes borrarme, soy window.test"
console.log(window.test);    //"Sí puedes borrarme, soy window.test"
delete test;
console.log(test);           // ReferenceError: test is not defined

Como vimos antes, esta misma variable global declarada con var NO puede borrarse.

Delete devuelve un boolean

El operador devuelve true si la propiedad referenciada ya no existe y false si aún existe. Es muy importante tener en cuenta que el valor devuelto no indica exactamente si la operación ha tenido éxito o no, sino si la propiedad referenciada existe ahora o no. Si intentamos borrar una propiedad que no existía obtenemos true.


var test1 = "no puedes borrarme";
test2 = "sí puedes borrarme";
var myObj = {
    uno: 1,
    dos: 2
};    

delete test1;        // false    
delete test2;        // true
delete test3;        // true!!. test3 no existía    
delete myObj.uno;    // true
delete myObj.cuatro; // true!!. 'myObj.cuatro' no existía

Los argumentos de una función no se borran

Los argumentos de una función son equivalentes a variables declaradas dentro de la función mediante var, por lo tanto, no podemos borrarlos:

function suma (arg1, arg2) {
    delete arg1; //false. No hace nada
    //...
}  

Hay propiedades 'especiales' que no pueden borrarse

La mayoría de las propiedades de los objetos predefinidos de JavaScript, como Array, Object, Math, etc, están marcadas con un flag especial que impide borrarlas (el atributo DontDelete). Esto impide, por ejemplo, borrar .length en un array:

delete [].length; // false
delete Math.PI;   // false

Propiedades heredadas del prototipo

No podemos borrar directamente una propiedad heredada. Tenemos que borrarla desde su objeto original (el prototipo):


function MyConstructor(){}
MyConstructor.prototype.test = "Hello";
var myObject = new MyConstructor();
delete myObject.test;                 // no hace nada
console.log(myObject.test);           // "Hello"
delete MyConstructor.prototype.test;  // borra la propiedad en el prototipo
console.log(myObject.test);           // "undefined"

Comportamiento en ES5

ECMAScript 5th edition introduce algunas novedades. Básicamente nuevos tipos de error para notificar el uso inapropiado:

  • Intentar borrar una variable, un argumento de función o una función provoca un SyntaxError
  • Borrar una variable inexistente provoca un SyntaxError

(function (arg1) {

    "use strict"; // activar modo estricto (ES5) 

    var test;
    function sum(){}

    delete arg1;  // SyntaxError (when deleting argument)
    delete test;  // SyntaxError (when deleting variable)
    delete sum;   // SyntaxError (when deleting variable created with function declaration)
    delete i_dont_exist; // SyntaxError

  })();

Fuentes:
Perfection Kills: Understanding delete
You Can’t Delete With Delete
StackOverflow: deleting objects in javascript
MDN: delete

lunes, 26 de agosto de 2013

JavaScript: diferencia entre expresión y sentencia ( expression and statement)

En JavaScript es importante conocer esta sutil diferencia porque es la causa de algunos comportamientos "especiales".

-Una expresión siempre devuelve un valor como resultado
-Una sentencia realiza alguna acción

Ejemplos clásicos de sentencias son las instrucciones 'if', 'for', 'switch' o 'while':


if ( limit > 3) {
    limit = 0;
}


for ( var i=0; i<10; i++ ) {
    // do something here
}

Todos los siguientes ejemplos son expresiones, todos devuelven un valor:


a = 3      // es una asignación pero también devuelve el valor 3
3 + 2      // devuelve 5
myCounter  // devuelve el valor de la variable 'myCounter'
"abc"      // devuelve el string "abc"
sum(a,b)   // devuelve la suma de a y b
(limit > 3) ? 0 : 1;   // devuelve 0 ó 1, según condición 

Cuando creamos un objeto con la notación literal, estamos realmente utilizando una expresión que devuelve un objeto:

{ color: "red" }    // esto es una expresión que devuelve un objeto
[1, 2, 3]           // expresión que devuelve un array  
"abc"               // expresión que devuelve un string
function () { }     // function anónima. Expresión que devuelve una función

En JavaScript podemos utilizar una expresión en cualquier punto donde se espere un valor, por ejemplo como argumento de una función o en un switch:

//como argumento
myFunction( a+3, b);

//en un switch
switch (expression) {
  case label1:
    //..
    break
  case label2:
    //..
    break;
  default:
    //..
}

JavaScript acepta también una expresión en cualquier punto donde se espera una sentencia. Lo contrario no es cierto, no podemos escribir una sentencia donde se espera una expresión. Por ejemplo, no podemos poner un 'if' como argumento de una función.

declaración de función vs expresión de función

Una función puede crearse mediate una sentencia (declaración) o mediante una expresión. Lo siguiente es una declaración de función:

function suma (a, b) {
  return a+b;
}

Como sentencia, realiza una acción, crea una variable suma que contiene la función.

Una expresión devolverá un valor (una función) que podemos asignar a una variable:

var suma = function ( a, b ) {
               return a+b;
            }

También podemos tener una expresión que crea una función con nombre (named function expression):

var sumVar = function suma ( a, b ) {
               return a+b;
            };

En este caso la parte a la derecha de la función es exactamente igual que una declaración de función, pero no es una declaración, es una expresión. ¿Como sabemos entonces si es una declaración (sentencia) o una expresión? por el contexto en el que aparece.

Cuando el parser de JavaScript encuentra la palabra reservada function al principio de una instrucción, lo toma como una declaración de función. Cuando la encuentra formando parte de una sentencia (o de una expresión actuando como sentencia), lo considera una expresión (por ejemplo en una asignación).

Es importante el concepto de contexto de expresión (expression context) y de contexto de sentencia (statement context). Debemos tenerlo en cuenta siempre que un mismo código pueda actuar como expresión y como sentencia y debemos saber como forzar el cambio de interpretación si lo necesitamos.

Un ejemplo de cambio de contexto son las funciones autoejecutables. JavaScript no permite ejecutar una declaración de función, por lo que no podemos hacer esto:

//ejemplo de error. No se puede ejecutar
function suma (a, b) {
  return a+b;
}();

Sólo una expresión de función puede ejecutarse. Para que el intérprete considere la función como una expresión tenemos que colocarla entre paréntesis. De esta forma la palabra function no se encuentra al principio y el motor de JS lo considera como una expresión a evaluar.

(function ( a, b ) {
    return a+b;
})();

Nota: '(...)' es el operador de agrupado y sólo puede contener una expresión. Por lo tanto JavaScript siempre esperará una expresión entre los paréntesis. Si intentamos colocar una sentencia dentro de los parentesis obtendremos un syntax error.

Otro caso parecido, en el que el contexto decide como se interpreta un código idéntico, es la notación literal para crear un objeto:

{ 
  color: "red" 
}

Este código puede interpretarse de dos maneras:

  1. una expresión que crea un objeto
  2. Un bloque de código ( '{ }' ) que contiene una etiqueta ('color:') y una expresión que actúa como sentencia ("red")

Igual que en el caso de la función, si el parser encuentra '{' en el inicio, sin formar parte de una sentencia o otra expresión, lo considera como el inicio de un bloque y no como una expresión; no creará el objeto. Ademas, si tenemos varias propiedades provocará un syntax error: Unexpected token:

//Syntax error
{ 
  color: "red",
  size: 14 
}

Si lo encerramos entre paréntesis o lo asignamos a una variable, el intérprete lo considera una expresión y crea el objeto. Esta es la razón por la que tenemos que añadir paréntesis a un string JSON si queremos evaluarlo con eval():

eval ("(" + myJsonString + ")" );

Fuentes:
Expressions versus statements in JavaScript
Named function expressions demystified

miércoles, 14 de agosto de 2013

Transformar saltos de línea en <br> en un textarea

Este es un problema que aparece cada vez que presentamos un área de texto en una interfaz de usuario. El usuario pulsa enter para crear líneas nuevas:

Pero cuando nosotros queremos mostrar este texto en HTML, lo que obtenemos es

El problema es que un salto de línea no se interpreta como tal en HTML. Hay varias soluciones para mostrar el texto correctamente.

Transformando saltos de línea en <br>

Lo que necesitamos es transformar los saltos de línea (invisibles en HTML) en la etiqueta <br>.

Tenemos un textarea de este tipo:


El texto que esbribe el usuario lo podemos obtener de esta forma:

//con jQuery    
var value = $('#userText').val();

//sin jQuery
var value = document.getElementById('userText').value;

Nota: No debe utilizarse .html() (de jQuery) o .innerHTML para obtener el valor de un área de texto. Devuelve el valor inicial del textarea. Cuando se escribe algo nuevo su valor será incorrecto.

El texto que obtenemos en la variable value es:

"Línea1\nLínea2\nLínea3"

Utilizamos el símbolo \n para indicar el carácter new line. Es el que se utiliza en las expresiones regulares. Es una forma sencilla para el desarrollador de introducir este carácter (ASCII: 10) en un string.

Ahora que sabemos como referenciar el carácter de nueva línea y la etiqueta que necesitamos en HTML, hacer la sustitución es sencillo:

value = value.replace(/\n/g, "<br>");

Utilizamos una expresión regular ( ver tutorial ) para sustituir cada salto de línea introducido con enter por "<br>".

En realidad, dependiendo de la plataforma y del navegador, la representación del salto de línea puede ser \n o \r\n

\r -> carriage return (retorno de carro)
\n -> Line Feed (nueva línea)

Para cubrir los dos casos la expresión regular más completa es:

value = value.replace(/\r?\n/g, "<br>");

Utilizando la etiqueta <pre>

Otra solución es no transformar el texto y mostrarlo siempre dentro de etiquetas <pre></pre>. Estas etiquetas indican texto preformateado y el navegador respeta el formato, incluyendo saltos de línea y espacios en blanco.

El HTML para mostrar el texto sería:

<pre>
Línea1
Línea2
Línea3    
</pre>    

En JavaScript podríamos mostrar el valor de la variable value sin transformarla:

var value = $('#userText').val();
$("#placeToShowText").html("<pre>" + value + "</pre>");