que - (Fuente abierta) Ejemplos de JavaScript Prototypical OO
llamar javascript desde html (8)
Edición de recompensas:
Estoy buscando un código escrito en un paradigma de OO prototípico puro (piense en el Yo). No es una mezcla de OO prototípica y OO clásica. No quiero ver envoltorios OO genéricos sino simplemente el uso de técnicas de OO prototípicas y solo técnicas de OO prototípicas.
Pregunta relacionada de referencia:
En la pregunta anterior me centré principalmente en
¿Se puede escribir OO prototípico así?
¿Necesitamos constructores y lógica de inicialización, cuáles son las alternativas?
Nueva pregunta:
Básicamente, ¿hay buenos ejemplos de OO prototípico de javascript en grandes proyectos de código abierto?
Aclaración:
Tendré que aclarar a qué me refiero con OO prototípico :
- No hay clases. Solo hay objetos.
- No existe una emulación de los conceptos de las clases, de nuevo , solo hay objetos y objetos clonados para crear nuevos objetos.
Aclaración adicional de la OO prototípica:
La diferencia entre la OO prototípica en JavaScript y la emulación de OO clásica es un área muy gris . No es que valore evitar el OO clásico. Quiero aprender la OO prototípica de manera académica por sí misma, sin aprender la combinación (probablemente más óptima) de la emulación de OO clásica y la OO prototípica.
Esta es la razón por la que "baneo" las clases, solo para poder ver estas técnicas de manera pura y extender mi propio kit de herramientas de OO.
Ejemplos:
Ejemplos populares como jQuery no cumplen con el segundo criterio. El objeto jQuery
es una gran emulación de clase. Se centra en crear nuevos objetos de una clase en lugar de clonar objetos existentes.
Si realmente supiera algún ejemplo de uso de OO prototípico "puro", te habría mostrado. Creo que el 99% de JavaScript OO está demasiado influenciado por la emulación clásica.
Puntos extra
Si
- Está bien documentado / documentado
- Tiene pruebas unitarias
- Está en github.
También aceptaré artículos / tutoriales y ejemplos sobre cómo escribir código OO prototípico que vaya más allá de su aplicación trivial hello world.
¿Has echado un vistazo a OMeta / JS? OMeta es un lenguaje experimental de comparación de patrones basado en Smalltalk y Self. OMeta / JS es una implementación en javascript que usa OO prototípico.
Está bien comentado y documentado con muchos ejemplos. También está en Github.
http://tinlizzie.org/ometa-js/
https://github.com/alexwarth/ometa-js
Edición: OMeta es el resultado de la tesis doctoral de Alexander Warth.
Actualmente estoy usando un modelo de complemento de herencia que intenta combinar el patrón OO prototípico con el patrón de complemento jQuery. Se publica en detalle en mi respuesta aquí: adjuntar una clase a un objeto jQuery
Nota: No se desanime al mencionar la palabra Class
En mi marco, todo es un objeto o una "interfaz".
Las interfaces definen las funciones comunes (métodos / propiedad_gets / propiedad_sets) que pueden tener los objetos.
Crea una interfaz como esta: var some_interface = GetInterface(constructor, interface_setup, parent_interface..)
Puede especificar cualquier número de interfaces de padres. Así que si interface_A
hereda tanto interface_B
como interface_C
, puede crear interface_A como tal: GetInterface(constructor, interface_setup, interface_B, interface_C);
Si interface_A
hereda interface_X
e interface_X
hereda interface_Y
, entonces interface_A
tendrá todas las funciones que tienen tanto interface_X
como interface_Y
.
Las interfaces que no requieren un constructor dejarán el argumento del constructor como nulo. El interface_setup es una función que se parece a esto:
function(proto){
}
El objeto al que apunta el argumento proto tiene 4 métodos: SetM
, ShadowM
, SetP
y ShadowP
.
Utiliza estos 4 métodos para configurar su interfaz.
Este marco también proporciona una instanciación perezosa de interfaces. (en otras palabras, el código de configuración nunca se ejecutará hasta que realmente se necesite primero).
Las limitaciones de este marco requieren al menos soporte para Object.keys
, Object.getOwnPropertyDescriptor
y Object.defineProperty
. (En otras palabras, funciona en las últimas versiones de FireFox, IE, Chrome, Safari pero no Opera)
TestPage2.html:
<!doctype html>
<script src="js.js"></script>
<script>
var human = GetInterface(function(){
},function(proto){
//alert("trace: initing human");
proto.$SetM("Sleep",function(){
alert(this.Name+" is sleeping");
});
proto.$SetP("Name",function(){
return this._name;
},function(value){
this._name=value;
});
});
var female = GetInterface(function(){
},function(proto){
//alert("trace: initing female");
proto.$SetM("Dance",function(){
alert(this.Name+" is dancing");
});
},human);
var male = GetInterface(function(){
},function(proto){
//alert("trace: initing male");
proto.$SetM("Fight",function(){
alert(this.Name+" is fighting");
});
proto.$ShadowP("Name",function(parent_get){
return "Mr. "+parent_get();
},function(parent_set,value){
parent_set(value);
});
},human);
var child = GetInterface(function(){
},function(proto){
//alert("trace: initing child");
proto.$SetM("Play",function(){
alert(this.Name+" is playing");
});
},human);
var adult = GetInterface(function(){
},function(proto){
//alert("trace: initing adult");
proto.$SetM("Work",function(){
alert(this.Name+" is working");
});
},human);
var mammal = GetInterface(function(){
},function(proto){
//alert("trace: initing mammal");
proto.$SetM("DoMammalStuff",function(){
alert("doing mammal stuff");
});
});
var john=new male();
john.Name="john";
john.Sleep();
var mary=new female();
mary.$IsA(child);
mary.$IsA(mammal);
mary.$Setup(function(proto){
proto.$ShadowP("Name",function(parent_get){
return "Miss "+parent_get.call(this);
},function(parent_set,value){
parent_set.call(this,value);
});
});
mary.Name="mary";
mary.Play();
</script>
TestPage.html:
<!doctype html>
<script src="js.js"></script>
<script>
var human_interface = GetInterface(function(){
},function(proto){
alert("trace: initing human");
proto.$SetM("Sleep",function(){
alert(this.Name+" is sleeping");
});
proto.$SetP("Name",function(){
return this._name;
},function(value){
this._name=value;
});
});
var female_interface = GetInterface(function(){
},function(proto){
alert("trace: initing female");
proto.$SetM("Dance",function(){
alert(this.Name+" is dancing");
});
},human_interface);
var male_interface = GetInterface(function(){
},function(proto){
alert("trace: initing male");
proto.$SetM("Fight",function(){
alert(this.Name+" is fighting");
});
},human_interface);
var child_interface = GetInterface(function(){
},function(proto){
alert("trace: initing child");
proto.$SetM("Play",function(){
alert(this.Name+" is playing");
});
},human_interface);
var adult_interface = GetInterface(function(){
},function(proto){
alert("trace: initing adult");
proto.$SetM("Work",function(){
alert(this.Name+" is working");
});
},human_interface);
var mammal_interface = GetInterface(function(){
},function(proto){
alert("trace: initing mammal");
proto.$SetM("DoMammalStuff",function(){
alert("doing mammal stuff");
});
});
var john={};
john.$IsA(adult_interface);
//the above 2 lines are equal to simply doing:
//var john=new adult_interface();
//you can think of it as a shortcut
john.$IsA(mammal_interface);
john.DoMammalStuff();
john.Name="john";
john.Sleep();
var mary=new female_interface();
mary.$IsA(child_interface);
mary.$IsA(mammal_interface);
mary.DoMammalStuff();
mary.Name="mary";
mary.Play();
mary.Dance();
</script>
Js.js:
"use strict";
var GetInterface;
(function(){
//================================================================================//
//(constructor:Function, setup:Function?, parent_interfaces:Function..):Function
GetInterface = function (constructor, setup) {
var parent_classes = GetParray(arguments, 2);
var output = function () {
output.$Init();
for (var x = parent_classes.length - 1; x >= 0; --x) {
parent_classes[x](this);
}
if(constructor===null){
constructor.apply(this, arguments);
}
};
output.$Init = Mize(function () {
var output_proto = output.prototype;
parent_classes.forEach(function (parent_class) {
parent_class.$Init();
Infect(output_proto, parent_class.prototype);
});
init_proto(output_proto,setup);
if(setup!==undefined){
setup(output_proto);
}
});
return output;
};
var init_proto=function(proto){
$defineProperty(proto, "$SetM", { value: set_m, writable: true, configurable: true });
$defineProperty(proto, "$ShadowM", { value: shadow_m, writable: true, configurable: true });
$defineProperty(proto, "$SetP", { value: set_p, writable: true, configurable: true });
$defineProperty(proto, "$ShadowP", { value: shadow_p, writable: true, configurable: true });
};
var set_m = function (method_name, method) {
this[method_name] = method;
};
var set_p = function (property_name, getter, setter) {
$defineProperty(this, property_name, { get: getter, set: setter, enumerable: true, configurable: true });
};
var shadow_m = function (method_name, supplied_method) {
var old_method = this[method_name];
this[method_name] = function () {
var args = GetParray(arguments);
args.unshift(old_method.bind(this));
supplied_method.apply(this, args);
};
};
var shadow_p = function (property_name, getter, setter) {
var old_descriptor = $getOwnPropertyDescriptor(this, property_name);
var old_get = old_descriptor.get;
var old_set = old_descriptor.set;
$defineProperty(this, property_name, { get: function () {
return getter.call(this, old_get.bind(this));
}, set: function (value) {
setter.call(this, old_set.bind(this), value);
}, enumerable: true, configurable: true
});
};
var $slice=Array.prototype.slice;
var $defineProperty=Object.defineProperty;
var $getOwnPropertyDescriptor=Object.getOwnPropertyDescriptor;
if($defineProperty===undefined){
throw "Object.defineProperty, Object.getOwnPropertyDescriptor, Object.keys are required";
}
//================================================================================//
//(victim:Object, disease:Object):void
var Infect=function (victim, disease, excludes) {
var keys=Object.keys(disease);
if(excludes!==undefined){
excludes.forEach(function(exclude){
ForEach(keys,function(key,x){
if(key===exclude){
keys.splice(x,1);
return false;
}
});
});
}
keys.forEach(function(key){
$defineProperty(victim, key, $getOwnPropertyDescriptor(disease, key));
});
};
//================================================================================//
//(args:Object # arguments object #, start_index:int?):Array
var GetParray = function (args, start_index) {
if (start_index === undefined) {
start_index = 0;
}
return $slice.call(args, start_index);
};
//================================================================================//
//(array:Array, f:Function(item:Object|null, index:pint):boolean?):Object
var ForEach=function(array,f){
for (var x = 0, xx = array.length, last_index=xx-1; x < xx; ++x) {
var result = f(array[x], x, last_index);
if (result !== undefined) {
return result;
}
}
};
//================================================================================//
//provides memoization.
//(f:Function, arity_fixed:boolean?true):Function
//caching is done according to the inputs. the results of calling function(undefined) and function() are cached differently.
//if arity is fixed, optimizations can be done
var Mize=function(f, arity_fixed) {
if (arity_fixed === undefined) {
arity_fixed = true;
}
var used; //for 0 arg
var result; //for 0 arg
var results; //for >0 args
var used_params; //for 1 arg
var used_param_sets; //for >1 args
var f_length = f.length;
var use_generic = !arity_fixed || f_length > 3;
if (use_generic) { //if `f_length` <= 3, it will be optimized (i.e. not using generic function)
results = [];
used_param_sets = [];
return function () {
var params = GetParray(arguments);
var result_found = false;
var result = ForEach(used_param_sets,function (used_param_set, x) {
if (used_param_set.length === params.length) {
var params_match = true;
ForEach(params,function (param, y) {
if (used_param_set[y] !== param) {
params_match = false;
return false;
}
});
if (params_match) {
result_found = true;
return results[x];
}
}
});
if (!result_found) {
used_param_sets.push(params);
result = f.apply(null, params);
results.push(result);
}
return result;
};
}
if (f_length === 0) {
used = false;
return function () {
if (!used) {
result = f();
used = true;
}
return result;
};
}
if (f_length === 1) {
used_params = [];
} else {
used_param_sets = [];
}
results = [];
switch (f_length) {
case 1:
return function (arg) {
var result_found = false;
var result = ForEach(used_params,function (used_param, x) {
if (arg === used_param) {
result_found = true;
return results[x];
}
});
if (!result_found) {
used_params.push(arg);
result = f(arg);
results.push(result);
}
return result;
};
break;
case 2:
return function (arg1, arg2) {
var result_found = false;
var result = ForEach(used_param_sets,function (used_param_set, x) {
if (arg1 === used_param_set[0] && arg2 === used_param_set[1]) {
result_found = true;
return results[x];
}
});
if (!result_found) {
used_param_sets.push([arg1, arg2]);
result = f(arg1, arg2);
results.push(result);
}
return result;
};
break;
case 3:
return function (arg1, arg2, arg3) {
var result_found = false;
var result = ForEach(used_param_sets,function (used_param_set, x) {
if (arg1 === used_param_set[0] && arg2 === used_param_set[1] && arg3 === used_param_set[2]) {
result_found = true;
return results[x];
}
});
if (!result_found) {
used_param_sets.push([arg1, arg2, arg3]);
result = f(arg1, arg2, arg3);
results.push(result);
}
return result;
};
break;
default:
throw "Invalid `f_length`: " + f_length;
}
};
//================================================================================//
Object.prototype.$Setup=function(setup){
setup(Object.getPrototypeOf(this));
};
//================================================================================//
Object.prototype.$IsA=function(_interface){
var excludes=GetParray(arguments,1);
if(this.$SetM===undefined){
this.$SetM=set_m;
this.$SetP=set_p;
this.$ShadowM=shadow_m;
this.$ShadowP=shadow_p;
}
_interface.$Init();
/*var this_proto={};
init_proto(this_proto);
Infect(this_proto,Object.getPrototypeOf(this));
this.__proto__=this_proto;*/
Infect(Object.getPrototypeOf(this),_interface.prototype,excludes);
};
//================================================================================//
})();
Este es un ejemplo que muestra la base de la programación OO que está buscando. El mejor ejemplo del mundo real sería jQuery probablemente.
Al aprender JavaScript, debes tener en cuenta que en realidad está más cerca de Scheme que de C o Java en sus raíces. Básicamente es esquema en la sintaxis de C.
Casi nunca hay un caso en el que debas usar "nuevo" en JavaScript, especialmente si estás escribiendo API. Parece que se agregó el "nuevo" operador porque JavaScript no estaba seguro de su marco prototípico. Para la mayoría de nosotros que empezamos a programar con lenguajes clásicos como C, C ++ y Java, esto parece extraño, ya que "nuevo" generalmente es exactamente lo que estamos buscando, porque se traduce fácilmente.
¿Por qué no debería usar "nuevo" preguntas? Bueno, debido a la implementación de "nuevo", puede empezar a borrar inadvertidamente sus datos globales (lo que recuerda es que todo no está en una función en JavaScript). Si resulta que usted es víctima de esto, no verá ningún error o notificación, sino que solo verá un comportamiento impredecible en su programa. Además, puede o no estar claro en cuanto a qué "esto" está realmente vinculado dentro de su "clase".
El borrado de la memoria global sin saberlo se produce principalmente cuando se escribe una función que está destinada a ser llamada con "nuevo" y el usuario no usa "nuevo". Sugerencias de por qué usarlo en las API puede llevar a usuarios descontentos.
La forma correcta de "clases" orientadas a objetos y la herencia es usar el atributo más poderoso de JavaScript ... el objeto.
Puede escribir una función para devolver un objeto para establecer una "clase". Todo lo que coloque en este objeto (números, cadenas, métodos, etc.) son propiedades públicas de su "clase". Todo lo que escriba dentro de su función que no esté dentro de ese objeto que se está devolviendo, es privado.
Para heredar de su "clase", simplemente puede inicializar su objeto y volver a un resultado de su "clase base" y luego ampliar su funcionalidad.
Las siguientes secciones del código mostrarán cómo construir una clase base con variables privadas y públicas, y los 2 niveles de herencia.
Objeto base
//Base Object
var Animal = function(spec) {
//This is our output object
//Everything provided from ''spec'' and
//everything not addded to ''that'' will
//be ''private''. Everything added to
//''that'' is ''public''.
var that = {};
//Private Methods
function extend(obj1,obj2) {
for(var key in obj2) {
obj1[key] = obj2[key];
}
}
//Private Variables
var defaults = {
name : ''Default Name'',
food : ''Default Food'',
saying : ''Default Saying'',
}
extend(defaults,spec);
//Public Methods
that.name = function() {
return defaults.name;
}
that.eats = function() {
if(typeof(defaults.food) === ''string'') {
return defaults.food;
} else if(typeof(defaults.food) === ''object'') {
return defaults.food.join('', '');
}
}
that.says = function() {
return defaults.saying;
}
return that;
}
var myAnimal = Animal(); //Create a new instance
alert(myAnimal.name()); //Alerts ''Default Name''
alert(myAnimal.eats()); //Alerts ''Default Food''
alert(myAnimal.says()); //Alerts ''Default Saying''
alert(myAnimal.saying); //Alerts ''undefined''
//alert(myAnimal.extend()); //Has No Method Error
var myAnimal2 = Animal({ //Create a new instance using a spec
name : ''Mike'',
food : [''Chicken'',''Duck''],
saying : ''Rawr'',
});
alert(myAnimal2.name()); //Alerts ''Mike''
alert(myAnimal2.eats()); //Alerts ''Chicken, Duck''
alert(myAnimal2.says()); //Alerts ''Rawr''
Herencia
//Inheritance Object
var Mammal = function(spec) {
//Private Methods
//Have to redefine this since
//I decided to use this as an
//example of a private method
function extend(obj1,obj2) {
for(var key in obj2) {
obj1[key] = obj2[key];
}
}
//Private Variables
//New list of defaults
var defaults = {
name : ''Mammal'',
attributes : [''fur''],
}
extend(defaults,spec);
//Inherrit from our Animal Object
//Use Mammal defaults
var that = Animal(defaults);
that.attributes = function() {
if(typeof(defaults.attributes) === ''string'') {
return defaults.attributes;
} else if(typeof(defaults.attributes) === ''object'') {
return defaults.attributes.join('', '');
} else {
return false;
}
}
return that;
}
//Second-Level Inheritance
var Cat = function(spec) {
//Private Methods
//Have to redefine this since
//I decided to use this as an
//example of a private method
function extend(obj1,obj2) {
for(var key in obj2) {
obj1[key] = obj2[key];
}
}
//Private Variables
//New list of defaults
var defaults = {
name : ''Cat'',
saying : ''Meow'',
food : [''fish'',''birds'',''frogs'',''MeowMix''],
fur_color : ''Default Fur Color'',
attributes : [''fur'',''claws'',''crazy eyes'',''long tail''],
}
extend(defaults,spec);
//Inherrit from our Mammal Object
//We use our defaults for the cat
var that = Mammal(defaults);
that.fur_color = function() {
return defaults.fur_color;
}
that.purr = function(n) {
var str = '''';
for(var i=0;i<n;i++) {
if(i === 0) {
str = ''p-'';
} else if(i === n-1) {
str += ''r'';
} else {
str += ''r-'';
}
}
return str
};
return that;
}
var myMammal = Mammal();
alert(myMammal.name()); //Alerts Mammal
alert(myMammal.attributes()); //Alerts ''fur''
var myCat = Cat();
alert(myCat.name()); //Alerts Cat
alert(myCat.says()); //Alerts Meow
var toonces = Cat({
name : ''Toonces the Driving Cat'',
food : [''Whiskas'',''ham''],
saying : ''Meeeooooowww'',
fur_color : ''Black'',
attributes : [
''Can Drive a Car'', ''Claws'',
''fur'',''crazy eyes'',''long tail'',
''Steals Hub Caps'',
],
});
alert(toonces.name()); //Alerts ''Toonces the Driving Cat''
alert(toonces.says()); //Alerts ''Meeooooowww''
alert(toonces.eats()); //Alerts ''Whiskas, ham''
alert(toonces.fur_color()); //Alerts ''Black''
alert(toonces.attributes()); //Alerts ''Can Drive a Car, Claws,
//fur, crazy eyes, long tail,
// Steals Hub Caps'',
alert(toonces.purr(5)); //Alerts ''p-r-r-r-r''
EDITAR : Me alertaron de que no usé el objeto "prototipo". Hice esto para evitar TENER que usar el operador "nuevo", como se menciona en el texto anterior. Para completar, daré un ejemplo usando el objeto prototipo a continuación ...
Herencia con el objeto prototipo
//Building a class to use the prototype object
var Dog = function(spec) {
var that = this;
//Private Methods
//Have to redefine this since
//I decided to use this as an
//example of a private method
function extend(obj1,obj2) {
for(var key in obj2) {
obj1[key] = obj2[key];
}
}
//Private Variables
//New list of defaults
var defaults = {
name : ''Dog'',
saying : ''Woof'',
food : [''bacon''],
fur_color : ''Default Fur Color'',
attributes : [''fur'',''Barks at Mailman''],
}
//Attach the properties of a Mammal to "self"
this.self = new Mammal(defaults);
//Add a function to get the name
this.getName = function() {
return that.self.name();
}
}
//Extend the prototype
Dog.prototype.growl = "grrrrrrr";
//Make a new dog...HAVE TO CALL NEW HERE OR ELSE BAD THINGS CAN HAPPEN
d= new Dog();
alert(d.growl); //Alerts ''grrrrrrr''
alert(d.getName()); //Alerts ''Dog''
alert(d.self.says()); //Alerts ''Woof''
Por favor, siéntase libre de preguntarme acerca de este post Disfrutar.
No estoy exactamente seguro de lo que está buscando, pero mi marco actual le permite programar en modo OO así:
Cin.define({
name: ''MyApp.Logger'',
extends: ''Cin.Component'',
implements: [''MyApp.ILogger''],
mixes: {
SomeMixin: ''MyApp.SomeMixin''
},
init: function() {
},
method: function() {
},
statics: {
staticMethod: function() {}
}
});
Y luego puedes escribir código como:
var instance = new MyApp.Logger();
instance.method();
MyApp.Logger.staticMethod();
No estoy tratando de emular el OO clásico aquí. Estoy tratando de hacer una manera conveniente y útil de declarar la herencia, los mixins, las interfaces y los conceptos generales de OO para que sea fácil para el desarrollador escribir dicho código OO. Esto también me da la oportunidad de terminar mi componente de carga automática para que ya no se ocupe de las dependencias y pueda hacer compilaciones personalizadas y disfrutar de un desarrollo más rápido gracias a que no necesita cargar 100 scripts por cada carga de página.
Si desea aprender conceptos prototípicos de OO, creo que debería escribir algún tipo de sistema de herencia. Echa un vistazo a Dojo Toolkit o ExtJS . Una buena cosa para recordar es que los sistemas basados en prototipos giran y se debilitan, son más poderosos que los lenguajes OO basados en clase. En mi opinión, no hay una única manera correcta de escribir código prototípico.
Sin embargo, me temo que la mayoría, si no todos los sistemas de herencia, parecen emular el OO clásico. En mi opinión, mi marco no lo hace, pero entonces ni siquiera está terminado.
No lo encontrarás.
Hace un tiempo fui a buscar este tipo de cosas, y esto es lo que encontré: el Self Paper Organizing Programs Without Classes (Busque en Citeseer una versión en PDF ). Este documento analiza las mejores prácticas para Self , el lenguaje prototípico original. y la mejor práctica es usar el "lenguaje de objeto de rasgos", que consiste en hacer que sus objetos hereden de los "objetos de rasgos" que contienen solo métodos, y no datos específicos del objeto. En otras palabras, un objeto que es sospechosamente como una clase.
Incluso el lenguaje prototípico original emula las clases.
Probablemente JSLint (Crockford es un partidario de la herencia prototípica, pero no he revisado cada pulgada). También parece más funcional que orientado a objetos, pero espero que ese sea generalmente el caso con el código que realmente abarca la herencia prototípica.
ExtJS es un excelente ejemplo de JavaScript OO. Implementa una jerarquía OO realmente sofisticada a nivel empresarial en JavaScript que hace muchas cosas fuera de la caja. Puede ser una lectura desalentadora (la última vez que ingresé en la versión 3.X, tenía más de 1 MB de JavaScript en bruto y sin comprimir), pero le daría muchas ideas. Puede comenzar revisando la documentación para obtener una vista de alto nivel.