tutorial español javascript oop inheritance ecmascript-5 resig

javascript - solidity tutorial español pdf



Mejora de la herencia de JavaScript simple (3)

John Resig (de jQuery fame) proporciona una implementación concisa de la herencia de JavaScript simple . Su enfoque inspiró mi intento de mejorar las cosas aún más. He reescrito la función Class.extend original de Class.extend para incluir las siguientes ventajas:

  • Rendimiento - menos sobrecarga durante la definición de clase, la construcción de objetos y las llamadas de método de clase base

  • Flexibilidad : optimizado para navegadores compatibles con ECMAScript 5 más nuevos (p. Ej., Chrome), pero proporciona un "shim" equivalente para navegadores antiguos (por ejemplo, IE6)

  • Compatibilidad : se valida en modo estricto y proporciona una mejor compatibilidad de herramientas (por ejemplo, comentarios VSDoc / JSDoc, Visual Studio IntelliSense, etc.)

  • Simplicidad : no tiene que ser un "ninja" para comprender el código fuente (y es incluso más simple si pierde las funciones de ECMAScript 5)

  • Robustez : pasa más pruebas unitarias de "caja de esquina" (p. Ej. Anulando a Estrías en IE)

Debido a que casi parece demasiado bueno para ser cierto, quiero asegurarme de que mi lógica no tenga fallas o errores fundamentales, y ver si alguien puede sugerir mejoras o refutar el código. Con eso, presento la función de 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éntico al de Resig, excepto por el nombre del constructor ( constructor vs. init ) y la sintaxis para las llamadas al método 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 que todos pasan:

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 mal con mi enfoque contra el enfoque original de John Resig? 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.


No tan rapido. Simplemente no funciona.

Considerar:

var p = new Person(true); alert("p.dance()? " + p.dance()); => true var n = new Ninja(); alert("n.dance()? " + n.dance()); => false n.dancing = true; alert("n.dance()? " + n.dance()); => false

base es solo otro objeto inicializado con miembros predeterminados que te hicieron pensar que funciona.

EDITAR: para el registro, aquí está mi propia implementación (aunque más detallada) de Java como herencia en Javascript, creada en 2006 en el momento en que me inspiré en Base.js de Dean Edward (y estoy de acuerdo con él cuando dice que la versión de John es solo una reescritura de su Base.js ). Puedes verlo en acción (y paso a corregirlo en Firebug) aquí .

