console.log("Hello World!!");
JavaScript (JS) es el lenguaje de programación desarrollado por Netscape para la web1. Se trata de un lenguaje de script, generalmente interpretado por el navegador del usuario, esto es, se ejecuta en el cliente2. Hablamos del cliente refiriéndonos a la máquina del usuario final que visitará el sitio web, y del servidor para referirnos a la máquina o el conjunto de ellas que lo almacenan y proporcionan. Sin embargo, también se puede utilizar JavaScript como lenguaje de servidor gracias a Node.js.
JavaScript tiene una sintaxis similar a la de C++ o Java, y puede funcionar como un lenguaje procedimental o como uno orientado a objetos. Además, es capaz de responder a eventos generados mediante la interacción del usuario, lo que es generalmente una de sus principales funciones.
- Nota
- Pese al nombre, JavaScript no deriva de Java, los dos lenguajes apenas se parecen, exceptuando el aspecto de la sintaxis. De hecho, se llamó Mocha y LiveScript antes de obtener su nombre definitivo.
Para ejecutar tu propio código JavaScript, pulsa F12 o Ctrl-Mayus-C en tu navegador. En las herramientas de desarrollador tendrás una consola JS interactiva disponible para experimentar. Es importante notar que cada navegador implementa JavaScript de una manera ligeramente distinta, luego puede haber peculiaridades en la forma de aprovechar algunos recursos del navegador. El estándar del lenguaje se denomina ECMAScript.
Sintaxis y tipos
Declaraciones, variables y funciones
JavaScript no es fuertemente tipado, por lo que declaramos las variables
simplemente con la palabra reservada var
. Declaramos una constante con
const
y una función con var ... = function()
o function ...()
. También podemos declarar
variables simplemente con una asignación, pero
entonces serán globales, lo que es equivalente a declararlas fuera de
cualquier función (las variables y funciones globales se convierten en
atributos del objeto window
).
var una = 1;
const PI = 3.141592;
var perimetro_circulo = function(radio) {
// Esta variable no estaba declarada antes,
// así que es global
tau = 2 * PI;
return tau * radio;
};
// Otra forma de declarar funciones
function area_circulo(radio) {
return PI * radio * radio;
}
- Nota
- JavaScript acepta ciertos símbolos a la hora de nombrar variables. Un
nombre de variable no puede empezar por un número, pero sí que puede
comenzar y contener símbolos como
$
,_
o incluso 3.
Colecciones y objetos
En JS utilizamos los corchetes para representar arrays. Estos pueden contener objetos de distinta naturaleza, por ejemplo, el siguiente array es completamente válido:
var válido = [20, "Hola", /^[a-z]/];
Los objetos son uno de los pilares de JS. En este lenguaje, los objetos existen, no se crean como instancias de una clase, por lo que podemos declarar nuevos con la siguiente sintaxis:
var un_objeto = {
dato: "valor",
otro: 3,
una_funcion: function() {
return Math.random();
}
};
// Acceso a datos:
un_objeto.otro; // --> 3
un_objeto["dato"]; // --> "valor"
un_objeto.una_funcion();
La forma en que se define el objeto es conocida como JavaScript Object
Notation (JSON). Un objeto puede funcionar como un
diccionario, de la forma { clave: "valor", clave2: "otro" }
. Solemos llamar
propiedad a cada una de las parejas clave-valor de un objeto.
Tipos
JavaScript tiene algunos tipos básicos: Number
, String
, Boolean
.
El resto de objetos (los de Date
, RegExp
…) serán de tipo
Object
, y las funciones y constructores son de tipo Function
.
Se puede comprobar el tipo de un objeto mediante typeof
:
typeof 5; // --> Number
typeof []; // --> Object
typeof Math.pow; // --> Function
Además, se utilizan algunas palabras para identificar variables nulas y
no inicializadas: null
y undefined
, respectivamente. Para indicar
resultados de operaciones matemáticas no válidas se usa NaN
(o Infinity
,
en caso de divisiones por 0 o números demasiado grandes):
var sin_inicializar;
sin_inicializar; // --> undefined
Math.log(-1); // --> NaN
Math.pow(2, 10000); // --> Infinity
Control de flujo
Disponemos de las mismas estructuras que en el resto de lenguajes similares:
- if-else
- for
- for-in
- while
- do-while
- switch-case
La estructura for-in es algo diferente de sus equivalentes en C++ y Java, y además de arrays nos permite recorrer las propiedades de cualquier objeto:
var recetas = [
{
nombre: "Paella",
ingrediente_principal: "arroz"
},
{
nombre: "Tortilla",
ingrediente_principal: "huevo"
},
{
nombre: "Bocata",
ingrediente_principal: "pan"
}
];
for (var i in recetas) {
// i referencia al índice!
console.log("Receta nº " + i);
for (var dato in recetas[i]) {
// dato referencia la clave
console.log(dato + ": " + recetas[i][dato]);
}
}
Tenemos dos comparaciones de igualdad, una que convierte tipos implícitamente para comparar el contenido, y otra que es estricta con los tipos:
const POLO = 28;
// Comparación de igualdad:
POLO == "28"; // --> true
// Comparación de igualdad y tipo:
POLO === "28"; // --> false ("28" es String)
Por último, también podemos usar los operadores lógicos ||
y &&
para control
de flujo, algo muy utilizado para escribir código compatible con todos los
navegadores:
var nodo = document.getElementById("identificador");
// Cada navegador aceptará innerText o textContent
var texto = nodo.innerText || nodo.textContent;
El Document Object Model
Acceso a los nodos HTML
Desde luego, si nuestro objetivo es modificar contenido en un sitio web e interactuar con el usuario, es necesario algo más que todo esto. Para esta tarea disponemos del Document Object Model o DOM, que básicamente se encarga de proveer la información de la página web de una forma que sea consistente con el lenguaje.
Así, tendremos disponibles una serie de objetos, partiendo de window
,
que nos proporcionarán acceso, entre otros, a lo siguiente:
window
: Es el padre de todos los objetos del documento. Es indistinto acceder awindow.objeto
y aobjeto
. Si declaramos una variable fuera de una función queda como objeto hijo dewindow
.title
: Título de la página.location
location.href
: URL de la página.location.hash
: hash (la parte del URL que viene después del símbolo#
)
document
: Acceso a los nodos HTML del documento.document.getElementByID(identificador)
: Obtiene un nodo mediante el identificador HTML (atributoid
).document.querySelector(selector)
: Obtiene un nodo mediante un selector estilo CSS.document.createElement(tipo)
: Crea un nuevo nodo HTML del tipo especificado.
- Nota
- El DOM sólo está disponible cuando se ejecuta JavaScript en una página HTML en el cliente. No existe en otros intérpretes de JS como Rhino ni en la parte de servidor como en node.js.
Eventos
Además, para responder a interacciones del usuario disponemos de eventos, es decir, podemos asociar funciones a acciones de tipo click, key press, drag, etc. de forma que la página web responda ante ellas. Por ejemplo:
<!-- Nodo HTML: -->
<input id="mi_boton" type="button" value="Púlsame">
var alerta_click = function() {
// this referencia al nodo que responde al evento
console.log("Has pulsado el botón " + this.id);
};
document.querySelector("#mi_boton").onclick = alerta_click;
// Otra forma:
document.querySelector("#mi_boton").addEventListener("click", alerta_click);
Mucho más sobre eventos en 4.
Orientación a objetos: Los prototipos
Construyendo objetos
JavaScript está orientado a objetos, literalmente. No existen las clases en este lenguaje,
sino que podemos crear objetos a partir de otros que llamamos prototipos, que están
asociados a funciones constructoras. Estas funciones son idénticas a
cualquier otra, salvo por que no devuelven un valor, y en su lugar obtenemos un objeto
al utilizarlas con new
. Veamos un ejemplo:
// El constructor
var Cancion = function(nombre, artista) {
this.nombre = nombre;
this.artista = artista;
};
// Definimos un método en el prototipo
Cancion.prototype.reproducir = function() {
console.log("Reproduciendo " + this.nombre + " de " + this.artista);
};
// Creamos un objeto con el constructor
var una_cancion = new Cancion("Clocks", "Coldplay");
una_cancion.reproducir(); // --> "Reproduciendo Clocks de Coldplay"
Mediante la función constructora Cancion
hemos creado un prototipo vacío (en Cancion.prototype
), al que
después hemos añadido el método reproducir
. Al utilizar el prototipo como plantilla,
cada objeto que se cree mediante new Cancion(...)
dispondrá del método. Aun así,
también podemos añadir otros métodos y atributos a cualquier objeto independientemente
del prototipo con que se haya creado.
Visibildad de datos
Otra peculiaridad de la orientación a objetos de JS es la falta de encapsulación. Un
dato miembro de un objeto, declarado con this.dato
es visible en cualquier ámbito:
var inception = new Cancion("Time", "Hans Zimmer");
// Podemos modificar los datos miembro, e incluso los métodos
inception.nombre = "Mombasa";
inception.reproducir = function() {
console.log("Banda sonora épica");
};
Una alternativa para ocultar información es utilizar variables en el constructor, de forma que solo están visibles en el ámbito de la función constructora. El problema es que entonces no estarán visibles para los métodos que declaremos en el prototipo. Podemos sin embargo definir getters dentro del constructor, y utilizarlos en los métodos:
var Cancion = function(nombre, artista) {
var name = nombre;
var artist = artista;
this.nombre = function() {
return name;
}
this.artista = function() {
return artist;
}
};
Cancion.prototype.reproducir = function() {
console.log("Reproduciendo " + this.nombre() + " de " + this.artista());
};
Ahora las variables name
y artist
no son visibles ni modificables fuera del constructor, pero se pueden
consultar mediante los métodos nombre()
y artista()
respectivamente. Esto funciona
porque aunque la función constructora se termine de ejecutar, las variables declaradas
no desaparecen, y permanecen disponibles en los métodos getters.
- Nota
- La diferencia entre declarar métodos en el prototipo y declararlos en el constructor
(con
this.metodo
) es que los del prototipo se definen una vez y se aplican a todos los objetos, mientras que los del constructor se definen para cada objeto creado, lo cual es menos eficiente. Por eso es preferible declarar los métodos en el prototipo5.
Herencia
Como era de esperar, la herencia en JavaScript también es bastante particular. No existe una forma directa de hacer que una función “herede” de otra, por lo que no podemos crear constructores a partir de constructores anteriores. Sin embargo, podemos escribir constructores cuyo prototipo asociado sea una instancia de uno ya existente6:
var Parent = function() {
console.log("Creando objeto de la clase padre");
};
Parent.prototype.metodo = function() {
return "Este método se heredará";
};
// Constructor nuevo
var Child = function() {
console.log("Este es el constructor de la clase hija");
};
// Usamos el prototipo de la clase padre
Child.prototype = new Parent();
// ...pero asociándolo al constructor de la clase hija
Child.prototype.constructor = Child;
// Podemos añadir métodos
Child.prototype.nuevo_metodo = function() {
return "Solo en la clase hija";
};
var partest = new Parent();
partest instanceof Parent; // --> true
partest instanceof Child; // --> false
var chitest = new Child();
chitest instanceof Parent; // --> true
chitest instanceof Child; // --> true
En este ejemplo hemos creado dos constructores, uno con prototipo
propio, y otro al que asociamos un objeto creado a partir del primer
prototipo. Comprobamos mediante instanceof
que la herencia funciona
como se esperaba, un objeto creado mediante Child
es instancia de este
constructor y del padre, Parent
, y dispone de los métodos de ambos en
su prototipo.
Conclusión
En este artículo hemos visto cómo funciona JavaScript y cómo interactúa con un sitio web, y hemos estudiado sus posibilidades desde el punto de vista de la orientación a objetos. Si queréis saber más sobre el lenguaje y aprender a programar en él, serán muy útiles las referencias a páginas web, y os puedo recomendar el libro 7, algo anticuado ya pero muy completo y conciso.