web-dev-qa-db-fra.com

Le modèle OLOO de Kyle Simpson contre un modèle de conception de prototype

Est-ce que le motif "OLOO" de Kyle Simpson est différent du motif de conception Prototype? Mis à part le fait que quelque chose indique spécifiquement "lier" (le comportement des prototypes) et clarifie qu'il n'y a pas de "copie" qui se passe ici (un comportement de classes), qu'est-ce que son modèle introduit exactement?

Voici n exemple du modèle de Kyle de son livre, "Vous ne savez pas JS: cet objet prototypes":

var Foo = {
    init: function(who) {
        this.me = who;
    },
    identify: function() {
        return "I am " + this.me;
    }
};

var Bar = Object.create(Foo);

Bar.speak = function() {
    alert("Hello, " + this.identify() + ".");
};

var b1 = Object.create(Bar);
b1.init("b1");
var b2 = Object.create(Bar);
b2.init("b2");

b1.speak(); // alerts: "Hello, I am b1."
b2.speak(); // alerts: "Hello, I am b2."
101
shmuli

qu'est-ce que son motif introduit exactement?

OLOO englobe la chaîne de prototypes telle quelle, sans qu'il soit nécessaire d'appliquer une autre sémantique (source de confusion pour IMO) pour obtenir le lien.

Ainsi, ces deux extraits ont exactement le même résultat, mais y arriver différemment.

Forme du constructeur:

function Foo() {}
Foo.prototype.y = 11;

function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.z = 31;

var x = new Bar();
x.y + x.z;  // 42

Formulaire OLOO:

var FooObj = { y: 11 };

var BarObj = Object.create(FooObj);
BarObj.z = 31;

var x = Object.create(BarObj);
x.y + x.z;  // 42

Dans les deux extraits, un objet x est [[Prototype]]- lié à un objet (Bar.prototype ou BarObj), qui est à son tour lié à un troisième objet (Foo.prototype ou FooObj).

Les relations et la délégation sont identiques entre les extraits. L'utilisation de la mémoire est identique entre les extraits. La possibilité de créer de nombreux "enfants" (alias de nombreux objets comme x1 à travers x1000, etc.) est identique entre les extraits. La performance de la délégation (x.y et x.z) est identique entre les extraits. La performance de création d'objet est plus lente avec OLOO, mais vérifier si cela est vrai révèle que les performances plus lentes ne sont vraiment pas un problème.

Selon moi, OLOO propose qu’il est beaucoup plus simple de simplement exprimer les objets et de les lier directement, que de les lier indirectement par le biais du mécanisme constructeur/new. Ce dernier prétend être sur les classes mais est vraiment une syntaxe terrible pour exprimer la délégation ( note en bas de page: est donc la syntaxe ES6 class! ).

OLOO est en train de couper l'homme moyen.

Voici ne autre comparaison de class vs OLOO.

141
Kyle Simpson

J'ai lu le livre de Kyle et je l'ai trouvé vraiment instructif, en particulier les détails sur la manière dont this est lié.

Avantages:

Pour moi, il y a quelques gros pros de OLOO:

1. simplicité

OLOO s'appuie sur Object.create() pour créer un nouvel objet lié [[prototype]] À un autre objet. Vous n'avez pas à comprendre que les fonctions ont une propriété prototype ni à vous inquiéter des pièges potentiels liés à sa modification.

2. Syntaxe de nettoyage

Ceci est discutable, mais j'estime que la syntaxe OLOO est (dans de nombreux cas) plus précise et plus concise que l'approche javascript "standard", en particulier en ce qui concerne le polymorphisme (appels de style super-).

Les inconvénients:

Je pense qu'il y a un aspect discutable de la conception (celui qui contribue réellement au point 2 ci-dessus), et qui concerne l'observation:

Dans la délégation de comportement, nous évitons autant que possible de nommer les choses de la même manière à différents niveaux de la chaîne [[Prototype]].