/** * A function that does nothing: to be used when resetting callback handlers. * @final */ EMPTY_FUNCTION = function() { // does nothing. } var Class = { /** * Defines a new class from the specified instance prototype and class * prototype. * * @param {Object} instancePrototype the object literal used to define the * member variables and member functions of the instances of the class * being defined. * @param {Object} classPrototype the object literal used to define the * static member variables and member functions of the class being * defined. * * @return {Function} the newly defined class. */ define: function(instancePrototype, classPrototype) { /* This is the constructor function for the class being defined */ var base = function() { if (!this.__prototype_chaining && base.prototype.initialize instanceof Function) base.prototype.initialize.apply(this, arguments); } base.prototype = instancePrototype || {}; if (!base.prototype.initialize) base.prototype.initialize = EMPTY_FUNCTION; for (var property in classPrototype) { if (property == ''initialize'') continue; base[property] = classPrototype[property]; } if (classPrototype && (classPrototype.initialize instanceof Function)) classPrototype.initialize.apply(base); function augment(method, derivedPrototype, basePrototype) { if ( (method == ''initialize'') &&(basePrototype[method].length == 0)) { return function() { basePrototype[method].apply(this); derivedPrototype[method].apply(this, arguments); } } return function() { this.base = function() { return basePrototype[method].apply(this, arguments); }; return derivedPrototype[method].apply(this, arguments); delete this.base; } } /** * Provides the definition of a new class that extends the specified * <code>parent</code> class. * * @param {Function} parent the class to be extended. * @param {Object} instancePrototype the object literal used to define * the member variables and member functions of the instances of the * class being defined. * @param {Object} classPrototype the object literal used to define the * static member variables and member functions of the class being * defined. * * @return {Function} the newly defined class. */ function extend(parent, instancePrototype, classPrototype) { var derived = function() { if (!this.__prototype_chaining && derived.prototype.initialize instanceof Function) derived.prototype.initialize.apply(this, arguments); } parent.prototype.__prototype_chaining = true; derived.prototype = new parent(); delete parent.prototype.__prototype_chaining; for (var property in instancePrototype) { if ( (instancePrototype[property] instanceof Function) &&(parent.prototype[property] instanceof Function)) { derived.prototype[property] = augment(property, instancePrototype, parent.prototype); } else derived.prototype[property] = instancePrototype[property]; } derived.extend = function(instancePrototype, classPrototype) { return extend(derived, instancePrototype, classPrototype); } for (var property in classPrototype) { if (property == ''initialize'') continue; derived[property] = classPrototype[property]; } if (classPrototype && (classPrototype.initialize instanceof Function)) classPrototype.initialize.apply(derived); return derived; } base.extend = function(instancePrototype, classPrototype) { return extend(base, instancePrototype, classPrototype); } return base; } }

Y así es como lo usas:

var Base = Class.define( { initialize: function(value) // Java constructor equivalent { this.property = value; }, property: undefined, // member variable getProperty: function() // member variable accessor { return this.property; }, foo: function() { alert(''inside Base.foo''); // do something }, bar: function() { alert(''inside Base.bar''); // do something else } }, { initialize: function() // Java static initializer equivalent { this.property = ''Base''; }, property: undefined, // static member variables can have the same // name as non static member variables getProperty: function() // static member functions can have the same { // name as non static member functions return this.property; } }); var Derived = Base.extend( { initialize: function() { this.base(''derived''); // chain with parent class''s constructor }, property: undefined, getProperty: function() { return this.property; }, foo: function() // override foo { alert(''inside Derived.foo''); this.base(); // call parent class implementation of foo // do some more treatments } }, { initialize: function() { this.property = ''Derived''; }, property: undefined, getProperty: function() { return this.property; } }); var b = new Base(''base''); alert(''b instanceof Base returned: '' + (b instanceof Base)); alert(''b.getProperty() returned: '' + b.getProperty()); alert(''Base.getProperty() returned: '' + Base.getProperty()); b.foo(); b.bar(); var d = new Derived(''derived''); alert(''d instanceof Base returned: '' + (d instanceof Base)); alert(''d instanceof Derived returned: '' + (d instanceof Derived)); alert(''d.getProperty() returned: '' + d.getProperty()); alert(''Derived.getProperty() returned: '' + Derived.getProperty()); d.foo(); d.bar();


Esto es tan simple como puedes obtener. Fue tomado de http://www.sitepoint.com/javascript-inheritance/ .

// copyPrototype is used to do a form of inheritance. See http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/# // Example: // function Bug() { this.legs = 6; } // Insect.prototype.getInfo = function() { return "a general insect"; } // Insect.prototype.report = function() { return "I have " + this.legs + " legs"; } // function Millipede() { this.legs = "a lot of"; } // copyPrototype(Millipede, Bug); /* Copy the prototype functions from Bug into Millipede */ // Millipede.prototype.getInfo = function() { return "please don''t confuse me with a centipede"; } /* ''''Override" getInfo() */ function copyPrototype(descendant, parent) { var sConstructor = parent.toString(); var aMatch = sConstructor.match(//s*function (.*)/(/); if (aMatch != null) { descendant.prototype[aMatch[1]] = parent; } for (var m in parent.prototype) { descendant.prototype[m] = parent.prototype[m]; } };


Hace algún tiempo, miré varios sistemas de objetos para JS e incluso implementé algunos de los míos, por ejemplo, class.js ( versión ES5 ) y proto.js .

La razón por la que nunca los utilicé: terminará escribiendo la misma cantidad de código. Un ejemplo: el Ninja-ejemplo de Resig (solo agregó algunos espacios en blanco):

var Person = Class.extend({ init: function(isDancing) { this.dancing = isDancing; }, dance: function() { return this.dancing; } }); var Ninja = Person.extend({ init: function() { this._super(false); }, swingSword: function() { return true; } });

19 líneas, 264 bytes.

Estándar JS con Object.create() (que es una función de ECMAScript 5, pero para nuestros propósitos puede ser reemplazada por una implementación personalizada de clone() ES3):

function Person(isDancing) { this.dancing = isDancing; } Person.prototype.dance = function() { return this.dancing; }; function Ninja() { Person.call(this, false); } Ninja.prototype = Object.create(Person.prototype); Ninja.prototype.swingSword = function() { return true; };

17 líneas, 282 bytes. Imo, los bytes adicionales no son realmente la complejidad añadida de un sistema de objetos separado. Es bastante fácil acortar el ejemplo estándar agregando algunas funciones personalizadas, pero de nuevo: no vale la pena.