martes, abril 24, 2012

Tutorial de desarrollo web, Parte XXIX: Strings

¡Seguimos adelante con el curso de JavaScript! Espero que todos estos posts que estoy dejando escritos antes de irme se estén quedando claros. Si no es así, no duden en dejarme comentarios y, cuando pueda, intentaré aclararlo en algún post (aunque tal vez tengais que esperar hasta mediados de junio, que vuelvo a España... en principio).
Bien, el ejercicio de la semana pasada prometí explicarlo en este post y por ahí es por donde voy a empezar. Como recordaréis, habíamos escrito un código JavaScript en el que se combinaban sumas y concatenaciones. En concreto era este código:

<script type="text/javascript">
var entero;
var real;
var caracter;
var booleano;
entero = 1;
real = 1.5;
booleano = true;
document.write('' + entero + ' es un entero. <br/> ');
document.write('' + booleano + 2 + ' son un booleano y un entero. <br/> ');
document.write(real + entero + ' es un número real.<br/>');
</script>
Al ejecutarlo, os habréis encontrado con este resultado:
1 es un entero.
true2 son un booleano y un entero.
2.5 es un número real.
 Habíamos visto que cuando dos valores numéricos se encontraban con un operador + en medio (contando como valores numéricos los valores "enteros", "reales" y "booleanos"), los valores de ambos se sumaban, mientras que si lo que se encontraba con dicho operador eran valores de texto ("caracter" o "ristra de caracteres"), sin importar si a ambos lados del operador habían valores de texto o sólo se encontraba en uno de los lados, ambos valores se concatenarían.
Sin embargo, en este ejercicio hay alguna excepción a esta regla. En la primera línea, todo ocurre como previsto. La cadena vacía '' se concatena con la variable "entero" que contiene un 1 y luego con la cadena ' es un entero <br/>' formando la frase que vemos más abajo. Sin embargo, en la segunda línea ocurre algo extraño.
En la anterior entrega habíamos visto que podíamos sumar números enteros (o incluso reales) a variables con valores booleanos, de modo que sus valores (0 ó 1 en el caso del booleano) se sumarían. Sin embargo, en esta ocasión, el valor del booleano, que puede ser interpretado como 1 y como "true", se está concatenando con el valor del entero literal 2. ¿Por qué? Simplemente por la cadena vacía '' que se concatena antes del booleano.
Como dije la semana pasada, JavaScript lee de arriba a abajo y de izquierda a derecha, así que al llegar a esa operación lo primero que lee es la cadena vacía y, al empezar la línea con una cadena, interpreta que todos los siguientes operadores + son de concatenación, por lo que hace eso.
Esto ocurrirá siempre a partir del primer valor de texto que incluyamos. Imaginemos que tenemos la siguiente expresión:

document.write(booleano + 2 + ' no es lo mismo que ' + booleano + 2);

Este código daría como resultado:
3 no es lo mismo que true2
 Y bien nos avisa el texto de que no es lo mismo. Como veis, al entrar en el "write", el navegador primero se encuentra con un booleano sumándose a un entero, y entonces asume que, por tanto, se trata de una suma numérica. Sin embargo, después de eso encuentra una cadena, y a partir de ese momento todo lo siguiente que encuentre en la línea será considerado una concatenación. ¿Existe alguna manera de que no lo concatenara? Sí, de la misma manera que haríamos en matemáticas: usando paréntesis. Veamos el siguiente ejemplo:

document.write(booleano + 2 + ' es lo mismo que ' + (booleano + 2));