L'idée sous-jacente est que les objets ont leurs propres fonctions plus spécifiques qui sont ensuite déléguées en interne à des fonctions situées plus bas dans la chaîne. Par exemple, vous pouvez avoir un objet resource avec une fonction save() qui envoie une version JSON de l'objet au serveur, mais vous pouvez également avoir un objet clientResource objet qui a une fonction stripAndSave(), qui supprime tout d'abord les propriétés qui ne devraient pas être envoyées au serveur.

Le problème potentiel est le suivant: si quelqu'un d'autre arrive et décide de créer un objet specialResource, ne connaissant pas parfaitement la chaîne de prototypes, il pourrait raisonnablement * décider de sauvegarder un horodatage pour la dernière sauvegarde sous une propriété appelée save, qui ombrage la fonctionnalité save() de base sur l'objet resource, deux liens vers le bas de la chaîne du prototype:

var resource = {
  save: function () { 
    console.log('Saving');
  }
};

var clientResource = Object.create(resource);

clientResource.stripAndSave = function () {
  // Do something else, then delegate
  console.log('Stripping unwanted properties');
  this.save();
};

var specialResource = Object.create( clientResource );

specialResource.timeStampedSave = function () {
  // Set the timestamp of the last save
  this.save = Date.now();
  this.stripAndSave();
};

a = Object.create(clientResource);
b = Object.create(specialResource);

a.stripAndSave();    // "Stripping unwanted properties" & "Saving".
b.timeStampedSave(); // Error!

Ceci est un exemple particulièrement artificiel, mais le fait est que spécifiquement not ombrer d’autres propriétés peut conduire à des situations délicates et à une utilisation intensive d’un thésaurus!

Peut-être une meilleure illustration de ceci serait une méthode init - particulièrement poignante en tant que fonctions de type constructeur OOLO sidesteps. Etant donné que chaque objet associé nécessitera probablement une telle fonction, il peut être fastidieux de le nommer de manière appropriée et son caractère unique peut rendre difficile de se rappeler lequel utiliser.

* En fait, ce n'est pas particulièrement raisonnable (lastSaved serait beaucoup mieux, mais ce n'est qu'un exemple.)

24
Ed Hinchliffe

La discussion dans "Vous ne savez pas JS: This & Object Prototypes" et la présentation de OLOO sont une source de réflexion et j'ai appris énormément en parcourant le livre. Les avantages du modèle OLOO sont bien décrits dans les autres réponses. cependant, j'ai les plaintes d'animaux suivantes avec elle (ou je manque quelque chose qui m'empêche de l'appliquer efficacement):

1

Lorsqu'une "classe" "hérite d'une" autre "classe dans le modèle classique, les deux fonctions peuvent être déclarées comme une syntaxe similaire ( " déclaration de fonction "ou" instruction de fonction " ):

function Point(x,y) {
    this.x = x;
    this.y = y;
};

function Point3D(x,y,z) {
    Point.call(this, x,y);
    this.z = z;
};

Point3D.prototype = Object.create(Point.prototype);

En revanche, dans le modèle OLOO, différentes formes syntaxiques permettent de définir la base et les objets dérivés:

var Point = {
    init  : function(x,y) {
        this.x = x;
        this.y = y;
    }
};


var Point3D = Object.create(Point);
Point3D.init = function(x,y,z) {
    Point.init.call(this, x, y);
    this.z = z;
};

Comme vous pouvez le voir dans l'exemple ci-dessus, l'objet de base peut être défini à l'aide de la notation littérale d'objet, alors que la même notation ne peut pas être utilisée pour l'objet dérivé. Cette asymétrie me dérange.

2

Dans le modèle OLOO, la création d'un objet se fait en deux étapes:

  1. appel Object.create
  2. appelez une méthode personnalisée, non standard, pour initialiser l’objet (que vous devez vous rappeler car elle peut varier d’un objet à l’autre):

     var p2a = Object.create(Point);
    
     p2a.init(1,1);
    

En revanche, dans le modèle Prototype, vous utilisez l'opérateur standard new:

var p2a = new Point(1,1);

3

