Herança JavaScript

Eu estou tentando implementar a inheritance em javascript. Eu criei um código mínimo para suportá-lo.

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; } 

Especialistas, por favor, deixe-me saber se isso será suficiente ou qualquer outra questão importante que eu possa ter perdido. Com base em problemas semelhantes enfrentados, sugira outras alterações.

Aqui está o script de teste 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); 

Para implementar a inheritance de javascript no ECMAScript 5, você pode definir o protótipo de um object e usar Object.create para herdar. Você também pode adicionar / replace propriedades tanto quanto desejar.

Exemplo:

 /** * 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 

Eu acho que a solução de Crockfords é muito complicada, assim como a de John. É muito mais simples obter inheritance de javascript do que os dois parecem descrever. 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) 

Eu escrevi uma descrição completa da solução acima no meu blog .

Enquanto jogava com objects JS, encontrei uma solução mais minimalista 🙂 Divirta-se!

 function extend(b,a,t,p) { b.prototype = a; a.apply(t,p); } 

Exemplo

 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 

Aqui é o mais simples e espero que a maneira mais fácil de entender a inheritance em JS. Mais útil este exemplo será para programadores PHP.

 function Mother(){ this.canSwim = function(){ console.log('yes'); } } function Son(){}; Son.prototype = new Mother; Son.prototype.canRun = function(){ console.log('yes'); } 

Agora o filho tem um método substituído e um novo

 function Grandson(){} Grandson.prototype = new Son; Grandson.prototype.canPlayPiano = function(){ console.log('yes'); }; Grandson.prototype.canSwim = function(){ console.log('no'); } 

Agora o neto tem dois methods substituídos e um novo

 var g = new Grandson; g.canRun(); // => yes g.canPlayPiano(); // => yes g.canSwim(); // => no 

Por que não usar objects em vez de funções:

 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(); }; 

E chame assim:

 var child1 = Object.create(Child1); child1.alert(); 

Essa abordagem é muito mais limpa do que com funções. Eu encontrei este blog explicando porque a inheritance com funções não é uma maneira correta de fazer isso no JS: http://davidwalsh.name/javascript-objects-deconstruction

EDITAR

var Child também pode ser escrito 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); }} }); 

Aqui está a minha solução, que é baseada no método padrão de inheritance prototípica descrito na resposta de Lorenzo Polidori .

Primeiro, começo definindo esses methods auxiliares, que tornam as coisas mais fáceis de entender e mais fáceis de ler depois:

 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); }; 

Agora, não só você pode definir a superclass de uma class com SubClass.setSuperclass(ParentClass) , mas você também pode chamar methods substituídos com SubClass.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 

É certo que, mesmo com as funções auxiliares, a syntax aqui é bem desajeitada. Felizmente, no ECMAScript 6 alguns acréscimos sintáticos ( maximamente classs mínimas ) foram adicionados para tornar as coisas muito mais bonitas. Por exemplo:

 /** * 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 

Observe que o ECMAScript 6 ainda está no estágio de rascunho neste momento e, até onde eu sei, não está implementado em nenhum grande navegador da web. No entanto, se você quiser, pode usar algo como o compilador Traceur para compilar o ECMAScript 6 para o JavaScript simples baseado no ECMAScript 5 . Você pode ver o exemplo acima compilado usando o Traceur aqui .

Embora eu concorde com todas as respostas acima, sinto que o JavaScript não precisa ser Orientado a Objetos, (Evite inheritance), em vez disso, uma abordagem baseada em object deve ser suficiente na maioria dos casos.

Eu gosto do modo como o Eloquent JavaScript inicia seu Capítulo 8 sobre programação orientada a objects falando sobre OO. Em vez de decifrar a melhor maneira de implementar a Herança, mais energia deve ser dedicada a aprender aspectos funcionais do JavaScript, portanto, achei o Capítulo 6 sobre Programação Funcional, mais interessante.

 //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; } 

Como sobre esta abordagem simples

  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(); 

Herança prototípica básica

Uma maneira simples, mas eficaz de fazer inheritance em JavaScript, é usar o seguinte two-liner:

 B.prototype = Object.create(A.prototype); B.prototype.constructor = B; 

Isso é semelhante a fazer isso:

 B.prototype = new A(); 

A principal diferença entre ambos é que o construtor de A não é executado ao usar Object.create , que é mais intuitivo e mais semelhante à inheritance baseada em classs.

Você sempre pode optar por executar opcionalmente o construtor de A ao criar uma nova instância de B adicionando-a ao construtor de B :

 function B(arg1, arg2) { A(arg1, arg2); // This is optional } 

Se você quiser passar todos os argumentos de B para A , você também pode usar Function.prototype.apply() :

 function B() { A.apply(this, arguments); // This is optional } 

Se você quiser misturar outro object na cadeia de construtores de B , você pode combinar Object.assign com Object.assign :

 B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype); B.prototype.constructor = B; 

Demonstração

 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()); 
 // // 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(' ~ hellothere ~ '); }; // 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 // 

A maneira mais fácil de usar a biblioteca AWeb . Amostra 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() ); } 

Eu encontrei uma solução muito mais fácil do que estender e prototipar coisas. Na verdade, não sei o quão eficiente isso é, embora pareça limpo e 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 

exemplo prático:

 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});