El resultado de este código sería:
3 es lo mismo que 3
Efectivamente, es lo mismo. ¿Y cómo hemos conseguido arreglarlo? Simplemente: al igual que ocurría cuando resolvíamos operaciones matemáticas, el paréntesis es preferente en las operaciones, así que, aunque el navegador vaya ejecutando las operaciones de izquierda a derecha, en primer lugar busca si hay paréntesis y resuelve su interior, comprobando que no se trata de una concatenación sino de una suma. Después resuelve el resto, en primer lugar resolviendo la primera suma y finalmente, al encontrarse la ristra, concatenando todos los valores.
De todos modos, ya más adelante veremos la preferencia de operadores, donde hablaremos una vez más de los paréntesis, pero para situaciones como esta nos viene bien echarle un vistazo. Además, habrá veces que nos interesará concatenar valores numéricos, por los que el truco de usar la cadena vacía '' al comienzo de la línea nos vendrá bastante bien.
La última de las líneas tampoco tiene mucho misterio: sumamos un real y un entero, de modo que su valor final será real, y luego le concatenamos una ristra. Recordemos que la suma de dos valores numéricos siempre dará como resultado un valor del tipo más amplio (en este caso, el real, que acepta decimales, es más amplio que el entero, que no los acepta).
Y, como prometimos la semana pasada, hoy vamos a hablar de uno de los primeros tipos de datos complejos: los Strings o ristras de caracteres. Hasta el momento hemos visto muchas ristras de caracteres pero no hemos ni siquiera empezado a indagar en la cantidad de posibilidades que tiene. Así que, antes que nada, vamos a entender lo que es un String.
Los cuatro tipos de datos que vimos la semana pasada, como dije, eran los tipos básicos. Los tipos complejos, como los Strings, se forman al unir varios datos básicos. En el caso de los Strings, como podemos ver, son muchas variables de tipo caracter concatenadas. Sin embargo, su utilidad y su frecuente uso ha hecho que, a pesar de tratarse de un tipo de dato complejo, se suela aprender a usarlo casi al mismo tiempo que los tipos de datos simples.
Para declarar una variable de tipo String no hay que hacer nada especial. Declaremos, pues, en un nuevo documento, la siguiente variable, e inicialicémosla (recordemos el verbo "inicializar" porque lo veremos mucho: consiste en dar un primer valor a una variable):

var ristra;
ristra = 'Esto es una ristra';

Podemos ver que una String funciona, básicamente, del mismo modo que una variable caracter, sólo que conteniendo varios caracteres. Pero aún su valor puede estar entrecomillado. Si queremos mostrar su valor por pantalla sólo tendremos que hacer:

document.write(ristra);

