¿Qué es un Objeto?Un conjunto no ordenado de pares clave-valor.
Estos pares son llamados propiedades.Cuando el valor de una propiedad es una función decimos
que es un método.
[[Prototype]]
Propiedad interna que apunta al prototipo del objeto. Algunas implementaciones proveen la propiedad __proto__ (no-standard) como referencia explícita.
obj.__proto__> Object.prototype
Esto nos da acceso a las propiedades y métodos definidos en Object.prototype:
obj.toString();obj.hasOwnProperty();
etc..
Prototype ChainEs el mecanismo de herencia en JavaScript.
Cuando le pedimos una propiedad a un objeto, se busca primero entre las propiedades del objeto y luego si no existe, se sigue la cadena de
[[Prototype]] hasta encontrarlo.
ClasesLos objetos se crean mediante constructores.
Un constructor es una función que va a ser llamada con new.El constructor tiene asociado un objeto llamado prototype.El prototype se utiliza para definir propiedades de instancia.
Por defecto, el prototype es una instancia de Object, por lo tanto, su [[Prototype]] apunta al Object.prototype.
// Constructorfunction MyClass (msg) { this.msg = msg;};
// Instance methodMyClass.prototype.foo = function () { console.log(this.msg);};
var a = new MyClass(‘Hello!’);
a.foo(); // Hello!
MyClass.prototype
foo()
[[Prototype]]
msg
a
[[Prototype]]
[[Prototype]]
Object.prototype
null
...
Herencia por PrototiposLa herencia se obtiene encadenando el prototype de
la clase con una instancia de la clase Padre.
// Constructorfunction ClassA () { // call parent constructor MyClass.apply(this, arguments);};
// inherit from MyClassClassA.prototype = Object.create(MyClass.prototype);
// Instance methodClassA.prototype.bar = function () { console.log(this.msg + ‘ from bar’);};
var obj = new ClassA(‘Hello!’);obj.foo(); // Hello!obj.bar(); // Hello! from bar
MyClass.prototype
foo()
ClassA.prototype
bar()
[[Prototype]]
[[Prototype]]
msg
obj
No tan rápido.Hay algunos pequeños detalles que debemos tener
en cuenta, en especial al definir “propiedades”
// Constructorfunction MyClass () {};
MyClass.prototype.array = [];
// Instance methodMyClass.prototype.foo = function () { console.log(this.array);};
var a = new MyClass();var b = new MyClass();
a.foo(); // []
b.array.push(1);b.array.push(2);a.array.push(3);
a.foo(); // [1,2,3] ???b.foo(); // [1,2,3] ???
a
[[Prototype]]
MyClass.prototype
foo fn()
array [1,2,3]
[[Prototype]]
> Object.protoype
b
[[Prototype]]
¿Entonces?No definan propiedades en el prototype.
Usen el constructor. Después de todo, es el encargado de inicializar el objeto.
// Constructorfunction MyClass () { this.array = [];};
// Instance methodMyClass.prototype.foo = function () { console.log(this.array);};
var a = new MyClass();var b = new MyClass();
a.foo(); // []
b.array.push(1);b.array.push(2);a.array.push(3);
a.foo(); // [3]b.foo(); // [1,2]
a
array [3]
[[Prototype]]
MyClass.prototype
foo fn()
[[Prototype]]
> Object.protoype
b
array [1,2]
[[Prototype]]
“Using inheritance as a vehicle to reuse code is a bit like ordering a
happy meal because you want the plastic toy”
Angus Croll.
*Abstract*Class / *Base*ClassUsualmente cuando utilizamos algún framework que provee clases de las cuales tenemos que extender (por ej. Views, Components, Controllers, etc.) y nos encontramos código que se repite entre las subclases.
Problemas• Agregamos niveles de herencia.• La clase Base/Abstracta se vuelve una
bolsa de funciones comunes.• Las subclases acceden a mas funcionalidad
de la realmente requerida.• Refactorizar se vuelve mas complejo.
… pero el código se vuelve verborrágicovar instance = { msg: ‘Hello from instance’};
//borrowing foo method from MyClassMyClass.prototype.foo.apply(instance); // Hello from instance
//borrowing bar method from ClassAClassA.prototype.bar.apply(instance); // Hello from instance from bar
//borrowing foo method from ClassA inherited from MyClassClassA.prototype.foo.apply(instance); // Hello from instance
¿Qué es un Mixin?Un Mixin es una clase que, por lo general, no está pensada para ser instanciada o extendida, sino combinada en otra clase.
El Mixin es combinado en la clase a través de un merge.
En JavaScript, podemos usar Objects como Mixins.
Mixins: BeneficiosIncentivan la reutilización de código.Pueden ser utilizados como una solución alternativa a la herencia múltiple.Evitan la ambigüedad de herencia (The diamond problem) linealmente.
ImplementaciónObject.assign (o polyfill)- El Mixin es combinado en el prototipo de
la Clase.- Un Mixin puede ser una Clase o un Objeto.
function MixedClass () { // call mixin constructor MyClass.apply(this, arguments);};
// apply MixinObject.assign(MixedClass.prototype, MyClass.prototype);
// Instance methodMixedClass.prototype.bar = function () { console.log(this.msg + ‘ from bar’);};
var obj = new MixedClass(‘Hello!’);
obj.foo(); // Hello!obj.bar(); // Hello! from bar
MixedClass.prototype
foo()
[[Prototype]]
msg
obj
[[Prototype]]
[[Prototype]]
Object.prototype
null
...
bar()
EventEmitter como Mixinfunction MyClass() { // call Mixin constructor EventEmitter.call(this, arguments);}Object.assign(MyClass.prototype, EventEmitter.prototype);
var obj = new MyClass();
obj.on(‘event’, function ( ) { console.log(‘event!’); });obj.emit(‘event’);
Functional MixinsEl Mixin no es una clase sino una función que decora el
prototipo a través de “this”.
// Functional Mixinfunction WithFoo() {
this.foo = function () { console.log(this.msg); } return this;}
// MixedClass definitionfunction MixedClass(msg) { this.msg = msg;}
// Apply MixinWithFoo.call(MixedClass.prototype);
MixedClass.prototype.bar = function () { console.log(this.msg + ‘ from bar’);};
var obj = new MixedClass(‘Hello!’);
obj.foo(); // Hello!obj.bar(); // Hello! from bar
The Diamond ProblemBaseClass
ClassA ClassB
DerivedClass
foo()
var d = new DerivedClass();
d.foo() // ¿¿ cuál foo ??
...
...
...foo()
The Diamond ProblemObject.assign(DerivedClass.prototype, ClassA.prototype, ClassB.prototype);
var d = new DerivedClass();
d.foo() // from ClassB
Object.assign(DerivedClass.prototype, ClassB.prototype, ClassA.prototype);
var d = new DerivedClass();
d.foo() // from ClassA
Llamar a un Método Sobre-escrito….Object.assign(DerivedClass.prototype, ClassA.prototype, ClassB.prototype);
DerivedClass.prototype.foo = function() { // call “overridden” foo from A ClassA.prototype.foo.apply(this, arguments);}
ProblemasMétodos y propiedades son sobre-escritos basados en la posición de la declaración.
Refactoring (agregar/renombrar métodos) puede causar “problemas silenciosos”.
Llamar a un metodo sobre-escrito es verborragico y propenso a errores.
¿Qué es un Trait?Unidades de comportamiento que se pueden componer.Un tipo especial de clases sin estado.Se los puede ver como clases incompletas.Definen comportamiento y acceden al estado a través de métodos requeridos.
Componiendo ComportamientoPuede requerir un conjunto de métodos que servirán como parámetros del comportamiento ofrecido. (Clase incompleta)
Pueden ser compuestos por otros traits.
La colisión de nombres debe ser resuelta por el desarrollador (usando alias y exclusiones)
Resolución de ConflictosUn Conflicto aparece cuando combinamos dos o mas traits que provean métodos llamados idénticamente que no se originen en el mismo trait.Alias:Un método conflictivo se puede “renombrar".Exclusión: Podemos resolver el conflicto excluyendo el método en cuestión.
MyList.prototype
+getCollection()
[[Prototype]]
withFirst <Trait>
first()
*getCollection()
withLast <Trait>
last()
*getCollection()
withIterator <Trait>
iterator()
*getCollection()
@traits
[[Prototype]]
collection: [1,2,3,4,5]
list
var list = new MyList([1,2,3,4,5]);
console.log('collection: ', list.getCollection()); // [1,2,3,4,5]
console.log('first: ', list.first()); // 1console.log('last: ', list.last()); // 5
console.log('iterate:');
var iterator = list.iterator();var value;
while(value = iterator.next()){ console.log(value);}
(*) Required Method(+) Glue Code
MyList.prototype
getCollection()
[[Prototype]]
first()
last()
iterator()
[[Prototype]]
collection: [1,2,3,4,5]
list
MyList.prototype
getCollection(): {Array}
[[Prototype]]
withFirst <Trait>
first()
*getCollection()
withLast <Trait>
last()
*getCollection()
withIterator <Trait>
iterator()
*getCollection()
@traits
[[Prototype]]
collection: [1,2,3,4,5]
list
var list = new MyList([1,2,3,4,5]);
console.log('collection: ', list.getCollection()); // [1,2,3,4,5]
console.log('first: ', list.first()); // 1console.log('last: ', list.last()); // 5
console.log('iterate:');
var iterator = list.iterator();var value;
while(value = iterator.next()){ console.log(value);}
iterable <Trait>
MyList.prototype
getCollection(): {Array}
[[Prototype]]
withFirst <Trait>
first()
*getCollection()
withLast <Trait>
last()
*getCollection()
withIterator <Trait>
iterator()
*getCollection()
@traits
[[Prototype]]
collection: [1,2,3,4,5]
list
ERROR! method “first” is defined twice!
iterable <Trait>
first()
Resolución de Conflictos:Alias o Exclude
Podemos usar exclude en “first” del Trait iterable o alias para renombrarlo en caso que queramos
usarlo (Una alternativa de llamar al metodo sobre-escrito)