JavaScript POO dans NodeJS: comment?

118

Je suis habitué à la POO classique comme en Java.

Quelles sont les meilleures pratiques pour effectuer la POO en JavaScript à l'aide de NodeJS?

Chaque classe est un fichier avec module.export?

Comment créer des classes?

this.Class = function() {
    //constructor?
    var privateField = ""
    this.publicField = ""
    var privateMethod = function() {}
    this.publicMethod = function() {} 
}

vs. (je ne suis même pas sûr que ce soit correct)

this.Class = {
    privateField: ""
    , privateMethod: function() {}

    , return {
        publicField: ""
        publicMethod: function() {}
    }
}

contre.

this.Class = function() {}

this.Class.prototype.method = function(){}

...

Comment fonctionnerait l'héritage?

Existe-t-il des modules spécifiques pour implémenter la POO dans NodeJS?

Je trouve mille façons différentes de créer des choses qui ressemblent à la POO ... mais je n'ai aucune idée de la manière la plus utilisée / pratique / propre.

Question bonus : quel est le "style POO" suggéré à utiliser avec MongooseJS? (un document MongooseJS peut-il être vu comme une classe et un modèle utilisé comme une instance?)

ÉDITER

voici un exemple dans JsFiddle s'il vous plaît fournir des commentaires.

//http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/
function inheritPrototype(childObject, parentObject) {
    var copyOfParent = Object.create(parentObject.prototype)
    copyOfParent.constructor = childObject
    childObject.prototype = copyOfParent
}

//example
function Canvas (id) {
    this.id = id
    this.shapes = {} //instead of array?
    console.log("Canvas constructor called "+id)
}
Canvas.prototype = {
    constructor: Canvas
    , getId: function() {
        return this.id
    }
    , getShape: function(shapeId) {
        return this.shapes[shapeId]
    }
    , getShapes: function() {
        return this.shapes
    }
    , addShape: function (shape)  {
        this.shapes[shape.getId()] = shape
    }
    , removeShape: function (shapeId)  {
        var shape = this.shapes[shapeId]
        if (shape)
            delete this.shapes[shapeId]
        return shape
    }
}

function Shape(id) {
    this.id = id
    this.size = { width: 0, height: 0 }
    console.log("Shape constructor called "+id)
}
Shape.prototype = {
    constructor: Shape
    , getId: function() {
        return this.id
    }
    , getSize: function() {
        return this.size
    }
    , setSize: function (size)  {
        this.size = size
    }
}

//inheritance
function Square(id, otherSuff) {
    Shape.call(this, id) //same as Shape.prototype.constructor.apply( this, arguments ); ?
    this.stuff = otherSuff
    console.log("Square constructor called "+id)
}
inheritPrototype(Square, Shape)
Square.prototype.getSize = function() { //override
    return this.size.width
}

function ComplexShape(id) {
    Shape.call(this, id)
    this.frame = null
    console.log("ComplexShape constructor called "+id)
}
inheritPrototype(ComplexShape, Shape)
ComplexShape.prototype.getFrame = function() {
    return this.frame
}
ComplexShape.prototype.setFrame = function(frame) {
    this.frame = frame
}

function Frame(id) {
    this.id = id
    this.length = 0
}
Frame.prototype = {
    constructor: Frame
    , getId: function() {
        return this.id
    }
    , getLength: function() {
        return this.length
    }
    , setLength: function (length)  {
        this.length = length
    }
}

/////run
var aCanvas = new Canvas("c1")
var anotherCanvas = new Canvas("c2")
console.log("aCanvas: "+ aCanvas.getId())

var aSquare = new Square("s1", {})
aSquare.setSize({ width: 100, height: 100})
console.log("square overridden size: "+aSquare.getSize())

var aComplexShape = new ComplexShape("supercomplex")
var aFrame = new Frame("f1")
aComplexShape.setFrame(aFrame)
console.log(aComplexShape.getFrame())

aCanvas.addShape(aSquare)
aCanvas.addShape(aComplexShape)
console.log("Shapes in aCanvas: "+Object.keys(aCanvas.getShapes()).length)

anotherCanvas.addShape(aCanvas.removeShape("supercomplex"))
console.log("Shapes in aCanvas: "+Object.keys(aCanvas.getShapes()).length)
console.log("Shapes in anotherCanvas: "+Object.keys(anotherCanvas.getShapes()).length)

