miércoles, 25 de septiembre de 2013

Política del mismo origen (Same Origin Policy)

La política del mismo origen es una importante regla de seguridad que implementan todos los navegadores modernos. Su propósito es impedir que un script procedente de un sitio externo pueda acceder al DOM, datos y cookies de otra página y que pueda utilizar AJAX para realizar peticiones utilizando las cookies y credenciales que el usuario tiene activas (bancos, tiendas, correo, etc).

En otras palabras, se trata de que el navegador mantenga una separación estricta entre diferente páginas/aplicaciones. De esta forma una página cargada en un iframe o en otra pestaña del navegador no puede acceder a tu sesión de Amazon o del banco que tienes abierta en otra pestaña. También se descartan las peticiones AJAX a un origen diferente.

¿Que se considera un origen distinto?

En principio imaginamos un origen distinto como un dominio diferente. Esto es correcto, pero la definición es mucho más amplia. El origen también se considera diferente si cambia el protocolo (de http a https, por ejemplo) o el puerto desde el que se sirven los recursos:

En la siguiente tabla (de wikipedia.com) podemos ver con que orígenes se permite la comunicación si nuestra página original viene desde "http://www.example.com/dir/page.html":

URL Resultado Explicación
http://www.example.com/dir/page2.html Permitido Mismo protocolo y servidor
http://www.example.com/dir2/other.html Permitido Mismo protocolo y servidor
http://username:password@www.example.com/dir2/other.html Permitido Mismo protocolo y servidor
http://www.example.com:81/dir/other.html No Permitido Mismo protocolo y servidor pero diferente puerto
https://www.example.com/dir/other.html No Permitido Diferente protocolo
http://en.example.com/dir/other.html No Permitido Diferente servidor
http://example.com/dir/other.html No Permitido Diferente servidor
http://v2.www.example.com/dir/other.html No Permitido Diferente servidor
http://www.example.com:80/dir/other.html Evitar Puerto por defecto indicado. Depende de la implementación del navegador

¿De qué me protege la Política del mismo origen?

El objetivo es proteger al usuario que está viendo una página en su ordenador, con su navegador, de scripts maliciosos que intenten acceder a sus datos o a otros servidores utilizando sus cookies/credenciales. Si los navegadores no implementaran la SOP (Same Origin Policy) podría darse habitualmente el siguiente problema:

  • Inocencio entra en la página de su banco "misEurillos.com".
  • Inocencio abre otra pestaña para entrar en una tienda online a comprar unos caramelos para la tos: "tengoDeTodo.com".
  • La página de la tienda "tengoDeTodo.com" tiene código JavaScript para intentar robar datos de todos los clientes que entran. Mediante AJAX, hace peticiones a varios bancos conocidos (incluido misEurillos.com).
  • El banco responde con los datos de Inocencio porque la petición AJAX incluye automáticamente las cookies/credenciales que Inocencio tiene aún activas por tener sesión abierta en su banco.
  • El script de la página "tengoDeTodo.com" recibe los datos del banco y se los envía a Malone para realizar sus fechorías.

La política del mismo origen impide a los scripts externos acceder a información de la página y hacer peticiones AJAX a servidores diferentes. En este caso el script no podría enviar las peticiones AJAX a los bancos (son origenes diferentes a "tengoDeTodo.com") ni tendría acceso a las cookies o datos de las otras páginas que Inocencio está viendo.

Lo mismo es válido para el correo: si tenemos una sesión de correo abierta en una pestaña, otra página podría intentar, con sus scripts, acceder a datos de la nuestra o acceder directamente al servidor de correo para obtener nuestra información. Las cookies se envia con la petición AJAX a www.miCorreo.com, por lo que quedaremos identificados como el usuario original.

No todas las interacciones cross-site están prohibidas

Pedir recursos de un dominio diferente es algo habitual en las aplicaciones modernas y no siempre entraña peligro. Muchas páginas web cargan imágenes o contenido multimedia desde un origen diferente. Las restricciones se aplican principalmente a las peticiones que se hacen dinámicamente desde un script ( AJAX mediante XMLHttpRequest ) y a la interacción entre diferentes páginas cargadas en el navegador.

No se plantean las mismas restricciones para todos los casos de comunicación. Un script que se carga mediante la etiqueta <script> tiene acceso sin límites a toda nuestra aplicación. Una página de un dominio diferente que se pide en una etiqueta <iframe> se mostrará sin problemas, pero se limita su acceso al contenido de la página padre o de otros iframes.

