ene 5 2011

Metodos de instancia y de clase

En JavaScript no existen clases, se simulan las clases con objetos funcion que hacen las veces de constructor. Pero a pesar de ello a los desarrolladores backend les sigue gustando usar clases. En general a todo el mundo que venga de otro lenguaje OO le gusta seguir utilizando clases, o algo que las simule, para instanciar los objetos que van a utilizar.
En la documentacion de Prototype JS clasifican los métodos de una “clase” en dos categorías: métodos de instancia () y métodos de clase (). Por ejemplo la clase Element que se usa para trabajar con el DOM.

El caso de jQuery es bastante distinto. JQuery es un único objeto (lo que seria una clase para Prototype) con distintas propiedades. Cuando usamos el selector $, este nos devuelve un objeto instancia de jQuery, que referencia al elemento del DOM que le hallamos indicado. Este objeto instanciado, tiene propiedades comunes al objeto jQuery pero otras muy variadas. Esto es así porque en el objeto instanciado puede contener un array de elementos del DOM, todo el documento (con $(document)… ) o un div creado al vuelo y que no cuelga de ninguna rama del DOM aun; en general cualquier cosa, por eso tiene propiedades varias para trabajar con cualquier elemento posible.

La documentación de jQuery muestra los metodos de dos formas dependiendo de si son de instancia o de clase. Los métodos de clase aparecen con las nomenclatura “jQuery.metodo()” y los de instancia aparecen empezando por un punto: “.metodo()” que son los que normalmente para trabajar de forma ágil se usan del modo: “$(“#id”).metodo()“.

Documentación jQuery Prototype Style

Me preguntaba como sería ver la documentación de jQuery al estilo de la docu de PrototypeJS. En definitiva aparecería una sola clase (jQuery en si) seria algo parecido a lo que sigue:

Propiedades de jQuery: Class Method

prototype fn extend noConflict isReady ready bindReady isFunction
isArray isPlainObject isEmptyObject error parseJSON noop globalEval nodeName
each trim makeArray inArray merge grep map guid
proxy uaMatch browser support props cache expando noData
data removeData queue dequeue attrFn attr event Event
find expr unique text isXMLDoc contains filter dir
nth sibling fragments clean cleanData style css curCSS
swap get getScript getJSON post ajaxSetup ajaxSettings lastModified
etag ajax handleError active httpSuccess httpNotModified httpData param
speed easing timers fx offset

Propiedades del prototipo de jQuery (objeto jQuery): Instance Methods

init selector jquery length size toArray get pushStack
each ready eq first last slice map end
push sort splice extend data removeData queue dequeue
delay clearQueue attr removeAttr addClass removeClass toggleClass hasClass
val bind one unbind delegate undelegate trigger triggerHandler
toggle hover live die blur focus focusin focusout
load resize scroll unload click dblclick mousedown mouseup
mousemove mouseover mouseout mouseenter mouseleave change select submit
keydown keypress keyup error find has not filter
is closest index add andSelf parent parents parentsUntil
next prev nextAll prevAll nextUntil prevUntil siblings children
contents text wrapAll wrapInner wrap unwrap append prepend
before after remove empty clone html replaceWith detach
domManip appendTo prependTo insertBefore insertAfter replaceAll css serialize
serializeArray ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend show
hide _toggle fadeTo animate stop slideDown slideUp slideToggle
fadeIn fadeOut offset position offsetParent scrollLeft scrollTop innerHeight
outerHeight height innerWidth outerWidth width
*si el objeto es un elemento del DOM también aparecerá en esta lista la propiedad ‘context’.

Propiedades Comunes de jQuery y de su prototipo.

En general los métodos de instancia y de clase comunes son los siguientes:

extend ready error each map data removeData queue
dequeue attr find text filter css get offset

En este articulo no me voy a meter con la diferencia entre una librería y otra, eso ya quedaría para otro momento. Pero en cuanto a documentaciones se refiere me gusta bastante el estilo claro de la de Prototype. Aunque ya hace tiempo que me acostumbré a buscar en la documentación de jQuery, veo que no pueden hacer toda la documentación en una sola página. Su documentación sigue la misma filosofía que la librería pero puede ser confuso para quienes empiezan a trabajar con ella, y todo viene de que hay que cambiar la forma de pensar cuando se cambia de lenguaje. JavaScript is diferent. XD


dic 28 2010

Dos tipos de herencia en JavaScript

JavaScript arrastra con sigo una gran parte de sintaxis de los lenguajes clasicos orientados a objetos, sin embargo la orientación a objetos de JS es un tanto distinta, ya que aunque se puede considerar que todo son objetos como en Java, en JS no existen las clases, lo cual los distancia bastante.
Una parte muy confusa es la herencia entre objetos, no entre clase porque que no existen, y que un objeto herede de otro es un tanto extraño.
Para empezar, como deciamos, todo son objetos incluidas las funciones. Las funciones son objetos ejecutables, y se pueden ejecutar de dos formas, de la forma tradicional o con la instruccion ‘new’. De la manera tradicional la funcion devuelve lo que devuelva mediente return. Con ‘new’ la funcion se puede ver como un constructor (de hecho lo es) que devuelve un objeto al que se puede hacerse referencia dentro de la propia funcion con ‘this’; en este caso el ‘return’ es implicito y no hace falta que aparezca la instruccion ‘return this’.

function A(){
 this.nombre="aaaaaa";
 this.apellido="111111";
}

var aa=new A();

aa instanceof A;//true
aa.nombre;//aaaaaa
delete aa.nombre;
aa instanceof A;//true
aa.nombre;//undefined

Para implementar herencia entre objetos la primera instrucción que debe aparecer en el constructor es la llamada al constructor del padre. Esto se conoce como alquilar el constructor.

Podemos imvocar cualquier funcion con los métodos ‘call‘ o ‘apply‘.
A parte de las diferencias de rendimiento entre uno y otro en los distintos navegadores, lo que distingue a estos dos métodos es que ‘apply‘ debe ser invocado con los todos parámetros dentro de un array; en ‘call’ los parámetros pueden aparecer del mismo modo que en la llamada normal a la funcion.
En los dos métodos el primer argumento debe ser el objeto al que se aplica la funcion. En nuestro caso el objeto es ‘this‘ ya que desde nuestro constructor invocamos al constructor de otro objeto.

function B(){
A.apply(this,arguments);
this.nombre="bbbbbb";
this.apellido="222222";
}

Como el prototipo de la funcion puede ser extendido fuera de esta debemos de añadir todas sus propiedades al prototipo del objeto heredero. Esto se puede hacer de varias formas pero las dos más interesantes son las siguientes:
B.prototype=new A()  vs.  B.prototype=A.prototype
Con cualquiera de las dos elecciones el metodo ‘instanceOf’ de un objeto creado mediante ‘new B()’ nos devoveria verdadero para el objeto original y para el heredado, pero a parte de esto el resultado interno no es el mismo en los dos casos.
B.prototype=new A()
En este caso hay que tener en cuenta que el prototype de B es una instancia de A, y que su constructor ha sido ejecutado no solo en el constructor sino tambien para crear el prototipo de manera que si una propiedad o funcion no existe o es borrada en el objeto hijo, puede aun existiendo en la clase padre si se definió en su constructor. Esto es así porque al no encontrar cierta propiedad la maquina virtual de JavaScritp continua la cadena de prototipos para encontrarla antes de devolver un error y terminar con la ejecucion bruscamente. No siempre esto es malo, en ocasiones preferimos que las propiedades repetidas para usar la del padre en caso que quisieramos. Solo hay que tenerlo en cuenta

B.prototype=new A();
var bb=new B();

La propiedad ‘__proto__’, o lo que es lo mismo el prototype del constructor de bb (bb.constructor.prototype === bb.__proto__), está apuntando a una instancia del objeto A, la cual apuntará a través de su constructor al metodo ‘A’, se van generando dos saltos en cada herencia entre objetos.
En este caso sucede que:

bb.constructor.prototype === bb.__proto__ //false

bb.constructor.prototype === A.prototype //true
//pero
bb.__proto__ === A.prototype //false

B.prototype=A.prototype;

B.prototype=A.prototype;
var bb=new B();

Aquí la propiedad ‘__proto__’ del objeto bb apunta directamente a la funcion ‘A’, y no a un objeto instanciado de esta.

bb.constructor.prototype === bb.__proto__ //true
bb.constructor.prototype === A.prototype //true
bb.__proto__ === A.prototype //true

En este caso hay que tener en cuenta que el prototype de B el prototype A, por lo tanto una vez eliminado un elemento o funcion de nuestro objeto, quedará borrado para siempre y su lectura devolveria ‘undefined’.

B.prototype=new A();

bb instanceof A; bb instanceof B;// true true
bb.nombre;//bbbbbb
delete bb.nombre;
bb instanceof A; bb instanceof B;// true true
bb.nombre;//aaaaaa
delete bb.nombre;
delete bb.nombre;
bb instanceof A; bb instanceof B;// true true
bb.nombre;//aaaaaa

Más grados de herencia
¿Que pasa con la jerarquía de herencia?. Hay muchas maneras de crear herencia a partir de estas dos básicas. Uno de los patrones de herencia intenta crear un objeto intermedio entre el objeto hijo y el objeto padre.
En el siguiente ejemplo el objeto C utiliza como prototipo intermedio al objeto BtoC, de esta manera aunque hagamos un “delete C.nombre” los objetos creados a partir de un “new C()” seguiran teniendo la propiedad “nombre” de B.

function BtoC(){
 B.apply(this,arguments);
};
BtoC.prototype=new B();

function C(){
 BtoC.apply(this,arguments);
 this.nombre="cccccc";
}
C.prototype=BtoC.prototype;

var cc=new C();

cc instanceof A; cc instanceof B; cc instanceof C;// true true true
cc.nombre; cc.apellido; //cccccc 222222
delete cc.nombre; delete cc.apellido;
cc instanceof A; cc instanceof B; cc instanceof C; // true true true
cc.nombre; cc.apellido; //aaaaaa 111111
delete cc.nombre;delete cc.apellido;

Podemos decir que si utilizamos “prototype=new Clase()” rompemos la cadena de busqueda por los prototipos de la maquina virtual de JS. Los objetos en sí no tienen la propiedad prototype, por defecto solo la tienen los objetos Function. Así que emplear un tipo de herencia u otra depende de nuestros propios objetivos.

En mozilla proponen la herencia con la extensión de propiedades de un prototipo con las de otro, en definitiva es la misma extensión de un objeto con otro:

function extend(child, super){
 for (var property in super.prototype) {
  if (typeof child.prototype[property] == "undefined")
   child.prototype[property] = super.prototype[property];
  }
 return child;
}

https://developer.mozilla.org/en/JavaScript/Guide/Inheritance_Revisited
La extensión aunque permite tranferir las propiedades de un constructor a otro no nos es útil si en nuestro código vamos a utilizar el metodo “instanceOf” para verificar que nuestro objeto tiene ciertas propiedades por defecto. En el caso de este tipo de herencia habria que utilizar “propiedad in objeto” para comprobar que existe.

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/prototype
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf
Javascript Patterns. Stoyan Stefanov.