Obteniendo por pantalla:
Esto es una ristra
Nada difícil, ¿no? De hecho, ya en el tercer número de JavaScript (la parte XVII cuando por primera vez hablamos de variables) habíamos usado una variable de tipo String (lo que obteníamos del "prompt" era un conjunto de caracteres, por lo tanto, una String). Sin embargo, las Strings poseen una serie de funciones asociadas que nos pueden resultar muy útiles a la hora de trabajar con ellas. Y sobre eso es sobre lo que vamos a trabajar ahora.
Antes que nada hemos de recordar que las funciones (también conocidas como métodos o procedimientos) son instrucciones de JavaScript con un objetivo concreto. Como ejemplo de funciones hemos visto hasta el momento el "write", el "alert" y el "prompt" que, como veíamos, requerían que le introdujéramos, dentro de los paréntesis que caracterizan a todas las funciones de JavaScript, una String que, como hemos podido comprobar con la función "write", puede ser una String literal, una variable String o incluso una concatenación de varios valores, que al final acabarán formando una String. Esta característica también está presente en los "alert", "prompt" y cualquier otra función que requiera un String.
A continuación vamos a revisar las más importantes de estas funciones de Strings:
  • La primera de las características de String que vamos a ver no es realmente una función sino un atributo. Las diferencias las iremos conociendo con el tiempo, pero de momento tenemos que saber, al menos, que los atributos no llevan paréntesis, al contrario que las funciones. Es el atributo length que devuelve el número de caracteres que contenga la String. Por ejemplo, en el caso anterior, si hiciéramos:
  • var tamanyo = ristra.length;
  • En la variable "tamanyo" (nótese que las variables de JavaScript no pueden tener caracteres españoles como la ñ o las vocales con tilde o diéresis) hemos guardado el valor de "ristra.length", que, como he dicho, guarda el número de caracteres que contiene la ristra. Si tomamos en cuenta que el espacio es también un caracter, si le hacemos un "write" a "tamanyo" comprobaríamos que en su interior se ha guardado el valor 18.
  • También me gustaría mencionar que, como podéis ver, en una misma línea podemos declarar e inicializar una variable sin problema.
  • Empezando ya con funciones de verdad, en primer lugar veremos las funciones "toLowerCase" y "toUpperCase". Sabiendo un poco de inglés (o si hemos investigado algo de CSS) podemos deducir que estas dos funciones convierten en minúsculas ("toLowerCase") o en mayúsculas ("toUpperCase") todos los caracteres de una función. Su uso sería el siguiente:
  • var ristraMayusculas = ristra.toUpperCase();
  • Ahora dentro de "ristraMayusculas" encontraremos guardado el valor 'ESTO ES UNA RISTRA'.
  • Como veis, el uso es muy parecido al del "length", solo que en este caso, al ser una función, cuenta con un par de paréntesis al final. Sin embargo, a diferencia de las funciones que habíamos visto hasta ahora, en esta no introducimos nada entre los paréntesis. Lo cierto es que la sintaxis de todas las funciones requieren llevar esos paréntesis, que es un hueco reservado para los "valores de entrada" a la función, pero no todas las funciones requieren valores de entrada. De este modo, cada vez que llamemos a una función deberemos saber cuántos y qué tipo de valores de entrada requieren (en los que habíamos visto hasta ahora requerían una String, que podíamos hacer todo lo larga que quisiéramos, pero siempre era una única String).
  • También hay que mencionar el hecho de que, al igual que ocurría con el "write" (pero no con el "alert" ni el "prompt") e incluso con el atributo "length", antes de la función hemos de escribir la variable sobre la que se va a realizar la función separándolas por un punto. El por qué algunas funciones necesitan una variable sobre la que realizar la función y otras no lo estudiaremos cuando aprendamos a hacer nuestras propias funciones personalizadas.
  • Por supuesto, 'toLowerCase' funciona exactamente igual pero al contrario.
  • Otra función que nos puede ser de utilidad es la función "indexOf". Esta función sí recibe un valor (de hecho, puede recibir hasta dos valores distintos, ahora hablaremos de eso), que será una letra que vamos a buscar en la String. Lo entenderemos mejor en un ejemplo:
  • var lugarR = ristra.indexOf('r');
  • De esta manera le estamos indicando al navegador que queremos que guarde en la variable "lugarR" la posición que ocupa la letra "r" en su primera aparición en la ristra. Teniendo en cuenta que esta variable cuenta la primera letra como la posición 0, el valor guardado en "lugarR" será 12.
  • Como anotación, en caso de no encontrar la letra, la función devolverá el valor -1.
  • Sin embargo, como digo, la función "indexOf" tiene una variante, que en este caso contendrá dos valores de entrada, en este caso la posición inicial a partir de la cual empezará a contar. Por ejemplo:
  • var lugarR = ristra.indexOf('r', 13);
  • Como vemos, cuando una función requiere que le introduzcamos varios valores, estos serán separados por comas. En esta función, haciendo "trampa" ya que vimos en la anterior que la primera "r" estaba en el lugar 12 le hemos dicho, en su segundo valor, que empiece a buscar a partir de la posición 13, y su resultado sería 16.
  • La siguiente función que vamos a definir es el contrario a la anterior: la función "charAt". En esta ocasión le diremos una posición a la función para que nos devuelva el caracter que se encuentra en esa posición.
  • var letra5 = charAt(5);
  • Al igual que ocurría con el "indexOf", las posiciones de la String empiezan a contar a partir del 0, de modo que esta función estaría devolviéndonos la letra "e".
  • Siguiendo con funciones similares al "indexOf", en este caso tenemos el "lastIndexOf", que, como su nombre indica, devuelve la última aparición del caracter que le introduzcamos:
  • var letraRFinal = ristra.lastIndexOf('r');
  • Como pudimos comprobar antes, este método nos devolverá el número 16 (encuentra la última vez que aparece, pero da la posición como si contara desde el principio). Sin embargo, al igual que el "indexOf" tiene una variante que, tal vez, es un poco difícil de comprender.
  • El "lastIndexOf" también se puede introducir con dos parámetros (o "valores de entrada"), aunque es un poco más difícil de entender que el "indexOf" ya que el segundo parámetro... ¡también se cuenta desde el comienzo!
  • var letraRFinalAt2 = ristra.lastIndexOf('r', 2);
  • Por ejemplo en este caso la variable guardaría un -1 ya que la función no encontraría la letra "r". ¿Por qué? Intentemos entenderlo de esta manera. Al indicarle la posición "2", la función se ha colocado en la posición "2" de la ristra (recordamos que la primera posición es la posición 0, así que la posición 2 sería la "t") y luego ha empezado a contar hacia atrás, no encontrando ninguna "r" en el camino (sólo ha encontrado una "s" y una "e"). ¿Cómo podemos hacer que encuentre alguna "r"? Por ejemplo, sabiendo que hay una "r" en la posición 12, podemos decirle:
  • var letraRFinalAt13 = ristra.lastIndexOf('r', 13);
  • Con esta función, la función empezará a contar hacia atrás a partir de la posición 13 de la ristra y, al llegar al punto 12, encontrará la "r", devolviendo dicho número.
  • y ahora vamos a comprender el concepto de Substrings. Las Substrings, como su nombre indica, son un subconjunto de Strings. Realmente, una Substring no es más que una porción de una String, y como nadie nunca ha definido el tamaño de una String (existe la String vacía: ''), realmente una Substring es una String en todo derecho, pero así se llama al método con el que se crea esta String porción de otra.
  • var subristra = ristra.substring(3, 10)
  • En la variable "subristra" se guardará una String que equivaldrá a todo el contenido desde las posiciones 3 hasta la 10 (ambas inclusive) de la ristra que ya teníamos. Como siempre, hay que empezar a contar por el número 0 como primera posición de la ristra, de modo que esta substring tendrá el valor 'o es un '.