console.log(aSquare instanceof Shape)
console.log(aComplexShape instanceof Shape)
fusio
la source
12
Il n'y a rien de vraiment spécifique sur OO JS dans node.js. Il y a juste OO JS. Votre question concerne la traduction des techniques Java OOP en JS, ce qui n'est tout simplement pas correct . Je pense qu'il vaut mieux que vous passiez le même temps / énergie à apprendre comment fonctionne le modèle basé sur un prototype de JS et comment vous pouvez l'utiliser à votre avantage
Elias Van Ootegem
1
De plus, vous n'avez pas de classes en JavaScript. Vous pouvez créer un comportement de classe avec des fonctions, mais ce n'est généralement pas une bonne idée.
m_vdbeek
1
@AwakeZoldiek Que voulez-vous dire par "fonctionnalité native"?
Esailija
4
@fusio Avec l'héritage prototypique en général, les objets / instances héritent d'autres objets / instances. Ainsi, les classes ne sont pas utilisées car vous ne travaillez pas avec des définitions abstraites. Ainsi, l'héritage se fait à travers une prototypechaîne . Et, non, object ne prend pas en charge les membres " privés ". Seules les fermetures peuvent offrir cela, bien que les modules / scripts dans Node.js soient implémentés comme des fermetures.
Jonathan Lonowski
1
@Esailija Je ne voulais pas vraiment suggérer que les fermetures peuvent créer des membres privés. Je suggérais simplement que les fermetures et les variables incluses sont aussi proches que possible en JavaScript. Mais, pour l'autre partie: la seule « implémentation » que j'ai mentionnée concerne les modules Node, qui sont évalués dans une fermeture où certains des globaux sont définis de manière unique à chaque script.
Jonathan Lonowski

Réponses:

116

Ceci est un exemple qui fonctionne hors de la boîte. Si vous voulez moins de "hacky", vous devez utiliser une bibliothèque d'héritage ou autre.

Eh bien dans un fichier animal.js vous écririez:

var method = Animal.prototype;

function Animal(age) {
    this._age = age;
}

method.getAge = function() {
    return this._age;
};

module.exports = Animal;

Pour l'utiliser dans un autre fichier:

var Animal = require("./animal.js");

var john = new Animal(3);

Si vous voulez une "sous-classe", alors dans mouse.js:

var _super = require("./animal.js").prototype,
    method = Mouse.prototype = Object.create( _super );

method.constructor = Mouse;

function Mouse() {
    _super.constructor.apply( this, arguments );
}
//Pointless override to show super calls
//note that for performance (e.g. inlining the below is impossible)
//you should do
//method.$getAge = _super.getAge;
//and then use this.$getAge() instead of super()
method.getAge = function() {
    return _super.getAge.call(this);
};

module.exports = Mouse;

Vous pouvez également envisager «l'emprunt de méthode» au lieu de l'héritage vertical. Vous n'avez pas besoin d'hériter d'une "classe" pour utiliser sa méthode sur votre classe. Par exemple:

 var method = List.prototype;
 function List() {

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;
Esailija
la source
Quelle est la différence entre utiliser Animal.prototype.getAge= function(){}et simplement ajouter à l' this.getAge = function(){}intérieur function Animal() {}? La sous-classe semble un peu hacky .. avec la bibliothèque "héritage" vous voulez dire quelque chose comme inheritssuggéré par @badsyntax?
fusio
4
@fusio oui, vous pouvez faire quelque chose comme inherits(Mouse, Animal);ça nettoie un peu l'héritage mis en place. La différence est que vous créez une nouvelle identité de fonction pour chaque objet instancié au lieu de partager une fonction. Si vous avez 10 souris, vous avez créé 10 identités de fonction (c'est seulement parce que la souris a une méthode, si elle avait 10 méthodes, 10 souris créeraient 100 identités de fonction, votre serveur gaspillerait rapidement la plupart de son CPU sur GC: P) , même si vous ne les utiliserez pour rien. La langue n'a pas assez de pouvoir expressif pour optimiser cela actuellement.
Esailija
Woah. Merci :) Cela semble assez simple, j'ai également trouvé Details_of_the_Object_Model où ils comparent JS avec Java. Pourtant, pour hériter, ils le font simplement: Mouse.prototype = new Animal().. comment cela se compare-t-il à votre exemple? (par exemple, qu'est-ce que c'est Object.create()?)
fusio
@fusio Object.create n'appelle pas le constructeur ... si le constructeur a des effets secondaires ou autres (il peut faire tout ce qu'une fonction normale peut, contrairement à Java), alors l'invoquer n'est pas souhaitable lors de la configuration de la chaîne d'héritage.
Esailija
3
Je maintiens toujours que pour quelqu'un qui commence à utiliser JavaScript, pirater une solution comme celle-ci n'est pas une bonne solution. Il y a tellement de bizarreries et d'embûches qui ne sont pas faciles à déboguer que cela ne devrait pas être conseillé.
m_vdbeek
43

La communauté Node.js s'assure que les nouvelles fonctionnalités de la spécification JavaScript ECMA-262 sont apportées aux développeurs Node.js en temps opportun.

Vous pouvez jeter un œil aux classes JavaScript . Lien MDN vers les classes JS Dans les classes JavaScript ECMAScript 6, cette méthode fournit un moyen plus simple de modéliser des concepts POO en Javascript.

Remarque : les classes JS ne fonctionneront qu'en mode strict .