En general no se limita la inclusión de contenido embebido mediante etiquetas HTML. Podemos incluir recursos cross-site de diferente tipo:

  • Código javascript con <script src="..."></script>
  • CSS con <link rel="stylesheet" href="...">
  • Imágenes con <img>
  • ficheros multimedia con <video> o <audio>
  • plug-ins con <object>, <embed> o <applet>
  • fonts con @font-face
  • cualquier contenido con <iframe>

La diferencia principal con las peticiones AJAX es que ninguna de las peticiones de la lista anterior permite leer directamente la respuesta. Si estamos en la página del banco, con sesión abierta, y enviamos una petición AJAX, recibiremos los datos bancarios (probablemente en JSON o XML). El script del banco recibe la respuesta y puede leerla. Las respuestas de las peticiones anteriores las interpreta directamente el navegador, no tenemos acceso a ellas ( hay alguna forma de conseguirlo como veremos a continuación, pero tiene sus limitaciones).

¿Por qué <SCRIPT> sí y AJAX no?

El hecho de que se pueda incluir código mediante la etiqueta <script> sin ninguna restricción resulta llamativo. Podemos cargar sin ningún problema las librerias que necesitamos desde un dominio diferente al nuestro, por ejemplo:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

La línea anterior cargará jQuery desde los servidores de Google, sin ninguna limitación. Podemos utilizar los métodos de jQuery para acceder al DOM y a nuestros datos.

El código que se incluye de esta manera queda embebido en nuestra página, el origen de estos ficheros JavaScript queda definido por el origen de la página HTML que los incluye.

Hay algunas diferencias importantes entre una petición con <script> y otra utilizando XMLHttpRequest que hacen la segunda mucho más insegura:

  • La etiqueta <script> no permite leer la información que llega, todo el código que se incluye se ejecuta inmediatamente en el contexto global ( con AJAX la respuesta se recibe como un string y es la aplicación la 'lee' el contenido y decide que hacer con la información).
  • Sólamente se pueden enviar peticiónes HTTP GET, no se puede elegir POST, PUT, DELETE, HEAD, etc.
  • No se pueden modificar las cabeceras que se envian (no se pueden incluir credenciales de seguridad HTTP AUTH).
  • No se puede acceder a la respuesta: cabeceras, código, información, etc. (con AJAX se puede acceder a las cabeceras)

<script> se utiliza a veces para conseguir una comunicación cross-site similar a AJAX, inyectandola mediante javascript en el documento cargado. Esto se conoce como JSONP. De esta manera sí se puede leer información enviada en la respuesta, pero hay una puntualización importante: la implementación de JSONP requiere que tanto el cliente como el servidor utilizen un protocolo especial. Es decir, sólo se pueden leer datos de un servidor que los exponga mediante JSONP, aceptando de esta manera el acceso público a ese recurso desde cualquier origen.

JSONP requiere que tanto el cliente como el servidor utilizen un protocolo especial. Es decir, sólo se pueden leer datos de un servidor que los exponga mediante JSONP, aceptando de esta manera el acceso público a ese recurso desde cualquier origen.

Aún así es importante que sólo se realicen peticiones a servidores propios o sobre los que exista una total confianza, porque, como comentamos antes, el script se ejecuta inmediatamente en el contexto global de la aplicación. La respuesta puede devolver una llamada a una función, tal como se espera, y despues otro código malicioso que se ejecutará también ciegamente.

Fuentes:
W3C - Same-Origin Policy
Política del mismo origen, Cross-Site Scripting (web)
MDN: Same-origin policy
IT Security: Why is the same origin policy so important?
Principles of the Same-Origin Policy draft-abarth-principles-of-origin-00
stackoverflow: Why are cross domain ajax requests labelled as a security risk
StackOverflow: Why do browser APIs restrict cross domain requests
StackOverflow: Why the cross domain ajax is a security concern

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>");

jueves, 25 de julio de 2013

HTTP: diferencia entre POST y PUT

El protocolo HTTP define una serie de peticiones posibles entre cliente y servidor ( GET, POST, PUT, DELETE, HEAD, etc ). POST y PUT son muy similares y hay bastante confusión respecto a su uso y sus diferencias. Las dos pueden utilizarse para crear y para actualizar un recurso, pero en situaciones diferentes.

Hasta hace unos años se utilizaba casi exclusivamente GET y POST. La interacción con el servidor era básicamente por medio de formularios HTML, que no permiten otros métodos (ni siquiera en HTML5).

En las aplicaciones actuales, la mayor parte de la comunicación con el servidor se realiza mediante AJAX, que sí permite el uso de otras peticiones HTTP. Además, la popularización de los interfaces REST implica el uso de los métodos GET, POST, PUT y DELETE para interactuar con el servidor.

