Héritage JavaScript

J'essaie d'implémenter l'héritage en javascript. Je suis venu avec le code minimal suivant pour le supporter.

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

Experts, faites-le moi savoir si cela sera suffisant ou tout autre problème important que j'ai peut-être manqué. Sur la base de problèmes similaires rencontrés, veuillez suggérer d'autres modifications.

Voici un script de test complet:

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

Si vous avez besoin d'héritage, plusieurs bibliothèques offrent déjà cela. À tout le moins, lisez-les pour savoir où votre code est incorrect. Mais pourquoi réinventer? Deux superbes bibliothèques d'héritage javascript qui viennent à l'esprit sont klass et selfish.js (j'ai utilisé les deux, ils sont incroyables.)

Pour implémenter l'héritage javascript dans ECMAScript 5, vous pouvez définir le prototype d'un objet et utiliser Object.create pour hériter. Vous pouvez également ajouter / remplacer les propriétés autant que vous le souhaitez.

Exemple:

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

Je pense que la solution Crockfords est trop compliquée, tout comme John's. Il est beaucoup plus simple d'obtenir l'héritage javascript que les deux semblent décrire. Considérer:

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

J'ai écrit une description complète de la solution ci-dessus sur mon blog .

Comme j'ai joué avec des objets JS, j'ai trouvé une solution plus minimaliste 🙂 Profitez!

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

Exemple

 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 

Voici le plus simple et j'espère que la manière la plus simple de comprendre l'héritage dans JS. Le plus utile de cet exemple sera pour les programmeurs PHP.

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

Maintenant, le fils a une méthode surchargée et une nouvelle

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

Maintenant, le petit-fils a deux méthodes surchargées et une nouvelle

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

Pourquoi ne pas utiliser d'objets au lieu de fonctions:

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

Et appelez-le comme ceci:

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

Cette approche est beaucoup plus propre puis avec des fonctions. J'ai trouvé ce blog expliquant pourquoi l'héritage avec des fonctions n'est pas un bon moyen de le faire dans JS: http://davidwalsh.name/javascript-objects-deconstruction

MODIFIER

Var L'enfant peut également être écrit comme suit:

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

Voici ma solution, qui est basée sur la méthode d'héritage prototypique standard décrite dans la réponse de Lorenzo Polidori .

Tout d'abord, je commence par définir ces méthodes auxiliaires, ce qui rend les choses plus faciles à comprendre et plus lisibles plus tard:

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

Maintenant, non seulement vous pouvez définir la superclasse d'une classe avec SubClass.setSuperclass(ParentClass) , mais vous pouvez également appeler des méthodes SubClass.callSuper(this, 'functionName', [argument1, argument2...]) avec 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 

Certes, même avec les fonctions de l'assistant, la syntaxe ici est assez gênante. Heureusement cependant, dans ECMAScript 6, du sucre syntaxique ( classes maximales minimales ) a été ajouté pour rendre les choses plus jolies. Par exemple:

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

Notez que ECMAScript 6 est encore dans l'étape préliminaire à ce stade, et autant que je sais ne soit pas implémenté dans un navigateur Web majeur. Cependant, si vous le souhaitez, vous pouvez utiliser quelque chose comme le compilateur Traceur pour compiler ECMAScript 6 vers le bas de l'ancien JavaScript basé sur ECMAScript 5 . Vous pouvez voir l'exemple ci-dessus compilé à l'aide de Traceur ici .

Alors que je suis d'accord avec toutes les réponses ci-dessus, je pense que JavaScript n'a pas besoin d'être orienté objet (Évitez les héritages), mais une approche basée sur l'objet devrait être suffisante dans la plupart des cas.

J'aime la façon dont JavaScript éloquent commence son chapitre 8 sur la programmation orientée objet en parlant d'OO. Au lieu de déchiffrer la meilleure façon de mettre en œuvre l'héritage, plus d'énergie devrait être consacrée à apprendre les aspects fonctionnels du JavaScript, donc j'ai trouvé le chapitre 6 sur la programmation fonctionnelle, plus intéressant.

Qu'en est-il de cette approche 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(); 
 // // 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 // 

Le moyen le plus simple d'utiliser la bibliothèque AWeb . Exemple officiel:

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

J'ai trouvé une solution beaucoup plus facile que d'étendre et de prototypage des choses. En fait, je ne sais pas à quel point il est efficace, mais il semble propre et fonctionnel.

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

résultat:

 a a1 0 a2 0 b a1 0 b1 a2 0 b2 a2 0 b4 a1 0 a2 0 

Exemple pratique:

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