Lors de l'écriture du livre JavaScript en cours, j'ai passé beaucoup de temps sur le système d'héritage JavaScript et, ce faisant, j'ai étudié différentes solutions pour simuler l'héritage de classe classique. Parmi ces solutions techniques, l'implémentation de base2 et Prototype est celle que j'admire le plus.
De ces solutions, un cadre avec sa connotation idéologique doit être extrait. Le cadre doit être simple, réutilisable, facile à comprendre et indépendant des dépendances. La simplicité et la convivialité sont les points clés. Voici des exemples d'utilisation :
var Person = Class. extend ( { init: function (isDancing ) { this. dancing = isDancing; }, dance: function ( ) { return this. dancing; } } ); var Ninja = Person.extend({ init: function(){ this._super( false ); }, dance: function(){ // Call the inherited version of dance() return this._super(); }, swingSword: function(){ return true; } }); var p = new Person(true); p.dance(); // => true var n = new Ninja(); n.dance(); // => false n.swingSword(); // => true // Should all be true p instanceof Person && p instanceof Class && n instanceof Ninja && n instanceof Person && n instanceof Class
Il y a quelques points à noter :
Plutôt satisfait des résultats : structure de la définition de classe, maintien de l'héritage unique et capacité d'appeler des méthodes de superclasse.
Création et héritage de classes simples
Ce qui suit est sa mise en œuvre (facile à lire et comportant des commentaires), environ 25 lignes. Les suggestions sont les bienvenues et appréciées.
/* Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype ( function ( ) { var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; // The base Class implementation (does nothing) this.Class = function(){}; // Create a new Class that inherits from this class Class.extend = function(prop) { var _super = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) initializing = true; var prototype = new this(); initializing = false; // Copy the properties over onto the new prototype for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = _super[name]; // The method only need to be bound temporarily, so we // remove it when we're done executing var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } // The dummy class constructor function Class() { // All construction is actually done in the init method if ( !initializing && this.init ) this.init.apply(this, arguments); } // Populate our constructed prototype object Class.prototype = prototype; // Enforce the constructor to be what we expect Class.prototype.constructor = Class; // And make this class extendable Class.extend = arguments.callee; return Class; }; })();
Parmi eux, "initialiser/ne pas appeler init" et "créer une méthode _super" sont les plus difficiles. Ensuite, j'en donnerai une brève introduction afin que chacun puisse mieux comprendre son mécanisme de mise en œuvre.
Initialisation
Afin d'illustrer la méthode d'héritage du prototype de fonction, examinons d'abord le processus d'implémentation traditionnel, qui consiste à pointer l'attribut prototype de la sous-classe vers une instance de la classe parent. Comme indiqué ci-dessous :
function Person ( ) { } function Ninja ( ) { } Ninja. prototype = new Person ( ); // Allows for instanceof to work: (new Ninja()) instanceof Person
Cependant, le point difficile ici est que nous voulons uniquement obtenir l'effet de « instatnceOf », sans les conséquences de l'instanciation d'une personne et de l'appel de son constructeur. Pour éviter cela, définissez un paramètre bool d'initialisation dans le code, dont la valeur ne sera vraie que lorsque la classe parent sera instanciée et configurée sur la propriété prototype de la classe enfant. Le but de ce traitement est de distinguer la différence entre l'appel du constructeur lors d'une instanciation réelle et l'héritage de conception, puis appeler la méthode init lors d'une instanciation réelle :
if ( !initializing ) this.init.apply(this, arguments);
Cela mérite une attention particulière car dans la fonction init, du code assez gourmand en ressources peut être exécuté (comme la connexion au serveur, la création d'éléments DOM, etc., personne ne peut le prédire), c'est donc complètement nécessaire faire une distinction.
Super Méthode
Lors de l'utilisation de l'héritage, l'exigence la plus courante est que la sous-classe puisse accéder aux méthodes remplacées de la superclasse. Dans cette implémentation, la solution finale consiste à fournir une méthode temporaire (._super) qui pointe vers la méthode superclasse et n'est accessible que dans la méthode sous-classe.
var Person = Class. extend ( { init: function (isDancing ) { this. dancing = isDancing; } } ); var Ninja = Person.extend({ init: function(){ this._super( false ); } }); var p = new Person(true); p.dancing; // => true var n = new Ninja(); n.dancing; // => false
La mise en œuvre de cette fonctionnalité nécessite plusieurs étapes. Tout d’abord, nous utilisons extend pour fusionner l’instance de base de Person (instance de classe, dont nous avons mentionné le processus de construction ci-dessus) avec l’objet littéral (paramètre de fonction de Person.extend()). Pendant le processus de fusion, une vérification simple a été effectuée : vérifiez d'abord si l'attribut à fusionner est une fonction, si oui, vérifiez ensuite si l'attribut de superclasse à écraser est également une fonction ? Si les deux vérifications sont vraies, vous devez préparer une méthode _super pour cette propriété.
Notez qu'une fermeture anonyme (renvoyant un objet fonction) est créée ici pour encapsuler la super méthode ajoutée. En fonction de la nécessité de maintenir l'environnement d'exécution, nous devrions sauvegarder l'ancien this._super (qu'il existe ou non) pour qu'il soit réinitialisé après l'exécution de la fonction. Cela est utile dans les cas où il y a le même nom (vous ne le souhaitez pas). perdre accidentellement le pointeur d'objet) Problèmes imprévisibles.
Ensuite, créez une nouvelle méthode _super qui pointe uniquement vers la méthode remplacée dans la super classe. Dieu merci, il n'est pas nécessaire d'apporter des modifications à _super ou de changer la portée, car l'environnement d'exécution de la fonction changera automatiquement avec l'objet appelant la fonction (le pointeur pointera vers la super classe).
Enfin, appelez la méthode de l'objet littéral. This._super() peut être utilisé pendant l'exécution de la méthode, une fois la méthode exécutée, l'attribut _super est réinitialisé à son état d'origine, puis return quitte la fonction.
Il existe de nombreuses façons d'obtenir le même effet (j'ai déjà vu se lier super à lui-même, puis y accéder avec arguments.callee), mais je pense que cette méthode reflète le mieux les caractéristiques de convivialité et de simplicité.
Parmi les nombreux travaux basés sur des prototypes JavaScript que j'ai réalisés, c'est le seul plan d'implémentation de l'héritage de classe que j'ai publié pour partager avec vous. Je pense qu'un code concis (facile à apprendre, facile à hériter, moins de téléchargement) doit être proposé à tout le monde pour en discuter. Par conséquent, pour les personnes qui apprennent la construction et l'héritage de classes JavaScript, ce plan de mise en œuvre est un bon début.