En algunas páginas sobre REST encontramos la siguiente definición de los métodos (ejemplo):

  • GET para obtener un recurso del servidor
  • POST para crear un recurso del servidor
  • PUT para actualizar un recurso del servidor
  • DELETE para eliminar un recurso del servidor

Y en algunas otras encontraremos exactamente lo contrario (ejemplo):

  • GET para obtener un recurso del servidor del servidor
  • POST para actualizar un recurso del servidor
  • PUT para crear un recurso del servidor
  • DELETE para eliminar un recurso del servidor

En realidad estas definiciones son incorrectas. La diferencia no es que uno se utilice para crear y otro para actualizar. Los dos métodos pueden crear un recurso y los dos métodos pueden actualizarlo.

La diferencia no es que uno se utilice para crear y otro para actualizar, esto es incorrecto

PUT se utiliza para poner un recurso en un lugar especificado (la URL). POST es mucho más general. POST simplemente envía una información al servidor para que éste la trate como considere oportuno. Puede crear un recurso nuevo, devolver una información, puede guardarlo en la base de datos, borrarlo, duplicarlo o modificarlo, puede crear 10 recursos de un tipo y dos de otro o puede no hacer absolutamente nada.

  • PUT pone un recurso en la dirección especificada en la URL. Exactamente en esa dirección. Si no existe, lo crea, si existe lo reemplaza.
  • POST envía datos a una URL para que el recurso en esa URI los maneje.

La diferencia principal, tal como se explica en la RFC 2016 que define el protocolo HTTP 1.1, es el significado que se da a la URL de la petición:

"La diferencia fundamental entre las peticiones POST y PUT se refleja en el diferente significado de la URI de la petición. La URI en un POST identifica el recurso que manejará la entidad que se incluye (los datos). [...]. En cambio, la URI en un PUT identifica la entidad incluida con la petición -- el user agent sabe que URI debe emplearse y el servidor NO DEBE intentar aplicar la petición a un recurso diferente."

Es decir, PUT sólo debe emplearse para crear o reemplazar un recurso (que se incluye en el cuerpo de la petición) en una URL conocida. En terminos REST, conocemos la ID del objeto que vamos a crear/reemplazar y ese objeto (entidad) se incluye en el cuerpo de la petición. Por ejemplo:

PUT a la URL: myServer.com/user/1134

Crea o reemplaza el usuario con ID 1134. Exclusivamente ese. Si queremos crear un usuario nuevo, sin saber su ID, la operación correcta sería POST:

POST a la URL: myServer.com/user

El servidor crearía un nuevo objeto user y le asignaría una ID. Crearía, por ejemplo, el objeto:

myServer.com/user/1235

Esto nos lleva a otro concepto importante: idempotencia. PUT es idempotente, POST no lo es.

Idempotente significa que produce el mismo resultado sin importar cuantas veces se realice la operación.

En el ejemplo que veíamos antes, con el método PUT:

PUT a la URL: myServer.com/user/1134

Estamos definiendo una operación idempotente sobre un recurso concreto. No importa cuantas veces la lancemos, el resultado será siempre el mismo: el recurso identificado como "myServer.com/user/1134" existirá con los datos que se han pasado en la petición. Si no existía se creará. Si ya existía se reemplazará de nuevo. No afecta a nada más.

La operación POST que veíamos antes no es idempotente. Cada vez que se lance la petición al servidor creará un recurso nuevo, asignandole una nueva ID.

POST a la URL: myServer.com/user

Despues de 4 peticiones tendriamos los recursos:

  • myServer.com/user/1235
  • myServer.com/user/1236
  • myServer.com/user/1237
  • myServer.com/user/1238

Resumen

Estos son los puntos más importantes que debemos tener en cuenta:

  • Una petición PUT a una URL afecta únicamente a esa URL. Un POST a una URL puede tener cualquier efecto.
  • En una petición PUT, los datos incluidos en el cuerpo de la petición se toman como una entidad que quedará accesible en la dirección URL de la petición.
  • Solo debemos utilizar PUT cuando sabemos exactamente la url correspondiente al recurso que vamos a crear (o actualizar).
  • PUT es idempotente, POST no es idempotente.

Fuentes:
StackOverflow: PUT vs POST in REST
PUT or POST: The REST of the Story
POST vs. PUT
StackOverflow: What's the difference between a POST and a PUT HTTP REQUEST?

lunes, 22 de julio de 2013