Voici un squelette de classe, l'héritage écrit en Node.js (Used Node.js Version v5.0.0 )

Déclarations de classe:

'use strict'; 
class Animal{

 constructor(name){
    this.name = name ;
 }

 print(){
    console.log('Name is :'+ this.name);
 }
}

var a1 = new Animal('Dog');

Héritage:

'use strict';
class Base{

 constructor(){
 }
 // methods definitions go here
}

class Child extends Base{
 // methods definitions go here
 print(){ 
 }
}

var childObj = new Child();
Piyush Sagar
la source
14

Je suggère d'utiliser l' inheritsassistant fourni avec le utilmodule standard : http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

Il existe un exemple de son utilisation sur la page liée.

mauvaise syntaxe
la source
C'est la réponse la plus utile en ce qui concerne l'environnement NodeJS principal.
Philzen
1
Semble désormais obsolète. Depuis le lien de réponse: Remarque: l'utilisation de util.inherits () est déconseillée. Veuillez utiliser la classe ES6 et étendre les mots-clés pour obtenir la prise en charge de l'héritage au niveau de la langue. Notez également que les deux styles sont sémantiquement incompatibles.
Frosty Z du
11

Voici la meilleure vidéo sur le JavaScript orienté objet sur Internet:

Le guide définitif du JavaScript orienté objet

Regardez du début à la fin !!

Fondamentalement, Javascript est un langage basé sur un prototype qui est assez différent des classes en Java, C ++, C # et d'autres amis populaires. La vidéo explique les concepts de base bien mieux que n'importe quelle réponse ici.

Avec ES6 (sortie en 2015), nous avons un mot-clé "class" qui nous permet d'utiliser des "classes" Javascript comme nous le ferions avec Java, C ++, C #, Swift, etc.

Capture d'écran de la vidéo montrant comment écrire et instancier une classe / sous-classe Javascript: entrez la description de l'image ici

Etayluz
la source
Je vous remercie d'avoir fourni une réponse pour ES6. Je vous remercie! Malheureusement, je n'ai pas les données pour regarder une vidéo de 27 minutes. Je vais continuer ma recherche de conseils écrits.
tim.rohrer
Merci pour la vidéo. J'ai aidé à clarifier beaucoup de questions que j'avais sur javascript.
Kishore Devaraj
4

Dans la communauté Javascript, beaucoup de gens soutiennent que la POO ne devrait pas être utilisée car le modèle prototype ne permet pas de faire une POO stricte et robuste de manière native. Cependant, je ne pense pas que la POO soit une question de langage mais plutôt une question d'architecture.

Si vous souhaitez utiliser une véritable POO forte en Javascript / Node, vous pouvez jeter un œil au framework open source complet Danf . Il fournit toutes les fonctionnalités nécessaires pour un code POO fort (classes, interfaces, héritage, injection de dépendances, ...). Il vous permet également d'utiliser les mêmes classes sur les côtés serveur (nœud) et client (navigateur). De plus, vous pouvez coder vos propres modules danf et les partager avec n'importe qui grâce à Npm.

Gnucki
la source
-1

Si vous travaillez seul et que vous voulez ce qui se rapproche le plus de la POO comme vous le trouverez en Java, C # ou C ++, consultez la bibliothèque javascript, CrxOop. CrxOop fournit une syntaxe assez familière aux développeurs Java.

Attention, la POO de Java n'est pas la même que celle trouvée dans Javascript. Pour obtenir le même comportement qu'en Java, utilisez les classes de CrxOop, pas les structures de CrxOop, et assurez-vous que toutes vos méthodes sont virtuelles. Un exemple de syntaxe est,

crx_registerClass("ExampleClass", 
{ 
    "VERBOSE": 1, 

    "public var publicVar": 5, 
    "private var privateVar": 7, 

    "public virtual function publicVirtualFunction": function(x) 
    { 
        this.publicVar1 = x;
        console.log("publicVirtualFunction"); 
    }, 

    "private virtual function privatePureVirtualFunction": 0, 

    "protected virtual final function protectedVirtualFinalFunction": function() 
    { 
        console.log("protectedVirtualFinalFunction"); 
    }
}); 

crx_registerClass("ExampleSubClass", 
{ 
    VERBOSE: 1, 
    EXTENDS: "ExampleClass", 

    "public var publicVar": 2, 

    "private virtual function privatePureVirtualFunction": function(x) 
    { 
        this.PARENT.CONSTRUCT(pA);
        console.log("ExampleSubClass::privatePureVirtualFunction"); 
    } 
}); 

var gExampleSubClass = crx_new("ExampleSubClass", 4);

console.log(gExampleSubClass.publicVar);
console.log(gExampleSubClass.CAST("ExampleClass").publicVar);

Le code est pur javascript, pas de transpilage. L'exemple est tiré d'un certain nombre d'exemples de la documentation officielle.

ua692875
la source