Comment implémenter les modificateurs d’access C # en javascript?

  • Résumé

    J’ai essayé de réaliser l’inheritance et l’encapsulation correctement en JavaScript, comme dans un langage de classe tel que c #.

    La partie laide est que les membres protégés ont plusieurs copies dans les instances privées qui ne sont accessibles que par le biais de la fermeture, et je n’ai aucune idée sauf de rafraîchir ces membres dans les instances privées.

    Si cela est possible, je veux me débarrasser de la transmit et du transfer dans mon code de Function.extend .

  • Mise à jour Pour les personnes intéressées par la citation ou la recherche, voici le référentiel du code source:

    https://github.com/kenkins/Function.extend

  • L’histoire

    Étant donné que les assemblages peuvent être un concept qui est hors de scope de javascript, je ne prends pas en compte le modificateur internal , mais public , protected et private .

    public modificateurs public et private ne sont pas si difficiles à réaliser; mais avec l’inheritance, la protected est très délicate. Pourtant, ce n’est pas une chose recommandée avec JavaScript, la plupart des articles que j’ai lus disent préfixe un caractère spécial et le documentent .

    Mais il semble que je persiste à faire du javascript pour simuler les langages basés sur les classes. J’ai volé cette idée et je l’ai implémentée, le code est à l’arrière de ce post.

    L’idée derrière la scène est de rendre l’accessibilité supérieure avec un prototype plus élevé et d’accéder au plus haut avec une fermeture.

    Disons que nous avons trois prototypes A , D et G , on dirait

    BANxt.png

    Comme il n’est pas possible qu’un object soit une instance d’un type également d’un autre type qui n’est pas dans la chaîne prototype; J’ai choisi de chaîner horizontalement le niveau protected et de copier les membres du prototype du type déclarant. Cela rend la classe d’imbrication possible, car les membres déclarés sur un type moins dérivé peuvent être propagés à des types plus dérivés; la méthode de transmit dans mon code consiste à le faire. Si A , D et G ont leurs propres membres protégés, cela ressemblerait à ceci:

    bhcsI.png

    La fermeture pour accéder à l’instance privée est this[''] . Il prend un argument pour identifier une classe. Le titulaire du modificateur est uniquement l’identifiant de la classe, nommé y dans Function.extend et _ dans le code de test, il ne doit pas être exposé en dehors de la déclaration de classe. Il est également utilisé comme raccourci de this[''] .

    _['base'] est en fait non seulement l’invocateur du constructeur de base, mais aussi le créateur d’instances privées. Il crée les instances privées et met à jour this[''] pour chaque constructeur avec l’inheritance, il devrait donc toujours être appelé dans les constructeurs.

    Bien qu’une instance privée ait access aux membres publics, elle ne devrait pas être utilisée pour les modifier, car this[''] n’est pas garanti que cela soit invoqué lors de l’access aux membres publics. Mais l’access à l’instance privée est; recent souvient de l’instance privée la plus récemment accédée et met à jour les membres protégés s’il y a des modifications.

    Ma question est la suivante: comment puis-je me débarrasser de ce type de rafraîchissement des membres protégés? Y a-t-il de meilleures idées pour réaliser l’encapsulation plus réaliste?

    ps: en fait, je ne veux pas d’une solution qui utilise des méthodes / propriétés non standard .. et il serait préférable qu’il y ait des polyfills si les méthodes / propriétés utilisées sont trop modernes pour les anciens navigateurs.


  • Fonction.extension

     Function.extend=function (base, factory) { factory.call(initializeClass); updateStaticMembersOfDerivedInnerClasses(y['public'].constructor); transfer(y['protected'], y['public']); return y['public'].constructor; function y($this) { return $this[''](y); } function updateStaticMembersOfDerivedInnerClasses(outer) { var member, inner; for (var key in outer) { if (Object.prototype.hasOwnProperty.call(outer, key)? (member=outer[key]) instanceof outer? outer!==(inner=member.constructor): false:false) { transfer(inner, outer); } } } function initializeInstance() { var $this=Object.create(y['private']); var derivedGet=this['']; var recent=$this; this['']=function (x) { var value=y!==x?derivedGet.call(this, x):$this; if (value!==recent) { transfer(value, recent, x['protected']); recent=value; } return value; }; base.apply(this, arguments); $this['']=this['']; } function initializeClass(derived) { y['public']=Object.create(base.prototype); y['public'].constructor=derived; if (Object.prototype.hasOwnProperty.call(base, 'transmit')) { base.transmit(y); } else { y['protected']=Object.create(y['public']); } y['private']=Object.create(y['protected']); y['base']=initializeInstance; transfer(derived, base); derived.transmit=function (x) { if (x['public'] instanceof derived) { x['protected']=Object.create(y['protected']); x['protected'].constructor=x['public'].constructor; } }; derived.prototype=y['public']; return y; } }; 
  • transfert

     function transfer(target, source, descriptor) { if (target!==source? 'undefined'!==typeof target? 'undefined'!==typeof source: false:false) { var keys='undefined'!==typeof descriptor?descriptor:source; for (var key in keys) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key]=source[key]; } } } } 
  • code de test

     'use ssortingct'; var BaseClass=Function.extend(Object, function () { var _=this(BaseClass); var NestedClass=Function.extend(BaseClass, function () { var _=this(NestedClass); function NestedClass(x, y, z) { _['base'].apply(this, arguments); _(this).Y=y; _(this).Z=z; } _['public'].SetX=function (x) { _(this).InternalSetX(x); }; _['public'].GetX=function () { return _(this).InternalGetX(); }; _['public'].GetY=function () { return _(this).Y; }; _['public'].SetZ=function (z) { _(this).Z=z; }; _['public'].GetZ=function () { return _(this).Z; }; _['private'].Y=0; }); function BaseClass(x) { _['base'].apply(this, arguments); _(this).X=x; } _['protected'].InternalSetX=function (x) { _(this).X=x; }; _['protected'].InternalGetX=function () { return _(this).X; }; _['private'].X=0; _['protected'].Z=0; BaseClass.Sample=new NestedClass(1, 2, 3); }); var DerivedClass=Function.extend(BaseClass, function () { var _=this(DerivedClass); function DerivedClass(x, y, z) { _['base'].apply(this, arguments); } }); var o=DerivedClass.Sample; alert(o.GetX()); alert(o.GetY()); alert(o.GetZ()); o.SetX(3); o.SetZ(1); alert(o.GetX()); alert(o.GetY()); alert(o.GetZ()); 

    J’ai aussi eu une pensée similaire et j’ai décidé d’essayer d’écrire quelque chose. Une solution vanille Encore tôt mais j’aime ce qui en est ressorti. Vous pourriez trouver cela intéressant aussi.

    Ce n’est pas exactement c # mais fournit un écosystème plus ssortingct. Et d’autres fonctionnalités avancées de js dans une solution légère.

    https://github.com/iamlothian/rucksack.js

    Ce n’est pas une solution à votre code, mais une solution à votre concept. Si votre objective était de faire en sorte que votre idée fonctionne, alors continuez comme je suis intéressé par le résultat.

    Si vous aimez que je veuille juste un environnement js plus structuré, alors voici celui que j’ai écrit avec une ambition similaire à vos concepts de questions.

    Partie 2:

    L’idée ici est d’utiliser la ressortingction de fermeture et d’access pour créer un modèle qui restreint la façon dont le code peut être utilisé et modifié après la définition du terme. Le gros du travail a été accompli en grande partie. Mais le modèle est laissé à vous définir.

    Voici un exemple rapide de démonstration démontrant comment vous pouvez implémenter un inheritance public | protect | private. J’essaie de décider du temps, j’implémente une partie de ceci comme une fonctionnalité intégrée ou laisse aux utilisateurs le soin d’implémenter leur propre extension d’object, comme dans l’exemple.

    http://plnkr.co/edit/ao2hTyBV1b3nYIwr7ZS5

    L’implémentation est dans scripts.js. visualisez votre console pour voir ce qui se passe.

    Ce que le sac à dos fournit est un cadre pour créer des modules de code détachés. Ces modules sont regroupés dans des espaces de noms et peuvent dépendre les uns des autres. Ces dépendances sont résolues paresseusement comme défini, de sorte que l’ordre des définitions n’est pas vraiment important. Le processus de résolution fournit d’autres fonctionnalités utiles, telles que la correspondance des interfaces et le module scellé.

    caractéristiques actuelles:

    • Modulaire
    • Injection de dépendance
    • Constructeur d’usine (object Instances)
    • Constructeur de services (objects statiques)
    • Chargement paresseux
    • Enregistrement des erreurs facile (toutes les erreurs dans les modules sont capturées et peuvent être transmises)
    • Espaces de noms
    • Modules scellables et espaces de noms (modules inaccessibles en dehors de l’espace de noms)
    • Événement d’attente global pour le module
    • Interface pour l’object de configuration optionnel
    • Vérification de l’interface ssortingcte facultative pour l’injection

    Bien que le code avec fermeture puisse résoudre ce que vous voulez, j’irais avec les méthodes Privileged plus simples comme Crockford les a appelées ici .

    L’idée d’utilisation est simple:

    • Définir la méthode privilégiée sur l’object de base (avec la limite 1 – permet d’être appelé une seule fois).
    • La méthode privilégiée retourne une interface protégée de lui-même (d’un object de base) qui contient des fonctions protégées (ces fonctions sont probablement définies en privé dans la base, puis copiées sur l’object d’interface protégé … ou peut-être que l’interface protégée existe en privé).
    • Chaque object étend son interface protégée avec l’interface protégée de son object de base et l’expose encore par le biais de la méthode privilégiée.

    Vous allez vous retrouver avec quelque chose comme ça:

     function A() { var protected = { protectedA: function() { } }; this.getProtected = (function() { var allow = true; //privileged function. return function() { if (allow) { allow = false; return protected; } }; }); } //B - derives from (extends) A function B() { var base = {}; //acquiring a base omitted - depends on your implementation. var protected = { protectedB: function() { } }; //"extend" simply copies new members into protected: protected = $.extend(protected, base.getProtected()); this.getProtected = function() { /* privileged function - similar to A.getProtected */ }; } 

    JavaScript a des capacités limitées dans cette mesure, de sorte que le sucre protected un coût quand même.

    Le javascript est un langage large, car vous pouvez faire presque toutes les choses que vous voulez dans une page Web, simplement en créant des fonctions et en trouvant des moyens de le faire.

    Je peux vous dire que JavaScript n’est pas un langage sécurisé, car vous pouvez facilement accéder à la plupart des variables et des fonctions, les lire et savoir comment cela fonctionne, simplement en accédant au fichier .js, inclus dans la page.

    Ma pensée: Certains modificateurs d’access n’ont pas été créés pour être utilisés en JavaScript car les développeurs savent déjà que cela peut être inutile, car JavaScript ne “voyage” pas dans d’autres endroits (pages), sauf si vous utilisez une variable de session.

    Et à propos de ces modificateurs:

    • Privé

    • Protégé

    • Publique

    Je peux vous dire que je connais certains modificateurs javascript qui leur ressemblent, à savoir:

    Local:

    var Variable = 0;

    Automatiquement, ceci est converti en une variable Integer, car il reçoit une valeur Integer, et aussi, c’est une variable LOCAL à cause du modificateur var qui déclare cette variable de telle manière que vous ne pouvez pas accéder à sa valeur, sauf si vous sont à l’intérieur de la même fonction que cette variable a été déclarée.

    Exemple:

    Si vous déclarez ces fonctions de cette façon, avec les modificateurs par défaut:

     function conflict(){ i = 2; changeI(); alert(i); } function changeI(){ i = 0; } 

    Dans ce cas, le i est la même variable pour les deux fonctions.

    Donc, si vous exécutez un conflict(); vous aurez une alerte résultant 0 .

    MAIS, si vous déclarez i utilisant le modificateur var :

     function conflict(){ var i = 2; changeI(); alert(i); } function changeI(){ var i = 0; } 

    Dans ce cas, vous avez deux variables i , car elles ne peuvent être utilisées que dans leur fonction, donc si vous exécutez un conflict(); maintenant, vous recevrez une alerte avec une valeur de 2 .

    Variable de classe:

    this.Variable = "a";

    Cette variable est automatiquement une chaîne, car elle reçoit une valeur de chaîne. Vous savez probablement déjà ce que this modificateur fait, mais j’essayerai d’expliquer avec mon sharepoint vue, c’est-à-dire que cette variable provient de la SuperClasse ou de javascript un “SuperFunction” qui peut être appelé une classe, ou en d’autres termes, la classe “père”.

    Un exemple:

     function TClass() { this.getVar = function() { try { return "test"; } catch(err) { return false; } } this.alertVar = function() { try { alert(this.getVar()); } catch(err) { alert('error'); } } } var $Class = new TClass(); 

    Comme vous le voyez ci-dessus, j’ai créé une classe TClass et des variables contenant des fonctions (fermeture javascript) et ajouté le modificateur this. à eux, pour les rendre liés à la TClass et comme vous le voyez sur la fonction alertVar() , alert(this.getVar()); à l’ alert(this.getVar()); la fonction qui provient du TClass est la même dans ce contexte.

    Et cette partie: var $Class = new TClass(); Je crée la classe comme vous le saviez probablement, pour avoir access à ses méthodes, ce que je suis capable d’exécuter, de tester:

     $Class.alertVar(); 

    Et comme résultat, une boîte d’alerte contenant “test”, comme vous pouvez le voir:

    l'alerte de test

    Notez que vous ne pouvez pas accéder aux méthodes TClass d’une autre manière, vous ne pouvez y accéder qu’en créant la classe et en y accédant.

    J’espère donc que vous avez compris la facilité d’utilisation de this modificateur.

    Global:

    window.Variable = true;

    JavaScript automatiquement déclare que cette variable est une valeur booléenne, car elle reçoit une valeur booléenne. Le modificateur de window tel qu’il est dit, vous pouvez l’accéder à la fenêtre que vous êtes, car les variables javascript lorsqu’elles sont déclarées, elles vont dans le DOM dans la fenêtre, voyez ce qui est DOM:

    DOM (Document Object Model): Le DOM, multi-plateforme, représente la manière dont les balises html, xhtml et xml sont organisées et lues par le navigateur que vous utilisez. En d’autres termes, si vous accédez au DOM, vous pouvez voir chaque propriété, chaque variable ou toute chose existant sur le navigateur.

    Différentes des autres variables, les variables de la window peuvent avoir une autre valeur et accéder à la valeur réelle, quelle que soit votre position, dans une fonction ou non, dans un fichier js ou non.

    Exemple de Global (fenêtre):

    Exécutez sur l’événement onLoad d’une page un code qui déclare une variable de window ou déclarez-le vous-même à l’aide de la console du navigateur:

     window.Variable = true; 

    Ensuite, ajoutez un fichier JS contenant cette fonction ou créez-le simplement en exécutant le code sur la console du navigateur:

     function testGlobalVar(){ if (Variable) alert("it works!"); } 

    Lorsque vous exécutez testGlobalVar() vous recevez l’alerte, mais c’est uniquement parce que vous l’avez déclarée comme ‘window’ sinon, vous n’obtiendrez rien.

    Modificateur par défaut:

    Variable = 0.5

    Automatiquement, cette variable est déclarée comme flottante car elle reçoit une valeur flottante. Je ne sais pas si vous savez déjà, mais les variables javascript déclarées comme d’habitude ont un modificateur par défaut qui rend la variable similaire aux variables de la window , mais vous ne pouvez pas y accéder, mais dans la plupart des cas, vous pouvez y accéder , je ne connais pas tous les cas où vous ne pouvez pas y accéder, mais je sais que vous ne pouvez pas quand vous avez chargé un fichier js et qu’il a été déclaré à l’intérieur. Seulement si vous exécutez une fonction qui le déclare et que vous essayez ensuite d’accéder.

    Au fait, je vois que vous voulez connaître les modificateurs qui correspondent aux trois que vous avez dit, mais à mon avis certains des modificateurs que je vous ai dit peuvent être utilisés pour faire la même chose que vos modificateurs c #.

    J’espère que vous comprenez ce que je dis.

    Ah, et si vous étiez confus quand vous avez vu une fonction dans une variable, étudiez les fermetures Javascript, vous comprendrez après ça :).

    Comment les classes parent et enfant interagissent les unes avec les autres

    • Une classe enfant étendue appelle super.call , une fonction qui construit une instance de son parent.

    • Une classe parente partage ses membres protégés (à la fois des champs et des fonctions) avec la sous-classe en extension en utilisant this.share dans son constructeur.

    • Une sous-classe peut également appeler super.fetch() , qui renvoie l’object des champs / fonctions que la classe parente a transmis à this.share


    Pour illustrer ma technique, le code suivant montre quelques éléments essentiels à la POO avec un exemple simple d’une class Dog extends Animal

    Quelques fonctions de base pour ce modèle orienté object

     // runs in both node.js and browser var global_namespace = ('undefined'==typeof module)? window: global; // put a no-operation function in the value for `share` in case nothing is extending a class var not_extendable = {share:function(){}}; // when something is extending a class... var extendable = function(constructor) { // create a space for protected fields var protected_space = {}; // the following is what will get passed as `this` to the parent constructor var sharing = { share: function(fields) { protected_space = fields; }, }; // the following is what will get passed as the first arg to the child constructor return { // enables child to call its parent's constructor call: function(args) { return constructor.apply(sharing, args); }, // allows child to access protected fields shared by its parent fetch: function() { return protected_space; }, }; }; 

    Classe d’ Animal

     // class definition for `Animal` (function(namespace) { // construct an instance of this class var constructor = function(name, weight, info) { // private fields var color = (info && info.color) || 'unknown'; // for protected fields var protect = { weight: weight, noise: function() { return 'nothing'; }, }; // share the protected fields with any subclass that might be extending this this.share(protect); // public fields and methods return { speak: function() { console.log(name+' says '+protect.noise()); }, feed: function() { console.log(name+' is not hungry'); }, weigh: function() { console.log(name+' weighs '+protect.weight+' lbs'); }, toSsortingng: function() { return '{Animal}'; }, }; }; // handle calls to: `Animal()` namespace.Animal = function() { // called with new operator: `new Animal(...)` if(this !== namespace) { // construct simple instance of this class return constructor.apply(not_extendable, arguments); } // static call: `Animal(...)`, means the caller wants to extend this class else { // reference child constructor var child_constructor = arguments[0]; // return a wrapped constructor function return function() { // call child constructor and allow it to call the super constructor return child_constructor.apply({}, [extendable(constructor), arguments]); }; } }; })(global_namespace); 

    Classe de Dog

     // class definition for `Dog` (function(namespace) { // class `Dog` extends class `Animal` var constructor = Animal(function(super_class, args) { // private field var been_fed = false; // call super's constructor var operator = super_class.call(args); // inherit parent's protected members var parent = super_class.fetch(); // override a protected method parent.noise = function() { return 'bark!'; }; // override a public method operator.feed = function() { been_fed = true; parent.weight += 5; }; // extend a public method var super_weigh = operator.weigh; operator.weigh = function() { super_weigh(); if(been_fed) console.log('\t'+args[0]+' has been eating :)'); else console.log('\t'+args[0]+' has not been fed yet'); }; // override another public method operator.toSsortingng = function() { return '{Dog}'; }, // return the operator (interfacable instance object) return operator; }); // handle calls to: `Dog()` namespace.Dog = function() { // called with new operator: `new Dog()` if(this !== namespace) { return constructor.apply(this, arguments); } // static call: `Dog()` else { // we do no allow extending class `Dog` return false; } }; })(global_namespace); 

    Alors maintenant, nous pouvons le faire:

     var giraffe = new Animal('Mr. Giraffe', 720); giraffe.speak(); // "Mr. Giraffe says nothing" giraffe.weigh(); // "Mr. Giraffe weighs 720 lbs" var buddy = new Dog('Buddy', 50); buddy.speak(); // "Buddy says bark!" buddy.weigh(); // "Buddy weighs 50 lbs" // "Buddy has not been fed yet" buddy.feed(); buddy.weigh(); // "Buddy weighs 55 lbs" // "Buddy has been eating :)" 

    Cela permet des champs / fonctions privés, protégés et publics. Les champs / fonctions protégés et publics peuvent être écrasés et étendus.

     console.log(giraffe); // "{Animal}" console.log(buddy); // "{Dog}" 

    J’ai travaillé sur un autre projet JavaScript intéressant et mis en œuvre quelque chose qui pourrait être plus proche de ce que vous recherchez.

    Implement.js

    Intéressé par vos pensées.