John Resig (de jQuery fame) proporciona una implementación concisa de Simple JavaScript Inheritance. Su enfoque inspiró mi intento de mejorar las cosas aún más. He reescrito función original Class.extend
de Resig para incluir las siguientes ventajas:Mejora de la herencia de JavaScript simple
Rendimiento - menos sobrecarga durante la definición de la clase, la construcción objeto y método de la clase base llama
Flexibilidad - optimizado para los navegadores más nuevos compatibles con ECMAScript 5 (p. ej., Chrome), pero proporciona un "shim" equivalente para navegadores antiguos (p. ej. IE6)
Compatibilidad - valida en modo estricto y proporciona una mejor compatibilidad de herramienta (p. comentarios VSDoc/jsdoc, Estudio de IntelliSense Visual, etc.)
Simplicidad - usted no tiene que ser un "ninja" para entender el código fuente (y es aún más sencillo si pierde las características de ECMAScript 5)
Robustez - pasa más "caso límite" pruebas unitarias (por ejemplo primordiales toString en IE)
Debido a que casi parece demasiado bueno para ser verdad, quiero asegurar mi lógica no lo hace tener cualquier defecto fundamental o bu gs, y ver si alguien puede sugerir mejoras o refutar el código. Con eso, presento la función classify
:
function classify(base, properties)
{
/// <summary>Creates a type (i.e. class) that supports prototype-chaining (i.e. inheritance).</summary>
/// <param name="base" type="Function" optional="true">The base class to extend.</param>
/// <param name="properties" type="Object" optional="true">The properties of the class, including its constructor and members.</param>
/// <returns type="Function">The class.</returns>
// quick-and-dirty method overloading
properties = (typeof(base) === "object") ? base : properties || {};
base = (typeof(base) === "function") ? base : Object;
var basePrototype = base.prototype;
var derivedPrototype;
if (Object.create)
{
// allow newer browsers to leverage ECMAScript 5 features
var propertyNames = Object.getOwnPropertyNames(properties);
var propertyDescriptors = {};
for (var i = 0, p; p = propertyNames[i]; i++)
propertyDescriptors[p] = Object.getOwnPropertyDescriptor(properties, p);
derivedPrototype = Object.create(basePrototype, propertyDescriptors);
}
else
{
// provide "shim" for older browsers
var baseType = function() {};
baseType.prototype = basePrototype;
derivedPrototype = new baseType;
// add enumerable properties
for (var p in properties)
if (properties.hasOwnProperty(p))
derivedPrototype[p] = properties[p];
// add non-enumerable properties (see https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute)
if (!{ constructor: true }.propertyIsEnumerable("constructor"))
for (var i = 0, a = [ "constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf" ], p; p = a[i]; i++)
if (properties.hasOwnProperty(p))
derivedPrototype[p] = properties[p];
}
// build the class
var derived = properties.hasOwnProperty("constructor") ? properties.constructor : function() { base.apply(this, arguments); };
derived.prototype = derivedPrototype;
derived.prototype.constructor = derived;
derived.prototype.base = derived.base = basePrototype;
return derived;
}
Y el uso es casi idéntica a la de Resig excepto por el nombre del constructor (constructor
vs init
) y la sintaxis para las llamadas a métodos de la clase base.
/* Example 1: Define a minimal class */
var Minimal = classify();
/* Example 2a: Define a "plain old" class (without using the classify function) */
var Class = function()
{
this.name = "John";
};
Class.prototype.count = function()
{
return this.name + ": One. Two. Three.";
};
/* Example 2b: Define a derived class that extends a "plain old" base class */
var SpanishClass = classify(Class,
{
constructor: function()
{
this.name = "Juan";
},
count: function()
{
return this.name + ": Uno. Dos. Tres.";
}
});
/* Example 3: Define a Person class that extends Object by default */
var Person = classify(
{
constructor: function(name, isQuiet)
{
this.name = name;
this.isQuiet = isQuiet;
},
canSing: function()
{
return !this.isQuiet;
},
sing: function()
{
return this.canSing() ? "Figaro!" : "Shh!";
},
toString: function()
{
return "Hello, " + this.name + "!";
}
});
/* Example 4: Define a Ninja class that extends Person */
var Ninja = classify(Person,
{
constructor: function(name, skillLevel)
{
Ninja.base.constructor.call(this, name, true);
this.skillLevel = skillLevel;
},
canSing: function()
{
return Ninja.base.canSing.call(this) || this.skillLevel > 200;
},
attack: function()
{
return "Chop!";
}
});
/* Example 4: Define an ExtremeNinja class that extends Ninja that extends Person */
var ExtremeNinja = classify(Ninja,
{
attack: function()
{
return "Chop! Chop!";
},
backflip: function()
{
this.skillLevel++;
return "Woosh!";
}
});
var m = new Minimal();
var c = new Class();
var s = new SpanishClass();
var p = new Person("Mary", false);
var n = new Ninja("John", 100);
var e = new ExtremeNinja("World", 200);
Y aquí están mis pruebas QUnit el que pasan todos:
equals(m instanceof Object && m instanceof Minimal && m.constructor === Minimal, true);
equals(c instanceof Object && c instanceof Class && c.constructor === Class, true);
equals(s instanceof Object && s instanceof Class && s instanceof SpanishClass && s.constructor === SpanishClass, true);
equals(p instanceof Object && p instanceof Person && p.constructor === Person, true);
equals(n instanceof Object && n instanceof Person && n instanceof Ninja && n.constructor === Ninja, true);
equals(e instanceof Object && e instanceof Person && e instanceof Ninja && e instanceof ExtremeNinja && e.constructor === ExtremeNinja, true);
equals(c.count(), "John: One. Two. Three.");
equals(s.count(), "Juan: Uno. Dos. Tres.");
equals(p.isQuiet, false);
equals(p.canSing(), true);
equals(p.sing(), "Figaro!");
equals(n.isQuiet, true);
equals(n.skillLevel, 100);
equals(n.canSing(), false);
equals(n.sing(), "Shh!");
equals(n.attack(), "Chop!");
equals(e.isQuiet, true);
equals(e.skillLevel, 200);
equals(e.canSing(), false);
equals(e.sing(), "Shh!");
equals(e.attack(), "Chop! Chop!");
equals(e.backflip(), "Woosh!");
equals(e.skillLevel, 201);
equals(e.canSing(), true);
equals(e.sing(), "Figaro!");
equals(e.toString(), "Hello, World!");
¿Alguien ve algo malo en mi acercamiento vs John Resig de original approach? Sugerencias y comentarios son bienvenidos!
NOTA: El código anterior se ha modificado significativamente desde que inicialmente publiqué esta pregunta. Lo anterior representa la última versión. Para ver cómo ha evolucionado, verifique el historial de revisión.
Recomendaría 'Object.create' y [traitsjs] (http://traitsjs.org/). La herencia no es buena en javascript, usa la composición de objetos – Raynos
Quizás todavía no estoy acostumbrado, pero la sintaxis de los rasgos me da vueltas. Creo que pasaré hasta que obtenga un seguimiento ... – Will