Los fundamentos de JavaScript orientado a objetos
Spanish (Español) translation by Claudia Márquez (you can also view the original English article)
En los últimos años, JavaScript ha ganado cada vez más popularidad, en parte debido a las bibliotecas que se han desarrollado para hacer que las aplicaciones / efectos de JavaScript sean más fáciles de crear para aquellos que aún no han comprendido el lenguaje principal.
Mientras que en el pasado, era un argumento común de que JavaScript era un lenguaje básico y era muy simple y no tenía una base real; este ya no es el caso, especialmente con la introducción de aplicaciones web de gran escala y 'adaptaciones' como JSON (Notación de objetos de JavaScript).
JavaScript puede tener todo lo que un lenguaje orientado a objetos tiene que ofrecer, aunque con un esfuerzo adicional fuera del alcance de este artículo.
Vamos a crear un objeto
1 |
function myObject(){ |
2 |
|
3 |
};
|
Enhorabuena, acabas de crear un objeto. Hay dos formas de crear un objeto de JavaScript: son 'Funciones del constructor' y 'Notación literal'. El de arriba es una función de Constructor, explicaré cuál es la diferencia en breve, pero antes de hacerlo, esto es lo que parece una definición de Objeto usando notación literal.
1 |
var myObject = { |
2 |
|
3 |
};
|
Literal es una opción preferida para el espaciado de nombres para que su código JavaScript no interfiera (o viceversa) con otros scripts que se ejecutan en la página y también si está utilizando este objeto como un solo objeto y no requiere más de una instancia de objeto, mientras que la notación de tipo de función Constructor es preferible si necesita realizar un trabajo inicial antes de que se cree el objeto o si requiere varias instancias del objeto en las que se pueda cambiar cada instancia durante la vida útil del script. Continuemos construyendo sobre nuestros dos objetos simultáneamente para que podamos observar cuáles son las diferencias.
Definiendo métodos y propiedades
Versión de constructor:
1 |
function myObject(){ |
2 |
this.iAm = 'an object'; |
3 |
this.whatAmI = function(){ |
4 |
alert('I am ' + this.iAm); |
5 |
};
|
6 |
};
|
Versión literal:
1 |
var myObject = { |
2 |
iAm : 'an object', |
3 |
whatAmI : function(){ |
4 |
alert('I am ' + this.iAm); |
5 |
}
|
6 |
}
|
Para cada uno de los objetos, hemos creado una propiedad 'iAm' que contiene un valor de cadena que se utiliza en nuestro método de objetos 'whatAmI' que alerta un mensaje.
Las propiedades son variables creadas dentro de un objeto y los métodos son funciones creadas dentro de un objeto.
Es probable que ahora sea el mejor momento para explicar cómo usar las propiedades y los métodos (aunque ya lo habría hecho si estuviera familiarizado con una biblioteca).
Para usar una propiedad, primero escribe a qué objeto pertenece (por lo tanto, en este caso es myObject) y luego, para hacer referencia a sus propiedades internas, se pone una parada completa y luego el nombre de la propiedad para que finalmente se vea como myObject.iAm ( esto devolverá 'an object').
Para los métodos, es lo mismo, excepto para ejecutar el método, como con cualquier función, debe poner paréntesis después de él; de lo contrario, solo devolverá una referencia a la función y no a lo que realmente devuelve la función. Así se verá como myObject.whatAmI() (Esto alertará 'Soy un objeto').
Ahora para las diferencias:
- El objeto constructor tiene sus propiedades y métodos definidos con la palabra clave 'this' delante de él, mientras que la versión literal no lo tiene.
- En el objeto constructor, las propiedades / métodos tienen sus 'valores' definidos después de un signo igual '=', mientras que en la versión literal, se definen después de dos puntos ':'.
- La función constructora puede tener puntos y comas (opcionales) ';' al final de cada declaración de propiedad / método, mientras que en la versión literal, si tiene más de una propiedad o método, DEBEN separarse con una coma ',', y NO PUEDEN tener puntos y coma después de ellos, de lo contrario, JavaScript devolverá un error.
También existe una diferencia entre la forma en que se utilizan estos dos tipos de declaraciones de objetos.
Para usar un objeto anotado literalmente, simplemente utilícelo haciendo referencia al nombre de su variable, de modo que cuando sea necesario, puede llamarlo escribiendo;
1 |
myObject.whatAmI(); |
Con las funciones de constructor, debe crear una instancia (crear una nueva instancia de) el objeto primero; usted hace esto escribiendo
1 |
var myNewObject = new myObject(); |
2 |
myNewObject.whatAmI(); |
Usando una función de constructor.
Usemos nuestra función constructora anterior y la construyamos para que realice algunas operaciones básicas (pero dinámicas) cuando la ejemplifiquemos.
1 |
function myObject(){ |
2 |
this.iAm = 'an object'; |
3 |
this.whatAmI = function(){ |
4 |
alert('I am ' + this.iAm); |
5 |
};
|
6 |
};
|
Al igual que cualquier función de JavaScript, podemos usar argumentos con nuestra función de constructor;
1 |
function myObject(what){ |
2 |
this.iAm = what; |
3 |
this.whatAmI = function(language){ |
4 |
alert('I am ' + this.iAm + ' of the ' + language + ' language'); |
5 |
};
|
6 |
};
|
Ahora vamos a crear una instancia de nuestro objeto y llamar a su método whatAmI, completando los campos requeridos mientras lo hacemos.
1 |
var myNewObject = new myObject('an object'); |
2 |
myNewObject.whatAmI('JavaScript'); |
Esto alertará 'Soy un objeto del lenguaje JavaScript'.
Instanciar o no instanciar
Anteriormente mencioné las diferencias entre los constructores de objetos y los literales de objetos y que cuando se realiza un cambio en un objeto literal, afecta a ese objeto en todo el script, mientras que cuando se crea una instancia de la función Constructor y luego se realiza un cambio en esa instancia, No afectará a ninguna otra instancia de ese objeto. Probemos un ejemplo;
Primero crearemos un objeto literal;
1 |
var myObjectLiteral = { |
2 |
myProperty : 'this is a property' |
3 |
}
|
4 |
|
5 |
//alert current myProperty
|
6 |
alert(myObjectLiteral.myProperty); //this will alert 'this is a property' |
7 |
|
8 |
//change myProperty
|
9 |
myObjectLiteral.myProperty = 'this is a new property'; |
10 |
|
11 |
//alert current myProperty
|
12 |
alert(myObjectLiteral.myProperty); //this will alert 'this is a new property', as expected |
Incluso si crea una nueva variable y la apunta hacia el objeto, tendrá el mismo efecto.
1 |
var myObjectLiteral = { |
2 |
myProperty : 'this is a property' |
3 |
}
|
4 |
|
5 |
//alert current myProperty
|
6 |
alert(myObjectLiteral.myProperty); //this will alert 'this is a property' |
7 |
|
8 |
//define new variable with object as value
|
9 |
var sameObject = myObjectLiteral; |
10 |
|
11 |
//change myProperty
|
12 |
myObjectLiteral.myProperty = 'this is a new property'; |
13 |
|
14 |
//alert current myProperty
|
15 |
alert(sameObject.myProperty); //this will still alert 'this is a new property' |
Ahora probemos un ejercicio similar con una función de Constructor.
1 |
//this is one other way of creating a Constructor function
|
2 |
var myObjectConstructor = function(){ |
3 |
this.myProperty = 'this is a property' |
4 |
}
|
5 |
|
6 |
//instantiate our Constructor
|
7 |
var constructorOne = new myObjectConstructor(); |
8 |
|
9 |
//instantiate a second instance of our Constructor
|
10 |
var constructorTwo = new myObjectConstructor(); |
11 |
|
12 |
//alert current myProperty of constructorOne instance
|
13 |
alert(constructorOne.myProperty); //this will alert 'this is a property' |
14 |
|
15 |
//alert current myProperty of constructorTwo instance
|
16 |
alert(constructorTwo.myProperty); //this will alert 'this is a property' |
Entonces, como se esperaba, ambos devuelven el valor correcto, pero cambiemos myProperty para una de las instancias.
1 |
//this is one other way of creating a Constructor function
|
2 |
var myObjectConstructor = function(){ |
3 |
this.myProperty = 'this is a property' |
4 |
}
|
5 |
|
6 |
//instantiate our Constructor
|
7 |
var constructorOne = new myObjectConstructor(); |
8 |
|
9 |
//change myProperty of the first instance
|
10 |
constructorOne.myProperty = 'this is a new property'; |
11 |
|
12 |
//instantiate a second instance of our Constructor
|
13 |
var constructorTwo = new myObjectConstructor(); |
14 |
|
15 |
//alert current myProperty of constructorOne instance
|
16 |
alert(constructorOne.myProperty); //this will alert 'this is a new property' |
17 |
|
18 |
//alert current myProperty of constructorTwo instance
|
19 |
alert(constructorTwo.myProperty); //this will still alert 'this is a property' |
Como puede ver en este ejemplo, aunque cambiamos la propiedad de constructorOne, no afectó a myObjectConstructor y, por lo tanto, no afectó a constructorTwo. Incluso si se creara una instancia de constructorTwo antes de que cambiáramos la propiedad myProperty de constructorOne, no afectaría a la propiedad myProperty de constructorTwo, ya que es una instancia completamente diferente del objeto dentro de la memoria de JavaScript.
Entonces, ¿cuál debería usar? Bueno, depende de la situación, si solo necesita un objeto de su tipo para su script (como verá en nuestro ejemplo al final de este artículo), use un objeto literal, pero si necesita varias instancias de un objeto , donde cada instancia es independiente de la otra y puede tener diferentes propiedades o métodos dependiendo de la forma en que se construye, luego use una función constructora.
Esto y aquello
Mientras explicaba las funciones de los constructores, había muchas palabras clave de 'esto' que se lanzaban por ahí y me imagino qué mejor momento para hablar sobre el alcance.
Ahora puede que se pregunte "¿de qué alcance habla?" El alcance en JavaScript se basa en la función / objeto, por lo que significa que si está fuera de una función, no puede usar una variable que esté definida dentro de una función (a menos que use un cierre).
Sin embargo, hay una cadena de alcance, lo que significa que una función dentro de otra función puede acceder a una variable definida en su función principal. Echemos un vistazo a un código de ejemplo.
1 |
<script type="text/javascript"> |
2 |
|
3 |
var var1 = 'this is global and is available to everyone'; |
4 |
|
5 |
function function1(){ |
6 |
|
7 |
var var2 = 'this is only available inside function1 and function2'; |
8 |
|
9 |
function function2(){ |
10 |
|
11 |
var var3 = 'this is only available inside function2'; |
12 |
|
13 |
}
|
14 |
|
15 |
}
|
16 |
|
17 |
</script> |
Como puede ver en este ejemplo, var1
se define en el objeto global y está disponible para todas las funciones y el objeto, var2
se define dentro de function1 y está disponible para function1 y function2, pero si intenta hacer referencia a él desde el objeto global, le proporciona el error 'var2 no está definido', var3
solo es accesible para function2.
Entonces, ¿qué hace referencia a 'esto'? Bueno, en un navegador, 'este' hace referencia al objeto de la ventana, por lo que técnicamente la ventana es nuestro objeto global. Si estamos dentro de un objeto, 'esto' se referirá al objeto en sí mismo; sin embargo, si está dentro de una función, esto también se referirá al objeto de la ventana y de la misma manera si está dentro de un método que está dentro de un objeto ' esto 'se referirá al objeto.
Debido a nuestra cadena de alcance, si estamos dentro de un subobjeto (un objeto dentro de un objeto), 'esto' se referirá al subobjeto y no al objeto principal.
Como nota al margen, también vale la pena agregar que cuando se usan funciones como setInterval, setTimeout y eval, cuando se ejecuta una función o método a través de uno de estos, 'this' se refiere al objeto de la ventana, ya que estos son métodos de ventana, por lo que setInterval() y window.setInterval() son los mismos.
Ok, ahora que lo hemos dejado de lado, ¡hagamos un ejemplo del mundo real y creamos un objeto de validación de formularios!
Uso en el mundo real: un objeto de validación de formulario
Primero debo presentarle la función addEvent que crearemos y es una combinación de la función addEventListener() de ECMAScript (Firefox, Safari, etc.) y la función attachEvent() de Microsoft ActiveX Script.
1 |
function addEvent(to, type, fn){ |
2 |
if(document.addEventListener){ |
3 |
to.addEventListener(type, fn, false); |
4 |
} else if(document.attachEvent){ |
5 |
to.attachEvent('on'+type, fn); |
6 |
} else { |
7 |
to['on'+type] = fn; |
8 |
}
|
9 |
};
|
Esto crea una nueva función con tres argumentos, al ser el objeto DOM to
al que estamos adjuntando el evento, el type
es el tipo de evento y fn
la función que se ejecuta cuando se activa el evento. Primero verifica si se admite addEventListener; si es así, usará eso; si no lo hará, buscará attachEvent y si todo lo demás falla, probablemente esté usando IE5 o algo igualmente obsoleto, así que agregaremos el evento directamente a su propiedad de evento (nota: la tercera opción sobrescribirá cualquier función existente que se haya adjuntado a la propiedad del evento, mientras que las dos primeras la agregarán como una función adicional a su propiedad del evento).
Ahora configuremos nuestro documento para que sea similar a lo que podrías ver cuando desarrolles jQuery.
En jQuery tendrías;
1 |
$(document).ready(function(){ |
2 |
//all our code that runs after the page is ready goes here
|
3 |
});
|
Usando nuestra función addEvent tenemos;
1 |
addEvent(window, 'load', function(){ |
2 |
//all our code that runs after the page is ready goes here
|
3 |
});
|
Ahora para nuestro objeto Form.
1 |
var Form = { |
2 |
|
3 |
validClass : 'valid', |
4 |
|
5 |
fname : { |
6 |
minLength : 1, |
7 |
maxLength : 15, |
8 |
fieldName : 'First Name' |
9 |
},
|
10 |
|
11 |
lname : { |
12 |
minLength : 1, |
13 |
maxLength : 25, |
14 |
fieldName : 'Last Name' |
15 |
},
|
16 |
|
17 |
|
18 |
validateLength : function(formEl, type){ |
19 |
if(formEl.value.length > type.maxLength || formEl.value.length < type.minLength ){ |
20 |
formEl.className = formEl.className.replace(' '+Form.validClass, ''); |
21 |
return false; |
22 |
} else { |
23 |
if(formEl.className.indexOf(' '+Form.validClass) == -1) |
24 |
formEl.className += ' '+Form.validClass; |
25 |
return true; |
26 |
}
|
27 |
},
|
28 |
|
29 |
|
30 |
validateEmail : function(formEl){ |
31 |
var regEx = /^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$/; |
32 |
var emailTest = regEx.test(formEl.value); |
33 |
if (emailTest) { |
34 |
if(formEl.className.indexOf(' '+Form.validClass) == -1) |
35 |
formEl.className += ' '+Form.validClass; |
36 |
return true; |
37 |
} else { |
38 |
formEl.className = formEl.className.replace(' '+Form.validClass, ''); |
39 |
return false; |
40 |
}
|
41 |
},
|
42 |
|
43 |
getSubmit : function(formID){ |
44 |
var inputs = document.getElementById(formID).getElementsByTagName('input'); |
45 |
for(var i = 0; i < inputs.length; i++){ |
46 |
if(inputs[i].type == 'submit'){ |
47 |
return inputs[i]; |
48 |
}
|
49 |
}
|
50 |
return false; |
51 |
}
|
52 |
|
53 |
};
|
Así que esto es bastante básico pero se puede ampliar fácilmente.
Para desglosar esto primero, creamos una nueva propiedad que es solo el nombre de la cadena de nuestra clase css 'válida' que, cuando se aplica al campo de formulario, agrega efectos válidos, como un borde verde. También definimos nuestros dos subobjetos, fname
y lname
, por lo que podemos definir sus propias propiedades que se pueden usar con otros métodos, estas propiedades son minLength
, que es la cantidad mínima de caracteres que estos campos pueden tener, maxLength
que es el máximo de caracteres el campo puede tener un fieldName
que en realidad no se usa, pero se puede usar para identificar el campo con una cadena fácil de usar en un mensaje de error (por ejemplo, 'Se requiere el nombre del campo').
A continuación, creamos un método validateLength que acepta dos argumentos: formEl
el elemento DOM para validar y el type
que se refiere a uno de los subobjetos a utilizar (es decir, fname o lname). Esta función verifica si la longitud del campo se encuentra entre el rango minLength y maxLength, si no es así, eliminamos nuestra clase válida (si existe) del elemento y devolvemos false, de lo contrario, si es así, agregamos la clase válida y devolvemos cierto.
Luego tenemos un método validateEmail que acepta un elemento DOM como argumento, luego probamos este valor de elementos DOM contra una expresión regular de tipo de correo electrónico; de nuevo si pasa agregamos nuestra clase y devolvemos true y viceversa.
Finalmente tenemos un método getSubmit. Este método recibe el ID del formulario y luego recorre todos los elementos de entrada dentro del formulario especificado para encontrar cuál tiene un tipo de envío (type = "submit"). El motivo de este método es devolver el botón de envío para que podamos deshabilitarlo hasta que el formulario esté listo para enviar.
Pongamos este objeto validador para trabajar en una forma real. Primero necesitamos nuestro HTML.
1 |
<body> |
2 |
|
3 |
<form id="ourForm"> |
4 |
<label>First Name</label><input type="text" /><br /> |
5 |
<label>Last Name</label><input type="text" /><br /> |
6 |
<label>Email</label><input type="text" /><br /> |
7 |
<input type="submit" value="submit" /> |
8 |
</form> |
9 |
|
10 |
</body> |
Ahora accedamos a estos objetos de entrada utilizando JavaScript y validémoslos cuando se envíe el formulario.
1 |
addEvent(window, 'load', function(){ |
2 |
|
3 |
|
4 |
var ourForm = document.getElementById('ourForm'); |
5 |
var submit_button = Form.getSubmit('ourForm'); |
6 |
submit_button.disabled = 'disabled'; |
7 |
|
8 |
function checkForm(){ |
9 |
var inputs = ourForm.getElementsByTagName('input'); |
10 |
if(Form.validateLength(inputs[0], Form.fname)){ |
11 |
if(Form.validateLength(inputs[1], Form.lname)){ |
12 |
if(Form.validateEmail(inputs[2])){ |
13 |
|
14 |
submit_button.disabled = false; |
15 |
return true; |
16 |
|
17 |
}
|
18 |
}
|
19 |
}
|
20 |
|
21 |
submit_button.disabled = 'disabled'; |
22 |
return false; |
23 |
|
24 |
};
|
25 |
|
26 |
checkForm(); |
27 |
addEvent(ourForm, 'keyup', checkForm); |
28 |
addEvent(ourForm, 'submit', checkForm); |
29 |
|
30 |
|
31 |
});
|
Vamos a desglosar este código.
Envolvemos nuestro código en la función addEvent para que, cuando se cargue la ventana, se ejecute este script. En primer lugar, tomamos nuestro formulario con su ID y lo colocamos en una variable llamada ourForm
, luego agarramos nuestro botón de envío (usando el método getSubmit de nuestros objetos de Formulario) y lo colocamos en una variable llamada submit_button
, y luego configuramos el atributo de los botones de envío deshabilitado en ' discapacitado'.
A continuación definimos una función checkForm. Esto almacena todas las entradas dentro del campo de formulario como una matriz y lo adjuntamos a una variable llamada .. lo adivinaste ... inputs
! Luego define algunas declaraciones anidadas if que prueban cada uno de los campos dentro de la matriz de entradas en comparación con nuestros métodos de formulario. Esta es la razón por la que devolvimos verdadero o falso en nuestros métodos, por lo que si devuelve verdadero, pasamos esa sentencia if y continuamos con el siguiente, pero si devuelve falso, salimos de las sentencias if.
Siguiendo la definición de nuestra función, ejecutamos la función checkForm cuando la página se carga inicialmente y también adjuntamos la función a un evento keyup y un evento submit.
Podría estar preguntando, ¿por qué adjuntar para enviar si deshabilitamos el botón de enviar? Bueno, si se enfoca en un campo de entrada y presiona la tecla Intro, intentará enviar el formulario y debemos probar esto, por lo tanto, la razón por la que nuestra función checkForm devuelve true (envía el formulario) o false (no envía formar).
Conclusión
Así que aprendimos cómo definir los diferentes tipos de objetos dentro de JavaScript y crear propiedades y métodos dentro de ellos. También aprendimos una ingeniosa función addEvent y pudimos usar nuestro objeto en un ejemplo básico del mundo real.
Esto concluye los conceptos básicos de la orientación a objetos de JavaScript. ¡Esperamos que esto te ayude a construir tu propia biblioteca de JavaScript! Si le ha gustado este artículo y está interesado en otros temas relacionados con JavaScript, publíquelos en los comentarios, ya que me encantaría seguir escribiéndolos. Gracias por leerme.