Existen muchas otras funciones aplicables a las Strings, pero de momento con esa creo que tenemos bastante. Al igual que ocurre con HTML y CSS, dar una lista completa de las funciones de cada objeto de JavaScript sería casi imposible, por lo que intentaremos comprender algunos conceptos y, con el tiempo tal vez aprendamos alguna que otra función, pero para algo escribiré también la guía de referencia correspondiente cuando toque el momento.
Como última anotación, quiero señalar el hecho de que lo primero que vimos en este artículo, el atributo "length" de JavaScript, cuenta el número de caracteres que contiene la ristra, pero esto no se corresponde con la última posición de la String, ya que, como he repetido en varias ocasiones, las posiciones empiezan a contarse a partir del número 0. De este modo, la última posición de una String se puede definir como:

var ultimaPosicion = ristra.length - 1;

Simplemente es un truco que os será útil en muchas (demasiadas) ocasiones.

EJERCICIOS

Esta semana tenemos algo más de jugo para ir trabajando. Nuestro primer ejercicio de programación va a consistir en declarar en una página una ristra con el siguiente contenido:
Las monocotiledóneas tienen hojas no pecioladas y sus pétalos y estambres están dispuestos en grupos de tres.
Tendremos que conseguir extraer a una substring la palabra "pecioladas" pero, ¡ojo!, no se vale contar los puestos que hay, todo deberemos conseguirlo a través de programación (como si no supiéramos cómo es la frase).
En caso de que queráis hacerlo algo más difícil, intentadlo con "pétalos". Para ese segundo os doy una pista: la función "indexOf" no sólo busca letras, sino también substrings.

Y eso es todo por esta semana, la semana que viene empezaremos a ver las primeras estructuras, ¡estad atentos!

No hay comentarios: