Estoy tratando de implementar la herencia en javascript. Se me ocurrió el siguiente código mínimo para admitirlo.
function Base(){
this.call = function(handler, args){
handler.call(this, args);
}
}
Base.extend = function(child, parent){
parent.apply(child);
child.base = new parent;
child.base.child = child;
}
Expertos, háganme saber si esto será suficiente o cualquier otro problema importante que pueda haber pasado por alto. Basado en problemas similares enfrentados, sugiera otros cambios.
Aquí está el script de prueba completo:
function Base(){
this.call = function(handler, args){
handler.call(this, args);
}
this.superalert = function(){
alert('tst');
}
}
Base.extend = function(child, parent){
parent.apply(child);
child.base = new parent;
child.base.child = child;
}
function Child(){
Base.extend(this, Base);
this.width = 20;
this.height = 15;
this.a = ['s',''];
this.alert = function(){
alert(this.a.length);
alert(this.height);
}
}
function Child1(){
Base.extend(this, Child);
this.depth = 'depth';
this.height = 'h';
this.alert = function(){
alert(this.height); // display current object height
alert(this.a.length); // display parents array length
this.call(this.base.alert);
// explicit call to parent alert with current objects value
this.call(this.base.superalert);
// explicit call to grandparent, parent does not have method
this.base.alert(); // call parent without overriding values
}
}
var v = new Child1();
v.alert();
alert(v.height);
alert(v.depth);
javascript
inheritance
hambriento
fuente
fuente
Respuestas:
Para implementar la herencia de JavaScript en ECMAScript 5 , puede definir el prototipo de un objeto y usarlo
Object.create
para heredar. También puede agregar / anular propiedades tanto como desee.Ejemplo:
/** * Transform base class */ function Transform() { this.type = "2d"; } Transform.prototype.toString = function() { return "Transform"; } /** * Translation class. */ function Translation(x, y) { // Parent constructor Transform.call(this); // Public properties this.x = x; this.y = y; } // Inheritance Translation.prototype = Object.create(Transform.prototype); // Override Translation.prototype.toString = function() { return Transform.prototype.toString() + this.type + " Translation " + this.x + ":" + this.y; } /** * Rotation class. */ function Rotation(angle) { // Parent constructor Transform.call(this); // Public properties this.angle = angle; } // Inheritance Rotation.prototype = Object.create(Transform.prototype); // Override Rotation.prototype.toString = function() { return Transform.prototype.toString() + this.type + " Rotation " + this.angle; } // Tests translation = new Translation(10, 15); console.log(translation instanceof Transform); // true console.log(translation instanceof Translation); // true console.log(translation instanceof Rotation); // false console.log(translation.toString()) // Transform2d Translation 10:15
fuente
Translation.prototype = new Transform()
? Además, dado que la respuesta no funciona actualmente, ¿la editaría?Translation.prototype.constructor = Translation
. Útil para clonar el objeto (en la mayoría de las técnicas).Creo que la solución de Crockfords es demasiado complicada, al igual que la de John. Es mucho más sencillo obtener la herencia de JavaScript de lo que ambos parecen describir. Considerar:
//Classes function A() { B.call(this); } function B() { C.call(this); this.bbb = function() { console.log("i was inherited from b!"); } } function C() { D.call(this); } function D() { E.call(this); } function E() { //instance property this.id = Math.random() } //set up the inheritance chain (order matters) D.prototype = new E(); C.prototype = new D(); B.prototype = new C(); A.prototype = new B(); //Add custom functions to each A.prototype.foo = function() { console.log("a"); }; B.prototype.bar = function() { console.log("b"); }; C.prototype.baz = function() { console.log("c"); }; D.prototype.wee = function() { console.log("d"); }; E.prototype.woo = function() { console.log("e"); }; //Some tests a = new A(); a.foo(); a.bar(); a.baz(); a.wee(); a.woo(); console.log(a.id); a.bbb(); console.log(a instanceof A); console.log(a instanceof B); console.log(a instanceof C); console.log(a instanceof D); console.log(a instanceof E); var b = new B(); console.log(b.id)
He escrito una descripción completa de la solución anterior en mi blog .
fuente
this._myProtectedVariable = 5;
para crear miembros protegidos.Mientras jugaba con objetos JS, encontré una solución más minimalista :-) ¡Disfruta!
function extend(b,a,t,p) { b.prototype = a; a.apply(t,p); }
Ejemplo
function A() { this.info1 = function() { alert("A"); } } function B(p1,p2) { extend(B,A,this); this.info2 = function() { alert("B"+p1+p2); } } function C(p) { extend(C,B,this,["1","2"]); this.info3 = function() { alert("C"+p); } } var c = new C("c"); c.info1(); // A c.info2(); // B12 c.info3(); // Cc
fuente
Aquí está la forma más simple y espero que sea la forma más fácil de entender la herencia en JS. Lo más útil de este ejemplo será para los programadores de PHP.
function Mother(){ this.canSwim = function(){ console.log('yes'); } } function Son(){}; Son.prototype = new Mother; Son.prototype.canRun = function(){ console.log('yes'); }
Ahora el hijo tiene un método anulado y un nuevo
function Grandson(){} Grandson.prototype = new Son; Grandson.prototype.canPlayPiano = function(){ console.log('yes'); }; Grandson.prototype.canSwim = function(){ console.log('no'); }
Ahora el nieto tiene dos métodos anulados y uno nuevo.
var g = new Grandson; g.canRun(); // => yes g.canPlayPiano(); // => yes g.canSwim(); // => no
fuente
new
para la herencia¿Por qué no usar objetos en lugar de funciones?
var Base = { superalert : function() { alert('tst'); } }; var Child = Object.create(Base); Child.width = 20; Child.height = 15; Child.a = ['s','']; Child.childAlert = function () { alert(this.a.length); alert(this.height); } var Child1 = Object.create(Child); Child1.depth = 'depth'; Child1.height = 'h'; Child1.alert = function () { alert(this.height); alert(this.a.length); this.childAlert(); this.superalert(); };
Y llámalo así:
var child1 = Object.create(Child1); child1.alert();
Este enfoque es mucho más limpio que con las funciones. Encontré este blog que explica por qué la herencia con funciones no es una forma adecuada de hacerlo en JS: http://davidwalsh.name/javascript-objects-deconstruction
EDITAR
var Child también se puede escribir como:
var Child = Object.create(Base, { width : {value : 20}, height : {value : 15, writable: true}, a : {value : ['s', ''], writable: true}, childAlert : {value : function () { alert(this.a.length); alert(this.height); }} });
fuente
Aquí está mi solución, que se basa en el método de herencia prototípico estándar descrito en la respuesta de Lorenzo Polidori .
Primero, comienzo definiendo estos métodos auxiliares, que hacen que las cosas sean más fáciles de entender y más legibles más adelante:
Function.prototype.setSuperclass = function(target) { // Set a custom field for keeping track of the object's 'superclass'. this._superclass = target; // Set the internal [[Prototype]] of instances of this object to a new object // which inherits from the superclass's prototype. this.prototype = Object.create(this._superclass.prototype); // Correct the constructor attribute of this class's prototype this.prototype.constructor = this; }; Function.prototype.getSuperclass = function(target) { // Easy way of finding out what a class inherits from return this._superclass; }; Function.prototype.callSuper = function(target, methodName, args) { // If methodName is ommitted, call the constructor. if (arguments.length < 3) { return this.callSuperConstructor(arguments[0], arguments[1]); } // `args` is an empty array by default. if (args === undefined || args === null) args = []; var superclass = this.getSuperclass(); if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found."); var method = superclass.prototype[methodName]; if (typeof method != "function") throw new TypeError("TypeError: Object " + superclass.prototype + " has no method '" + methodName + "'"); return method.apply(target, args); }; Function.prototype.callSuperConstructor = function(target, args) { if (args === undefined || args === null) args = []; var superclass = this.getSuperclass(); if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found."); return superclass.apply(target, args); };
Ahora, no solo puede establecer la superclase de una clase con
SubClass.setSuperclass(ParentClass)
, sino que también puede llamar a métodos anulados conSubClass.callSuper(this, 'functionName', [argument1, argument2...])
:/** * Transform base class */ function Transform() { this.type = "2d"; } Transform.prototype.toString = function() { return "Transform"; } /** * Translation class. */ function Translation(x, y) { // Parent constructor Translation.callSuper(this, arguments); // Public properties this.x = x; this.y = y; } // Inheritance Translation.setSuperclass(Transform); // Override Translation.prototype.toString = function() { return Translation.callSuper(this, 'toString', arguments) + this.type + " Translation " + this.x + ":" + this.y; } /** * Rotation class. */ function Rotation(angle) { // Parent constructor Rotation.callSuper(this, arguments); // Public properties this.angle = angle; } // Inheritance Rotation.setSuperclass(Transform); // Override Rotation.prototype.toString = function() { return Rotation.callSuper(this, 'toString', arguments) + this.type + " Rotation " + this.angle; } // Tests translation = new Translation(10, 15); console.log(translation instanceof Transform); // true console.log(translation instanceof Translation); // true console.log(translation instanceof Rotation); // false console.log(translation.toString()) // Transform2d Translation 10:15
Es cierto que incluso con las funciones de ayuda, la sintaxis aquí es bastante incómoda. Afortunadamente, sin embargo, en ECMAScript 6 se ha agregado algo de azúcar sintáctico ( clases mínimas máximas ) para hacer las cosas mucho más bonitas. P.ej:
/** * Transform base class */ class Transform { constructor() { this.type = "2d"; } toString() { return "Transform"; } } /** * Translation class. */ class Translation extends Transform { constructor(x, y) { super(); // Parent constructor // Public properties this.x = x; this.y = y; } toString() { return super(...arguments) + this.type + " Translation " + this.x + ":" + this.y; } } /** * Rotation class. */ class Rotation extends Transform { constructor(angle) { // Parent constructor super(...arguments); // Public properties this.angle = angle; } toString() { return super(...arguments) + this.type + " Rotation " + this.angle; } } // Tests translation = new Translation(10, 15); console.log(translation instanceof Transform); // true console.log(translation instanceof Translation); // true console.log(translation instanceof Rotation); // false console.log(translation.toString()) // Transform2d Translation 10:15
Tenga en cuenta que ECMAScript 6 todavía se encuentra en la etapa de borrador en este momento, y que yo sepa no está implementado en ningún navegador web importante. Sin embargo, si lo desea, puede usar algo como el compilador de Traceur para compilar
ECMAScript 6
en elECMAScript 5
JavaScript simple y antiguo . Puede ver el ejemplo anterior compilado con Traceur aquí .fuente
Si bien estoy de acuerdo con todas las respuestas anteriores, creo que JavaScript no necesita estar orientado a objetos (evitar la herencia), sino que un enfoque basado en objetos debería ser suficiente en la mayoría de los casos.
Me gusta la forma en que Eloquent JavaScript comienza su Capítulo 8 sobre Programación Orientada a Objetos hablando de OO. En lugar de descifrar la mejor manera de implementar la herencia, se debería dedicar más energía a aprender los aspectos funcionales de JavaScript, por lo tanto, encontré el Capítulo 6 sobre Programación Funcional, más interesante.
fuente
//This is an example of how to override a method, while preserving access to the original. //The pattern used is actually quite simple using JavaScripts ability to define closures: this.somefunction = this.someFunction.override(function(args){ var result = this.inherited(args); result += this.doSomethingElse(); return result; }); //It is accomplished through this piece of code (courtesy of Poul Krogh): /*************************************************************** function.override overrides a defined method with a new one, while preserving the old method. The old method is only accessible from the new one. Use this.inherited() to access the old method. ***************************************************************/ Function.prototype.override = function(func) { var remember = this; var f = function() { var save = this.inherited; this.inherited = remember; var result = func.apply(this, Array.prototype.slice.call(arguments)); this.inherited = save; return result; }; return f; }
fuente
¿Qué tal este enfoque simple
function Body(){ this.Eyes = 2; this.Arms = 2; this.Legs = 2; this.Heart = 1; this.Walk = function(){alert(this.FirstName + ' Is Walking')}; } function BasePerson() { var BaseBody = new Body(this); BaseBody.FirstName = ''; BaseBody.LastName = ''; BaseBody.Email = ''; BaseBody.IntroduceSelf = function () { alert('Hello my name is ' + this.FirstName + ' ' + this.LastName); }; return BaseBody; } function Person(FirstName,LastName) { var PersonBuild = new BasePerson(); PersonBuild.FirstName = FirstName; PersonBuild.LastName = LastName; return PersonBuild; } var Person1 = new Person('Code', 'Master'); Person1.IntroduceSelf(); Person1.Walk();
fuente
Herencia prototípica básica
Una forma simple pero efectiva de hacer herencia en JavaScript es usar las siguientes dos líneas:
B.prototype = Object.create(A.prototype); B.prototype.constructor = B;
Eso es similar a hacer esto:
B.prototype = new A();
La principal diferencia entre ambos es que el constructor de
A
no se ejecuta cuando se usaObject.create
, lo cual es más intuitivo y más similar a la herencia basada en clases.Siempre puede optar por ejecutar opcionalmente el constructor de
A
al crear una nueva instancia deB
agregando agregarlo al constructor deB
:function B(arg1, arg2) { A(arg1, arg2); // This is optional }
Si desea pasar todos los argumentos de
B
aA
, también puede usarFunction.prototype.apply()
:function B() { A.apply(this, arguments); // This is optional }
Si desea mezclar otro objeto en la cadena de constructores de
B
, puede combinarObject.create
conObject.assign
:B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype); B.prototype.constructor = B;
Manifestación
function A(name) { this.name = name; } A.prototype = Object.create(Object.prototype); A.prototype.constructor = A; function B() { A.apply(this, arguments); this.street = "Downing Street 10"; } B.prototype = Object.create(A.prototype); B.prototype.constructor = B; function mixin() { } mixin.prototype = Object.create(Object.prototype); mixin.prototype.constructor = mixin; mixin.prototype.getProperties = function() { return { name: this.name, address: this.street, year: this.year }; }; function C() { B.apply(this, arguments); this.year = "2018" } C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype); C.prototype.constructor = C; var instance = new C("Frank"); console.log(instance); console.log(instance.getProperties());
Creando tu propia envoltura
Si no le gusta escribir aproximadamente las mismas dos líneas en todo su código, puede escribir una función de envoltura básica como esta:
function inheritance() { var args = Array.prototype.slice.call(arguments); var firstArg = args.shift(); switch (args.length) { case 0: firstArg.prototype = Object.create(Object.prototype); firstArg.prototype.constructor = firstArg; break; case 1: firstArg.prototype = Object.create(args[0].prototype); firstArg.prototype.constructor = firstArg; break; default: for(var i = 0; i < args.length; i++) { args[i] = args[i].prototype; } args[0] = Object.create(args[0]); var secondArg = args.shift(); firstArg.prototype = Object.assign.apply(Object, args); firstArg.prototype.constructor = firstArg; } }
Cómo funciona esta envoltura:
Object
.Manifestación
function inheritance() { var args = Array.prototype.slice.call(arguments); var firstArg = args.shift(); switch (args.length) { case 0: firstArg.prototype = Object.create(Object.prototype); firstArg.prototype.constructor = firstArg; break; case 1: firstArg.prototype = Object.create(args[0].prototype); firstArg.prototype.constructor = firstArg; break; default: for(var i = 0; i < args.length; i++) { args[i] = args[i].prototype; } args[0] = Object.create(args[0]); var secondArg = args.shift(); firstArg.prototype = Object.assign.apply(Object, args); firstArg.prototype.constructor = firstArg; } } function A(name) { this.name = name; } inheritance(A); function B() { A.apply(this, arguments); this.street = "Downing Street 10"; } inheritance(B, A); function mixin() { } inheritance(mixin); mixin.prototype.getProperties = function() { return { name: this.name, address: this.street, year: this.year }; }; function C() { B.apply(this, arguments); this.year = "2018" } inheritance(C, B, mixin); var instance = new C("Frank"); console.log(instance); console.log(instance.getProperties());
Nota
Object.create
se puede utilizar de forma segura en todos los navegadores modernos, incluido IE9 +.Object.assign
no funciona en ninguna versión de IE ni en algunos navegadores móviles. Se recomienda polyfillObject.create
y / oObject.assign
si desea utilizarlos y admitir navegadores que no los implementan.Puede encontrar un polyfill para
Object.create
aquí y uno paraObject.assign
aquí .fuente
// // try this one: // // function ParentConstructor() {} // function ChildConstructor() {} // // var // SubClass = ChildConstructor.xtendz( ParentConstructor ); // Function.prototype.xtendz = function ( SuperCtorFn ) { return ( function( Super, _slice ) { // 'freeze' host fn var baseFn = this, SubClassCtorFn; // define child ctor SubClassCtorFn = function ( /* child_ctor_parameters..., parent_ctor_parameters[] */ ) { // execute parent ctor fn on host object // pass it last ( array ) argument as parameters Super.apply( this, _slice.call( arguments, -1 )[0] ); // execute child ctor fn on host object // pass remaining arguments as parameters baseFn.apply( this, _slice.call( arguments, 0, -1 ) ); }; // establish proper prototype inheritance // 'inherit' methods SubClassCtorFn.prototype = new Super; // (re)establish child ctor ( instead of Super ctor ) SubClassCtorFn.prototype.constructor = SubClassCtorFn; // return built ctor return SubClassCtorFn; } ).call( this, SuperCtorFn, Array.prototype.slice ); }; // declare parent ctor function Sup( x1, x2 ) { this.parent_property_1 = x1; this.parent_property_2 = x2; } // define some methods on parent Sup.prototype.hello = function(){ alert(' ~ h e l l o t h e r e ~ '); }; // declare child ctor function Sub( x1, x2 ) { this.child_property_1 = x1; this.child_property_2 = x2; } var SubClass = Sub.xtendz(Sup), // get 'child class' ctor obj; // reserve last array argument for parent ctor obj = new SubClass( 97, 98, [99, 100] ); obj.hello(); console.log( obj ); console.log('obj instanceof SubClass -> ', obj instanceof SubClass ); console.log('obj.constructor === SubClass -> ', obj.constructor === SubClass ); console.log('obj instanceof Sup -> ', obj instanceof Sup ); console.log('obj instanceof Object -> ', obj instanceof Object ); // // Object {parent_property_1: 99, parent_property_2: 100, child_property_1: 97, child_property_2: 98} // obj instanceof SubClass -> true // obj.constructor === SubClass -> true // obj instanceof Sup -> true // obj instanceof Object -> true //
fuente
La forma más sencilla de utilizar la biblioteca AWeb . Muestra oficial:
/** * A-class */ var ClassA = AWeb.class({ public : { /** * A-class constructor */ constructor : function() { /* Private variable */ this.variable1 = "A"; this.calls = 0; }, /** * Function returns information about the object */ getInfo : function() { this.incCalls(); return "name=" + this.variable1 + ", calls=" + this.calls; } }, private : { /** * Private function */ incCalls : function() { this.calls++; } } }); /** * B-class */ var ClassB = AWeb.class({ extends : ClassA, public : { /** * B-class constructor */ constructor : function() { this.super(); /* Private variable */ this.variable1 = "B"; }, /** * Function returns extended information about the object */ getLongInfo : function() { return this.incCalls !== undefined ? "incCalls exists" : "incCalls undefined"; } } }); /** * Main project function */ function main() { var a = new ClassA(), b = new ClassB(); alert( "a.getInfo " + (a.getInfo ? "exists" : "undefined") + "\n" + "a.getLongInfo " + (a.getLongInfo ? "exists" : "undefined") + "\n" + "b.getInfo " + (b.getInfo ? "exists" : "undefined") + "\n" + "b.getLongInfo " + (b.getLongInfo ? "exists" : "undefined") + "\n" + "b.getInfo()=" + b.getInfo() + "\n" + "b.getLongInfo()=" + b.getLongInfo() ); }
fuente
Encontré una solución mucho más fácil que extender y hacer prototipos. En realidad, no sé qué tan eficiente es esto, aunque parece limpio y funcional.
var A = function (p) { if (p == null) p = this; p.a1 = 0; this.a2 = 0; var a3 = 0; }; var B = function (p) { if (p == null) p = this; p.b1 = new A(this); this.b2 = new A(this); var b3 = new A(this); this b4 = new A(); }; var a = new A (); var b = new B ();
resultado:
a a1 0 a2 0 b a1 0 b1 a2 0 b2 a2 0 b4 a1 0 a2 0
ejemplo práctico:
var Point = function (p) { if (p == null) p = this; var x = 0; var y = 0; p.getPoint = function () { return [x,y]; }; p.setPoint = function (_x,_y) { x = _x; y = _y; }; }; var Dimension = function (p) { if (p == null) p = this; var w = 0; var h = 0; p.getDimension = function() { return [w,h] }; p.setDimension = function(_w,_h) { w = _w; h = _h }; }; var Rect = function (p) { if (p == null) p = this; var dimension = new Dimension(this); var location = new Point(this); }; var rect = new Rect (); rect.setDimension({w:30,h:40}); rect.setPoint({x:50,y:50});
fuente