Dans le modèle classique, je peux créer des fonctions utilitaires "statiques" qui ne s'appliquent pas directement à un "instant" en les affectant directement à la fonction "classe" (par opposition à sa fonction .prototype). Par exemple. comme la fonction square dans le code ci-dessous:

Point.square = function(x) {return x*x;};

Point.prototype.length = function() {
    return Math.sqrt(Point.square(this.x)+Point.square(this.y));
};

En revanche, dans le modèle OLOO, toutes les fonctions "statiques" sont également disponibles (via la chaîne [[prototype]]) sur les instances d'objet:

var Point = {
    init  : function(x,y) {
        this.x = x;
        this.y = y;
    },
    square: function(x) {return x*x;},
    length: function() {return Math.sqrt(Point.square(this.x)+Point.square(this.y));}
};
13

"J'ai pensé que cela rend chaque obj dépendant de l'autre"

Comme Kyle l'explique quand deux objets sont [[Prototype]] liés, ils ne sont pas vraiment dépendants les uns des autres; au lieu de cela ils sont objet individuel. Vous reliez un objet à l'autre avec un [[Prototype]] _ lien que vous pouvez modifier à tout moment. Si vous prenez deux [[Prototype]] Les objets liés créés par le style OLOO étant dépendants les uns des autres, vous devriez également penser de la même manière à ceux créés via les appels constructor.

var foo= {},
    bar= Object.create(foo),
    baz= Object.create(bar);


console.log(Object.getPrototypeOf(foo)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //foo

console.log(Object.getPrototypeOf(baz)) //bar

Maintenant, réfléchissez une seconde. Pensez-vous que foobar et baz dépendent l'un de l'autre?

Faisons maintenant la même chose avec ce code de style constructor

function Foo() {}

function Bar() {}

function Baz() {}

Bar.prototype= Object.create(Foo);
Baz.prototype= Object.create(Bar);

var foo= new Foo(),
    bar= new Bar().
    baz= new Baz();

console.log(Object.getPrototypeOf(foo)) //Foo.prototype
console.log(Object.getPrototypeOf(Foo.prototype)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //Bar.prototype
console.log(Object.getPrototypeOf(Bar.prototype)) //Foo.prototype

console.log(Object.getPrototypeOf(baz)) //Baz.prototype
console.log(Object.getPrototypeOf(Baz.prototype)) //Bar.prototype

La seule différence entre ce dernier et l'ancien code est que, dans le dernier, foo, bar, baz bbjects sont liés les uns aux autres par le biais d'objets de leur choix. constructor fonction (Foo.prototype, Bar.prototype, Baz.prototype) mais dans le premier (OLOO style), ils sont directement liés. Dans les deux cas, vous ne faites que lier foo, bar, baz, directement dans le premier et indirectement dans le second. Mais, dans les deux cas, les objets sont indépendants l’un de l’autre car ce n’est pas vraiment une instance d’une classe qui, une fois instanciée, ne peut hériter d’une autre classe. Vous pouvez toujours changer l'objet qu'un objet devrait également déléguer.

var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);

Donc, ils sont tous indépendants les uns des autres.

"J'espérais que OLOO résoudrait le problème selon lequel chaque objet ne sait rien de l'autre."

Oui c'est bien possible-

Utilisons Tech comme objet utilitaire.

 var Tech= {
     tag: "technology",
     setName= function(name) {
              this.name= name;
}
}

créer autant d'objets que vous le souhaitez liés à Tech-

var html= Object.create(Tech),
     css= Object.create(Tech),
     js= Object.create(Tech);

Some checking (avoiding console.log)- 

    html.isPrototypeOf(css); //false
    html.isPrototypeOf(js); //false

    css.isPrototypeOf(html); //false
    css.isPrototypeOf(js); //false

    js.isPrototypeOf(html); //false
    js.isPrototypwOf(css); //false

    Tech.isPrototypeOf(html); //true
    Tech.isPrototypeOf(css); //true
    Tech.isPrototypeOf(js); //true

Pensez-vous que les objets html, css, js sont connectés les uns aux autres? Non, ils ne sont pas. Voyons maintenant comment nous aurions pu faire cela avec constructor function-

function Tech() { }

Tech.prototype.tag= "technology";

Tech.prototype.setName=  function(name) {
              this.name= name;
}

créer autant d'objets que vous le souhaitez liés à Tech.proptotype-

var html= new Tech(),
     css= new Tech(),
      js= new Tech();

Quelques vérifications (en évitant console.log) -

html.isPrototypeOf(css); //false
html.isPrototypeOf(js); //false

css.isPrototypeOf(html); //false
css.isPrototypeOf(js); //false

js.isPrototypeOf(html); //false
js.isPrototypeOf(css); //false

Tech.prototype.isPrototypeOf(html); //true
Tech.prototype.isPrototypeOf(css); //true
Tech.prototype.isPrototypeOf(js); //true

Comment pensez-vous que ces objets de style constructorhtml, css, js) diffèrent du code de style OLOO-? En fait, ils ont le même objectif. Dans OLOO- style, un des objets délègue à Tech (la délégation a été définie explicitement) alors que dans constructor- un des objets subit un délégué à Tech.prototype _ (la délégation était définie implicitement). En fin de compte, vous liez les trois objets, sans lien entre eux, à un objet, directement en utilisant le style OLOO, indirectement en utilisant le style constructor.

