Capítulo 2. Conceptos Básicos de JavaScript
2.1. Introducción
jQuery se encuentra escrito en JavaScript, un lenguaje de programación muy rico y expresivo.
Si usted esta interesado en aprender el lenguaje más en profundidad, puede leer el libro JavaScript: The Good Parts escrito por Douglas Crockford.
2.2. Sintaxis básica
Comprensión de declaraciones, nombres de variables, espacios en blanco, y otras sintaxis básicas de JavaScript.
Declaración simple de variable
var foo = 'hello world';
Los espacios en blanco no tienen valor fuera de las comillas
var foo = 'hello world';
Los paréntesis indican prioridad
2 * 3 + 5; // es igual a 11; la multiplicación ocurre primero 2 * (3 + 5); // es igual a 16; por lo paréntesis, la suma ocurre primero
La tabulación mejora la lectura del código, pero no posee ningún significado especial
var foo = function() { console.log('hello'); };
2.3. Operadores
2.3.1. Operadores básicos
Los operadores básicos permiten manipular valores.
Concatenación
var foo = 'hello';
var bar = 'world';
console.log(foo + ' ' + bar); // la consola de depuración muestra 'hello world'
Multiplicación y división
2 * 3; 2 / 3;
Incrementación y decrementación
var i = 1;
var j = ++i; // incrementación previa: j es igual a 2; i es igual a 2
var k = i++; // incrementación posterior: k es igual a 2; i es igual a 3
2.3.2. Operaciones con números y cadenas de caracteres
En JavaScript, las operaciones con números y cadenas de caracteres (en inglés strings) pueden ocasionar resultados no esperados.
Suma vs. concatenación
var foo = 1;
var bar = '2';
console.log(foo + bar); // error: La consola de depuración muestra 12
Forzar a una cadena de caracteres actuar como un número
var foo = 1;
var bar = '2';
// el constructor 'Number' obliga a la cadena comportarse como un número
console.log(foo + Number(bar)); // la consola de depuración muestra 3
El constructor Number
, cuando es llamado como una función (como se muestra en el ejemplo) obliga a su argumento a comportarse como un número. También es posible utilizar el operador de suma unaria, entregando el mismo resultado:
Forzar a una cadena de caracteres actuar como un número (utilizando el operador de suma unaria)
console.log(foo + +bar);
2.3.3. Operadores lógicos
Los operadores lógicos permiten evaluar una serie de operandos utilizando operaciones AND y OR.
Operadores lógicos AND y OR
var foo = 1;
var bar = 0;
var baz = 2;
foo || bar; // devuelve 1, el cual es verdadero (true)
bar || foo; // devuelve 1, el cual es verdadero (true)
foo && bar; // devuelve 0, el cual es falso (false)
foo && baz; // devuelve 2, el cual es verdadero (true)
baz && foo; // devuelve 1, el cual es verdadero (true)
El operador ||
(OR lógico) devuelve el valor del primer operando, si éste es verdadero; caso contrario devuelve el segundo operando. Si ambos operandos son falsos devuelve falso (false
). El operador &&
(AND lógico) devuelve el valor del primer operando si éste es falso; caso contrario devuelve el segundo operando. Cuando ambos valores son verdaderos devuelve verdadero (true
), sino devuelve falso.
Puede consultar la sección "Elementos Verdaderos y Falsos" para más detalles sobre que valores se evalúan como true
y cuales se evalúan como false
.
NOTAPuede que a veces note que algunos desarrolladores utilizan esta lógica en flujos de control en lugar de utilizar la declaración if
. Por ejemplo:
// realizar algo con foo si foo es verdadero
foo && doSomething(foo);
// establecer bar igual a baz si baz es verdadero;
// caso contrario, establecer a bar igual al
// valor de createBar()
var bar = baz || createBar();
Este estilo de declaración es muy elegante y conciso; pero puede ser difícil para leer (sobretodo para principiantes). Por eso se explícita, para reconocerlo cuando este leyendo código. Sin embargo su utilización no es recomendable a menos que esté cómodo con el concepto y su comportamiento.
2.3.4. Operadores de comparación
Los operadores de comparación permiten comprobar si determinados valores son equivalentes o idénticos.
Operadores de Comparación
var foo = 1;
var bar = 0;
var baz = '1';
var bim = 2;
foo == bar; // devuelve falso (false)
foo != bar; // devuelve verdadero (true)
foo == baz; // devuelve verdadero (true); tenga cuidado
foo === baz; // devuelve falso (false)
foo !== baz; // devuelve verdadero (true)
foo === parseInt(baz); // devuelve verdadero (true)
foo > bim; // devuelve falso (false)
bim > baz; // devuelve verdadero (true)
foo <= baz; // devuelve verdadero (true)
2.4. Código condicional
A veces se desea ejecutar un bloque de código bajo ciertas condiciones. Las estructuras de control de flujo — a través de la utilización de las declaraciones if
y else
permiten hacerlo.
Control del flujo
var foo = true;
var bar = false;
if (bar) {
// este código nunca se ejecutará
console.log('hello!');
}
if (bar) {
// este código no se ejecutará
} else {
if (foo) {
// este código se ejecutará
} else {
// este código se ejecutará si foo y bar son falsos (false)
}
}
NOTAEn una línea singular, cuando se escribe una declaración if
, las l laves no son estrictamente necesarias; sin embargo es recomendable su utilización, ya que hace que el código sea mucho más legible.
Debe tener en cuenta de no definir funciones con el mismo nombre múltiples veces dentro de declaraciones if
/else
, ya que puede obtener resultados no esperados.
2.5. Elementos verdaderos y falsos
Para controlar el flujo adecuadamente, es importante entender qué tipos de valores son "verdaderos" y cuales "falsos". A veces, algunos valores pueden parecer una cosa pero al final terminan siendo otra.
Valores que devuelven verdadero (true)
'0'; // una cadena de texto cuyo valor sea 0
'any string'; // cualquier cadena
[]; // un array vacío
{}; // un objeto vacío
1; // cualquier número distinto a cero
Valores que devuelven falso (false)
0;
''; // una cadena vacía
NaN; // la variable JavaScript "not-a-number" (No es un número)
null; // un valor nulo
undefined; // tenga cuidado -- indefinido (undefined) puede ser redefinido
2.5.1. Variables condicionales utilizando el operador ternario
A veces se desea establecer el valor de una variable dependiendo de cierta condición. Para hacerlo se puede utilizar una declaración if
/else
, sin embargo en muchos casos es más conveniente utilizar el operador ternario. [Definición: El operador ternario evalúa una condición; si la condición es verdadera, devuelve cierto valor, caso contrario devuelve un valor diferente.]
El operador ternario
// establecer a foo igual a 1 si bar es verdadero;
// caso contrario, establecer a foo igual a 0
var foo = bar ? 1 : 0;
El operador ternario puede ser utilizado sin devolver un valor a la variable, sin embargo este uso generalmente es desaprobado.
2.5.2. Declaración switch
En lugar de utilizar una serie de declaraciones if
/else
/else if
/else
, a veces puede ser útil la utilización de la declaración switch
. [Definición: La declaración Switch
evalúa el valor de una variable o expresión, y ejecuta diferentes bloques de código dependiendo de ese valor.]
Una declaración switch
switch (foo) {
case 'bar':
alert('el valor es bar');
break;
case 'baz':
alert('el valor es baz');
break;
default:
alert('de forma predeterminada se ejecutará este código');
break;
}
Las declaraciones switch
son poco utilizadas en JavaScript, debido a que el mismo comportamiento es posible obtenerlo creando un objeto, el cual posee más potencial ya que es posible reutilizarlo, usarlo para realizar pruebas, etc. Por ejemplo:
var stuffToDo = {
'bar' : function() {
alert('el valor es bar');
},
'baz' : function() {
alert('el valor es baz');
},
'default' : function() {
alert('de forma predeterminada se ejecutará este código');
}
};
if (stuffToDo[foo]) {
stuffToDo[foo]();
} else {
stuffToDo['default']();
}
2.6. Bucles
Los bucles (en inglés loops) permiten ejecutar un bloque de código un determinado número de veces.
Bucles
// muestra en la consola 'intento 0', 'intento 1', ..., 'intento 4'
for (var i=0; i<5; i++) {
console.log('intento ' + i);
}
Note que en el ejemplo se utiliza la palabra var
antes de la variable i
, esto hace que dicha variable quede dentro del "alcance" (en inglés *scope) del bucle. Más adelante en este capítulo se examinará en profundidad el concepto de alcance.*
2.6.1. Bucles utilizando for
Un bucle utilizando for
se compone de cuatro estados y posee la siguiente estructura:
for ([expresiónInicial]; [condición]; [incrementoDeLaExpresión])
[cuerpo]
El estado expresiónInicial es ejecutado una sola vez, antes que el bucle comience. Éste otorga la oportunidad de preparar o declarar variables.
El estado condición es ejecutado antes de cada repetición, y retorna un valor que decide si el bucle debe continuar ejecutándose o no. Si el estado condicional evalúa un valor falso el bucle se detiene.
El estado incrementoDeLaExpresión es ejecutado al final de cada repetición y otorga la oportunidad de cambiar el estado de importantes variables. Por lo general, este estado implica la incrementación o decrementación de un contador.
El cuerpo es el código a ejecutar en cada repetición del bucle.
Un típico bucle utilizando for
for (var i = 0, limit = 100; i < limit; i++) {
// Este bloque de código será ejecutado 100 veces
console.log('Currently at ' + i);
// Nota: el último registro que se mostrará
// en la consola será "Actualmente en 99"
}
2.6.2. Bucles utilizando while
Un bucle utilizando while
es similar a una declaración condicional if
, excepto que el cuerpo va a continuar ejecutándose hasta que la condición a evaluar sea falsa.
while ([condición]) [cuerpo]
Un típico bucle utilizando while
var i = 0;
while (i < 100) {
// Este bloque de código se ejecutará 100 veces
console.log('Actualmente en ' + i);
i++; // incrementa la variable i
}
Puede notar que en el ejemplo se incrementa el contador dentro del cuerpo del bucle, pero también es posible combinar la condición y la incrementación, como se muestra a continuación:
Bucle utilizando while con la combinación de la condición y la incrementación
var i = -1;
while (++i < 100) {
// Este bloque de código se ejecutará 100 veces
console.log('Actualmente en ' + i);
}
Se comienza en -1
y luego se utiliza la incrementación previa (++i
).
2.6.3. Bucles utilizando do-while
Este bucle es exactamente igual que el bucle utilizando while
excepto que el cuerpo es ejecutado al menos una vez antes que la condición sea evaluada.
do [cuerpo] while ([condición])
Un bucle utilizando do-while
do {
// Incluso cuando la condición sea falsa
// el cuerpo del bucle se ejecutará al menos una vez.
alert('Hello');
} while (false);
Este tipo de bucles son bastantes atípicos ya que en pocas ocasiones de necesita un bucle que se ejecute al menos una vez. De cualquier forma debe estar al tanto de ellos.
2.6.4. break
y continue
Usualmente, el fin de la ejecución de un bucle resultará cuando la condición no siga evaluando un valor verdadero, sin embargo también es posible parar un bucle utilizando la declaración break
dentro del cuerpo.
Detener un bucle con break
for (var i = 0; i < 10; i++) {
if (something) {
break;
}
}
También puede suceder que quiera continuar con el bucle sin tener que ejecutar más sentencias del cuerpo del mismo bucle. Esto puede realizarse utilizando la declaración continue
.
Saltar a la siguiente iteración de un bucle
for (var i = 0; i < 10; i++) {
if (something) {
continue;
}
// La siguiente declaración será ejecutada
// si la condición 'something' no se cumple
console.log('Hello');
}
2.7. Palabras reservadas
JavaScript posee un número de palabras reservadas, o palabras que son especiales dentro del mismo lenguaje. Debe utilizar estas palabras cuando las necesite para su uso específico.
abstract
boolean
break
byte
case
catch
char
class
const
continue
debugger
default
delete
do
double
else
enum
export
extends
final
finally
float
for
function
goto
if
implements
import
in
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
super
switch
synchronized
this
throw
throws
transient
try
typeof
var
void
volatile
while
with
2.8. Arrays
Los arrays (en inglés arrays) son listas de valores con índice-cero (en inglés zero-index), es decir, que el primer elemento del array está en el índice 0
. éstos son una forma práctica de almacenar un conjunto de datos relacionados (como cadenas de caracteres), aunque en realidad, un array puede incluir múltiples tipos de datos, incluso otros arrays.
Un array simple
var myArray = [ 'hello', 'world' ];
Acceder a los ítems del array a través de su índice
var myArray = [ 'hello', 'world', 'foo', 'bar' ];
console.log(myArray[3]); // muestra en la consola 'bar'
Obtener la cantidad de ítems del array
var myArray = [ 'hello', 'world' ];
console.log(myArray.length); // muestra en la consola 2
Cambiar el valor de un ítem de un array
var myArray = [ 'hello', 'world' ];
myArray[1] = 'changed';
Como se muestra en el ejemplo Cambiar el valor de un ítem de un array es posible cambiar el valor de un ítem de un array, sin embargo, por lo general, no es aconsejable.
Añadir elementos a un array
var myArray = [ 'hello', 'world' ];
myArray.push('new');
Trabajar con arrays
var myArray = [ 'h', 'e', 'l', 'l', 'o' ];
var myString = myArray.join(''); // 'hello'
var mySplit = myString.split(''); // [ 'h', 'e', 'l', 'l', 'o' ]
2.9. Objetos
Los objetos son elementos que pueden contener cero o más conjuntos de pares de nombres claves y valores asociados a dicho objeto. Los nombres claves pueden ser cualquier palabra o número válido. El valor puede ser cualquier tipo de valor: un número, una cadena, un array, una función, incluso otro objeto.
[Definición: Cuando uno de los valores de un objeto es una función, ésta es nombrada como un método del objeto.] De lo contrario, se los llama propiedades.
Curiosamente, en JavaScript, casi todo es un objeto — arrays, funciones, números, incluso cadenas — y todos poseen propiedades y métodos.
Creación de un "objeto literal"
var myObject = {
sayHello : function() {
console.log('hello');
},
myName : 'Rebecca'
};
// se llama al método sayHello, el cual muestra en la consola 'hello'
myObject.sayHello();
// se llama a la propiedad myName, la cual muestra en la consola 'Rebecca'
console.log(myObject.myName);
NOTANotar que cuando se crean objetos literales, el nombre de la propiedad puede ser cualquier identificador JavaScript, una cadena de caracteres (encerrada entre comillas) o un número:
var myObject = {
validIdentifier: 123,
'some string': 456,
99999: 789
};
Los objetos literales pueden ser muy útiles para la organización del código, para más información puede leer el artículo (en inglés) Using Objects to Organize Your Code por Rebecca Murphey.
2.10. Funciones
Las funciones contienen bloques de código que se ejecutaran repetidamente. A las mismas se le pueden pasar argumentos, y opcionalmente la función puede devolver un valor.
Las funciones pueden ser creadas de varias formas:
Declaración de una función
function foo() { /* hacer algo */ }
Declaración de una función nombrada
var foo = function() { /* hacer algo */ }
Es preferible el método de función nombrada debido a algunas profundas razones técnicas. Igualmente, es probable encontrar a los dos métodos cuando se revise código JavaScript.
2.10.1. Utilización de funciones
Una función simple
var greet = function(person, greeting) {
var text = greeting + ', ' + person;
console.log(text);
};
greet('Rebecca', 'Hello'); // muestra en la consola 'Hello, Rebecca'
Una función que devuelve un valor
var greet = function(person, greeting) {
var text = greeting + ', ' + person;
return text;
};
console.log(greet('Rebecca','hello'));
Una función que devuelve otra función
// la función devuelve 'Hello, Rebecca',
// la cual se muestra en la consola
var greet = function(person, greeting) {
var text = greeting + ', ' + person;
return function() { console.log(text); };
};
var greeting = greet('Rebecca', 'Hello');
greeting(); // se muestra en la consola 'Hello, Rebecca'
2.10.2. Funciones anónimas autoejecutables
Un patrón común en JavaScript son las funciones anónimas autoejecutables. Este patrón consiste en crear una expresión de función e inmediatamente ejecutarla. El mismo es muy útil para casos en que no se desea intervenir espacios de nombres globales, debido a que ninguna variable declarada dentro de la función es visible desde afuera.
Función anónima autoejecutable
(function(){
var foo = 'Hello world';
})();
console.log(foo); // indefinido (undefined)
2.10.3. Funciones como argumentos
En JavaScript, las funciones son ciudadanos de primera clase — pueden ser asignadas a variables o pasadas a otras funciones como argumentos. En jQuery, pasar funciones como argumentos es una práctica muy común.
Pasar una función anónima como un argumento
var myFn = function(fn) {
var result = fn();
console.log(result);
};
myFn(function() { return 'hello world'; }); // muestra en la consola 'hello world'
Pasar una función nombrada como un argumento
var myFn = function(fn) {
var result = fn();
console.log(result);
};
var myOtherFn = function() {
return 'hello world';
};
myFn(myOtherFn); // muestra en la consola 'hello world'
2.11.2.12. La palabra clave this
En JavaScript, así como en la mayoría de los lenguajes de programación orientados a objetos, this es una palabra clave especial que hace referencia al objeto en donde el método está siendo invocado. El valor de this
es determinado utilizando una serie de simples pasos:
- Si la función es invocada utilizando Function.call o Function.apply, this tendrá el valor del primer argumento pasado al método. Si el argumento es nulo (
null
) o indefinido (undefined
), this hará referencia el objeto global (el objeto window
);
- Si la función a invocar es creada utilizando Function.bind,
this
será el primer argumento que es pasado a la función en el momento en que se la crea;
- Si la función es invocada como un método de un objeto,
this
referenciará a dicho objeto;
- De lo contrario, si la función es invocada como una función independiente, no unida a algún objeto, this referenciará al objeto global.
Una función invocada utilizando Function.call
var myObject = {
sayHello : function() {
console.log('Hola, mi nombre es ' + this.myName);
},
myName : 'Rebecca'
};
var secondObject = {
myName : 'Colin'
};
myObject.sayHello(); // registra 'Hola, mi nombre es Rebecca'
myObject.sayHello.call(secondObject); // registra 'Hola, mi nombre es Colin'
Una función creada utilizando Function.bind
var myName = 'el objeto global',
sayHello = function () {
console.log('Hola, mi nombre es ' + this.myName);
},
myObject = {
myName : 'Rebecca'
};
var myObjectHello = sayHello.bind(myObject);
sayHello(); // registra 'Hola, mi nombre es el objeto global'
myObjectHello(); // registra 'Hola, mi nombre es Rebecca'
Una función vinculada a un objeto
var myName = 'el objeto global',
sayHello = function() {
console.log('Hola, mi nombre es ' + this.myName);
},
myObject = {
myName : 'Rebecca'
},
secondObject = {
myName : 'Colin'
};
myObject.sayHello = sayHello;
secondObject.sayHello = sayHello;
sayHello(); // registra 'Hola, mi nombre es el objeto global'
myObject.sayHello(); // registra 'Hola, mi nombre es Rebecca'
secondObject.sayHello(); // registra 'Hola, mi nombre es Colin'
En algunas oportunidades, cuando se invoca una función que se encuentra dentro de un espacio de nombres (en inglés namespace) amplio, puede ser una tentación guardar la referencia a la función actual en una variable más corta y accesible. Sin embargo, es importante no realizarlo en instancias de métodos, ya que puede llevar a la ejecución de código incorrecto. Por ejemplo:
var myNamespace = {
myObject : {
sayHello : function() {
console.log('Hola, mi nombre es ' + this.myName);
},
myName : 'Rebecca'
}
};
var hello = myNamespace.myObject.sayHello;
hello(); // registra 'Hola, mi nombre es undefined'
Para que no ocurran estos errores, es necesario hacer referencia al objeto en donde el método es invocado:
var myNamespace = {
myObject : {
sayHello : function() {
console.log('Hola, mi nombre es ' + this.myName);
},
myName : 'Rebecca'
}
};
var obj = myNamespace.myObject;
obj.sayHello(); // registra 'Hola, mi nombre es Rebecca' Determinación del tipo de variable
JavaScript ofrece una manera de poder comprobar el "tipo" (en inglés type) de una variable. Sin embargo, el resultado puede ser confuso — por ejemplo, el tipo de un array es object
.
Por eso, es una práctica común utilizar el operador typeof
cuando se trata de determinar el tipo de un valor específico.
Determinar el tipo en diferentes variables
var myFunction = function() {
console.log('hello');
};
var myObject = {
foo : 'bar'
};
var myArray = [ 'a', 'b', 'c' ];
var myString = 'hello';
var myNumber = 3;
typeof myFunction; // devuelve 'function'
typeof myObject; // devuelve 'object'
typeof myArray; // devuelve 'object' -- tenga cuidado
typeof myString; // devuelve 'string'
typeof myNumber; // devuelve 'number'
typeof null; // devuelve 'object' -- tenga cuidado
if (myArray.push && myArray.slice && myArray.join) {
// probablemente sea un array
// (este estilo es llamado, en inglés, "duck typing")
}
if (Object.prototype.toString.call(myArray) === '[object Array]') {
// definitivamente es un array;
// esta es considerada la forma más robusta
// de determinar si un valor es un array.
}
2.13. Alcance
El "alcance" (en inglés scope) se refiere a las variables que están disponibles en un bloque de código en un tiempo determinado. La falta de comprensión de este concepto puede llevar a una frustrante experiencia de depuración.
Cuando una variable es declarada dentro de una función utilizando la palabra clave var
, ésta únicamente esta disponible para el código dentro de la función — todo el código fuera de dicha función no puede acceder a la variable. Por otro lado, las funciones definidas dentro de la función podrán acceder a la variable declarada.
Las variables que son declaradas dentro de la función sin la palabra clave var
no quedan dentro del ámbito de la misma función — JavaScript buscará el lugar en donde la variable fue previamente declarada, y en caso de no haber sido declarada, es definida dentro del alcance global, lo cual puede ocasionar consecuencias inesperadas.
Funciones tienen acceso a variables definidas dentro del mismo alcance
var foo = 'hello';
var sayHello = function() {
console.log(foo);
};
sayHello(); // muestra en la consola 'hello'
console.log(foo); // también muestra en la consola 'hello'
El código de afuera no tiene acceso a la variable definida dentro de la función
var sayHello = function() {
var foo = 'hello';
console.log(foo);
};
sayHello(); // muestra en la consola 'hello'
console.log(foo); // no muestra nada en la consola
Variables con nombres iguales pero valores diferentes pueden existir en diferentes alcances
var foo = 'world';
var sayHello = function() {
var foo = 'hello';
console.log(foo);
};
sayHello(); // muestra en la consola 'hello'
console.log(foo); // muestra en la consola 'world'
Las funciones pueden "ver" los cambios en las variables antes de que la función sea definida
var myFunction = function() {
var foo = 'hello';
var myFn = function() {
console.log(foo);
};
foo = 'world';
return myFn;
};
var f = myFunction();
f(); // registra 'world' -- error
Alcance
// una función anónima autoejecutable
(function() {
var baz = 1;
var bim = function() { alert(baz); };
bar = function() { alert(baz); };
})();
console.log(baz); // La consola no muestra nada, ya que baz
// esta definida dentro del alcance de la función anónima
bar(); // bar esta definido fuera de la función anónima
// ya que fue declarada sin la palabra clave var; además,
// como fue definida dentro del mismo alcance que baz,
// se puede consultar el valor de baz a pesar que
// ésta este definida dentro del alcance de la función anónima
bim(); // bim no esta definida para ser accesible fuera de la función anónima,
// por lo cual se mostrará un error
No hay comentarios:
Publicar un comentario