Doble negación binaria (~~) para redondear en JavaScript

Alguna vez te puedes encontrar en JavaScript con una línea como esta:

var mins = ~~(seconds / 60);

¿Que significa el doble símbolo "~~"?¿que hace?

El operador ~

~ es un operador binario de negación o complemento (bitwise NOT operator). Este operador convierte el operando en un entero de 32 bits para luego invertir cada bit individualmente. Los ceros se convierten en unos y los unos en ceros.

El operador doble (~~) se utiliza para redondear, como un equivalente rápido de Math.floor(). Al invertir los bits dos veces quedan igual que antes, pero la conversión a entero permanece (utiliza el método interno ToInt32).

~~5.7;       // => 5 
~~32.18897;  // => 32
~~5.7e1;     // => 57  (podemos utilizar notación exponencial)
~~314e-2;    // => 3

Lo que hace realmente la doble negación binaria es eliminar cualquier número despues de la coma (truncar, más que redondear). Para los número positivos esto es equivalente a Math.floor(), pero para los números negativos no. Para los negativos es equivalente a Math.ceil(), ya que redondea hacia cero:

~~-5.7;                // => -5
Math.floor(-5.7)       // => -6 
Math.ceil(-5.7)        // => -5
~~-32.18897;           // => -32
Math.floor(-32.18897)  // => -33
Math.ceil(-32.18897)   // => -32

Mas diferencias con Math.floor()

Ademas de que el redondeo de números negativos no es igual, si el operando no es convertible a número, no nos va a devolver NAN, sino 0.

~~"abc";     // => 0 
~~null;      // => 0
~~undefined; // => 0
~~{};        // => 0
~~[];        // => 0
~~(1/0);     // => 0
~~false;     // => 0
~~true;      // => 1 //true es convertible a 1

La ganancia de rendimiento con los nuevos motores de JavaScript es mínima y la pérdida de legibilidad del código es importante. Mas que un atajo es un antipatrón, es decir, una mala forma de solucionar un problema.

Fuentes:
Double bitwise NOT (~~)
Tilde or the Floor? Practical use for JavaScript bitwise operators
Stackoverflow: What is the “double tilde” (~~) operator in JavaScript?

miércoles, 17 de julio de 2013

Diferencias entre URI y URL

Hemos escuchado y leido los dos términos miles de veces, ¿tenemos claro su significado?. En realidad son tres los términos implicados en esta enorme confusión: URI, URL y URN. El primero engloba a los otros dos:

Las definiciones son:

  • URI Uniform Resource Identifier (Identificador Uniforme de Recursos)
  • URL Uniform Resource Locator (Localizador Uniforme de Recursos)
  • URN Uniform Resource Name (Nombre Uniforme de Recursos)

Como vemos en la imagen, un identificador (URI) puede ser un localizador (URL), un nombre (URN) o ambas cosas.

Una URN es un nombre único para un recurso, pero no da ninguna información para su localización. Es también una URI, puesto que lo identifica.

Una URL también identifica un recurso (es una URI), pero su característica especial es que permite localizarlo, acceder al recurso que identifica.

Estos son algunos ejemplos de URN sacados del artículo de la wikipedia:

URN corresponde a
urn:isbn:0451450523 El libro El último unicornio, de 1968, identificado por su número ISBN.
urn:isan:0000-0000-9E59-0000-O-0000-0000-2 La película Spider-Man, de 2002, identificada por su número de identificación audiovisual (ISAN).
urn:issn:0167-6423 La revista científica Science of Computer Programming, identificada por su ISSN.
urn:ietf:rfc:2648 IETF's RFC 2648.
urn:lex:eu:council:directive:2010-03-09;2010-19-UE directiva de la Unión Europea utilizando el espacio de nombres Lex URN.

En todos los casos se proporciona una identificación única de un recurso mediante un sistema de nombrado, pero no obtenemos ninguna información respecto a la localización del documento.

Estos son algunos ejemplos de URLs:

http://www.example.com/welcome.html
https://github.com/proj/ascam.git
file:///home/user/myfile.txt

Estas direcciones identifican un recurso y además nos permiten acceder a él, indicando un protocolo y una dirección.

Es importante tener en cuenta que un recurso no tiene porqué ser un documento, una URI puede hacer referencia a un recurso abstracto, un objeto, un servicio temporal, etc.

Fuentes:

W3C: URIs, URLs, and URNs: Clarifications and Recommendations 1.0
URL vs. URI vs. URN: The Confusion Continues
URLs vs. URIs: Differences and Examples
Wikipedia: Uniform resource identifier