"En l'état, ObjB doit être créé à partir d'ObjA .. Object.create (ObjB) etc."

Non, ObjB ici ne ressemble pas à une instance (dans les langages classiques) de toute classe ObjA. --- (On peut dire que objB objet devient délégué à ObjA lors de sa création ". Si vous aviez utilisé le constructeur, vous auriez fait le même" couplage " , bien qu'indirectement en utilisant .prototypes.

5
Abhishek Sachan

@Marcus @bholben

Peut-être que nous pouvons faire quelque chose comme ça.

    const Point = {

        statics(m) { if (this !== Point) { throw Error(m); }},

        create (x, y) {
            this.statics();
            var P = Object.create(Point);
            P.init(x, y);
            return P;
        },

        init(x=0, y=0) {
            this.x = x;
            this.y = y;
        }
    };


    const Point3D = {

        __proto__: Point,

        statics(m) { if (this !== Point3D) { throw Error(m); }},

        create (x, y, z) {
            this.statics();
            var P = Object.create(Point3D);
            P.init(x, y, z);
            return P;
        },

        init (x=0, y=0, z=0) {
            super.init(x, y);
            this.z = z;
        }
    }; 

Bien sûr, créer un objet Point3D lié au prototype d'un objet Point2D est un peu ridicule, mais c'est tout à fait pertinent (je voulais être cohérent avec votre exemple). Quoi qu'il en soit, en ce qui concerne les plaintes:

  1. L'asymétrie peut être corrigée avec l'ES6 Object.setPrototypeOf ou le plus mal vu sur __proto__ = ... Que j'utilise. Nous pouvons également utiliser super sur des objets réguliers également, comme indiqué dans Point3D.init(). Une autre façon serait de faire quelque chose comme

    const Point3D = Object.assign(Object.create(Point), {  
        ...  
    }   
    

    bien que je n'aime pas particulièrement la syntaxe.


  1. On peut toujours envelopper p = Object.create(Point) puis p.init() dans un constructeur. par exemple. Point.create(x,y). En utilisant le code ci-dessus, nous pouvons créer une "instance" Point3D De la manière suivante.

    var b = Point3D.create(1,2,3);
    console.log(b);                         // { x:1, y:2, z:3 }
    console.log(Point.isPrototypeOf(b));    // true
    console.log(Point3D.isPrototypeOf(b))   // true
    

  1. Je viens de proposer ce hack pour imiter les méthodes statiques dans OLOO. Je ne sais pas si je l'aime ou non. Cela nécessite d'appeler une propriété spéciale au sommet de toute méthode "statique". Par exemple, j'ai rendu la méthode Point.create() statique.

        var p = Point.create(1,2);
        var q = p.create(4,1);          // Error!  
    

Alternativement, avec ES6 Symboles vous pouvez étendre en toute sécurité les classes de base Javascript. Ainsi, vous pourriez économiser du code et définir la propriété spéciale sur Object.prototype. Par exemple,

    const extendedJS = {};  

    ( function(extension) {

        const statics = Symbol('static');

        Object.defineProperty(Object.prototype, statics, {
            writable: true,
            enumerable: false,
            configurable: true,
            value(obj, message) {
                if (this !== obj)
                    throw Error(message);
            }
        });

        Object.assign(extension, {statics});

    })(extendedJS);


    const Point = {
        create (x, y) {
            this[extendedJS.statics](Point);
            ...

3
Andrew Szymczak

@james emanon - Vous faites donc référence à l'héritage multiple (voir page 75 du livre "Vous ne savez pas JS: this & Object Prototypes"). Et ce mécanisme que nous pouvons trouver dans la fonction "extend" du trait de soulignement, par exemple. Les noms d'objet que vous avez indiqué dans votre exemple sont un peu un mélange de pommes, d'oranges et de bonbons, mais je comprends le problème. D'après mon expérience, ce serait la version OOLO:

var ObjA = {
  setA: function(a) {
    this.a = a;
  },
  outputA: function() {
    console.log("Invoking outputA - A: ", this.a);
  }
};

// 'ObjB' links/delegates to 'ObjA'
var ObjB = Object.create( ObjA );

ObjB.setB = function(b) {
   this.b = b;
}

ObjB.setA_B = function(a, b) {
    this.setA( a ); // This is obvious. 'setA' is not found in 'ObjB' so by prototype chain it's found in 'ObjA'
    this.setB( b );
    console.log("Invoking setA_B - A: ", this.a, " B: ", this.b);
};

// 'ObjC' links/delegates to 'ObjB'
var ObjC = Object.create( ObjB );

ObjC.setC = function(c) {
    this.c = c;  
};

ObjC.setA_C = function(a, c) {
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC shows that prototype chaining goes through ObjB all the way to the ObjA
    this.setC( c );
    console.log("Invoking setA_C - A: ", this.a, " C: ", this.c);
};

ObjC.setA_B_C = function(a, b, c){
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC nor ObjB shows that prototype chaining got all the way to the ObjA
    this.setB( b );
    this.setC( c );
    console.log("Invoking setA_B_C - A: ", this.a, " B: ", this.b, " C: ", this.c);
};

ObjA.setA("A1");
ObjA.outputA(); // Invoking outputA - A:  A1

ObjB.setA_B("A2", "B1"); // Invoking setA_B - A:  A2  B:  B1

ObjC.setA_C("A3", "C1"); // Invoking setA_C - A:  A3  C:  C1
ObjC.setA_B_C("A4", "B2", "C1"); // Invoking setA_B_C - A:  A4  B:  B2  C:  C1

C'est un exemple simple, mais le point montré est que nous ne faisons que chaîner des objets dans une structure/formation plutôt plate, tout en ayant la possibilité d'utiliser des méthodes et des propriétés provenant de plusieurs objets. Nous réalisons les mêmes choses qu'avec l'approche classe/"copie des propriétés". Résumé de Kyle (page 114, "this & Object Prototypes"):

En d’autres termes, le mécanisme réel, l’essentiel de ce qui est important pour les fonctionnalités que nous pouvons exploiter en JavaScript, est tout ce qui concerne les objets liés à d’autres objets.

Je comprends que pour vous, il serait plus naturel d’énoncer tous les objets "parents" (attention :)) dans un seul appel de lieu/fonction plutôt que de modéliser toute la chaîne.

Ce qu'il faut, c'est un changement de pensée et de modélisation des problèmes dans nos applications en fonction de cela. Je m'y habitue aussi. J'espère que cela aidera et que le verdict final de Kyle lui-même serait formidable. :)

2
NenadPavlov