JavaScript OOP in NodeJS: come?


118

Sono abituato al classico OOP come in Java.

Quali sono le migliori pratiche per eseguire OOP in JavaScript utilizzando NodeJS?

Ogni classe è un file con module.export?

Come creare classi?

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

vs. (non sono nemmeno sicuro che sia corretto)

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

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

vs.

this.Class = function() {}

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

...

Come funzionerebbe l'eredità?

Esistono moduli specifici per l'implementazione dell'OOP in NodeJS?

Sto trovando mille modi diversi per creare cose che assomigliano a OOP .. ma non ho idea di quale sia il modo più usato / pratico / pulito.

Domanda bonus : qual è lo "stile OOP" suggerito da usare con MongooseJS? (un documento MongooseJS può essere visto come una classe e un modello usato come istanza?)

MODIFICARE

ecco un esempio in JsFiddle per favore fornisci un feedback.

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

12
Non c'è niente di veramente specifico su OO JS in node.js. C'è solo OO JS. La tua domanda riguarda la traduzione delle tecniche Java OOP in JS, il che non è corretto . Penso che sia meglio che tu abbia speso lo stesso tempo / energia per imparare come funziona il modello basato su prototipi di JS e come puoi usarlo a tuo vantaggio
Elias Van Ootegem

1
Inoltre, non hai classi in JavaScript. Puoi creare comportamenti di classe con le funzioni, ma generalmente non è una buona idea.
m_vdbeek

1
@AwakeZoldiek Cosa intendi con non è una "funzionalità nativa"?
Esailija

4
@fusio Con l'ereditarietà prototipale in generale, oggetti / istanze ereditano da altri oggetti / istanze. Quindi, le classi non vengono utilizzate perché non stai lavorando con definizioni astratte. Quindi, l'eredità avviene attraverso una prototypecatena . E no, gli oggetti non supportano i membri " privati ". Solo le chiusure possono offrirlo, sebbene i moduli / script in Node.js siano implementati come chiusure.
Jonathan Lonowski

1
@Esailija In realtà non intendevo suggerire che le chiusure possano creare membri privati. Stavo solo suggerendo che le chiusure e le variabili racchiuse sono le più vicine che puoi ottenere in JavaScript. Ma, per l'altra parte: l'unica " implementazione " che ho citato riguardava i moduli Node, che vengono valutati all'interno di una chiusura in cui alcune delle globali sono definite uniche per ogni script.
Jonathan Lonowski

Risposte:


116

Questo è un esempio che funziona fuori dagli schemi. Se vuoi meno "hacky", dovresti usare la libreria di ereditarietà o simili.

Ebbene in un file animal.js scriveresti:

var method = Animal.prototype;

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

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

module.exports = Animal;

Per usarlo in un altro file:

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

var john = new Animal(3);

Se vuoi una "sottoclasse" allora dentro 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;

Inoltre puoi considerare il "metodo di prestito" invece dell'ereditarietà verticale. Non è necessario ereditare da una "classe" per utilizzare il suo metodo sulla classe. Per esempio:

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

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;

qual è la differenza tra usare Animal.prototype.getAge= function(){}e semplicemente aggiungere this.getAge = function(){}all'interno function Animal() {}? La sottoclasse sembra un po 'hacky .. con la libreria " inheritsinheritance " intendi qualcosa come suggerito da @badsyntax?
fusio

4
@fusio sì, puoi fare qualcosa del genere per inherits(Mouse, Animal);ripulire un po 'l'eredità impostata. La differenza è che stai creando una nuova identità di funzione per ogni oggetto istanziato invece di condividere una funzione. Se hai 10 mouse, hai creato 10 identità di funzione (questo è solo perché il mouse ha un metodo, se avesse 10 metodi, 10 mouse creerebbero 100 identità di funzione, il tuo server sprecherebbe rapidamente la maggior parte della sua CPU su GC: P) , anche se non li userai per niente. La lingua non ha abbastanza potenza espressiva per ottimizzarla al momento.
Esailija

Woah. Grazie :) Questo sembra abbastanza semplice, ho anche trovato Details_of_the_Object_Model dove confrontano JS con Java. Tuttavia, per ereditare fanno semplicemente: Mouse.prototype = new Animal().. come si confronta con il tuo esempio? (es. cos'è Object.create()?)
fusio

@fusio Object.create non richiama il costruttore ... se il costruttore ha effetti collaterali o simili (può fare tutto ciò che può fare una normale funzione, a differenza di Java), quindi invocarlo è indesiderabile quando si imposta la catena di ereditarietà.
Esailija

3
Continuo a sostenere che per qualcuno che sta iniziando a utilizzare JavaScript, l'hacking di una soluzione come questa non è una buona soluzione. Ci sono così tante stranezze e insidie ​​che non sono facili da eseguire il debug che questo non dovrebbe essere consigliato.
m_vdbeek

43

Poiché la community di Node.js garantisce che le nuove funzionalità della specifica JavaScript ECMA-262 vengano fornite agli sviluppatori di Node.js in modo tempestivo.

Puoi dare un'occhiata alle classi JavaScript . Collegamento MDN alle classi JS Nelle classi JavaScript ECMAScript 6 vengono introdotte, questo metodo fornisce un modo più semplice per modellare i concetti OOP in Javascript.

Nota : le classi JS funzioneranno solo in modalità rigorosa .

Di seguito è riportato uno scheletro di classe, ereditarietà scritta in Node.js (usato Node.js versione v5.0.0 )

Dichiarazioni di classe:

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Ereditarietà:

'use strict';
class Base{

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

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

var childObj = new Child();

14

Suggerisco di utilizzare l' inheritshelper fornito con il utilmodulo standard : http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

C'è un esempio di come usarlo nella pagina collegata.


Questa è la risposta più utile per quanto riguarda l'ambiente principale NodeJS.
Philzen

1
Sembra ora deprecato. Dal link della risposta: Nota: l'utilizzo di util.inherits () è sconsigliato. Utilizzare la classe ES6 ed estendere le parole chiave per ottenere il supporto dell'ereditarietà a livello di lingua. Notare inoltre che i due stili sono semanticamente incompatibili.
Frosty Z

11

Questo è il miglior video su JavaScript orientato agli oggetti su Internet:

La guida definitiva a JavaScript orientato agli oggetti

Guarda dall'inizio alla fine !!

Fondamentalmente, Javascript è un linguaggio basato su prototipi che è molto diverso dalle classi in Java, C ++, C # e altri amici popolari. Il video spiega i concetti fondamentali molto meglio di qualsiasi risposta qui.

Con ES6 (rilasciato nel 2015) abbiamo ottenuto una parola chiave "class" che ci permette di utilizzare "classi" Javascript come faremmo con Java, C ++, C #, Swift, ecc.

Screenshot dal video che mostra come scrivere e istanziare una classe / sottoclasse Javascript: inserisci qui la descrizione dell'immagine


Apprezzo che tu abbia fornito una risposta per ES6. Grazie! Sfortunatamente, non ho i dati per guardare un video di 27 minuti. Continuerò la mia ricerca di una guida scritta.
tim.rohrer

Grazie per il video. Mi ha aiutato a chiarire molte domande che avevo su javascript.
Kishore Devaraj

4

Nella comunità Javascript, molte persone sostengono che l'OOP non dovrebbe essere utilizzato perché il modello prototipo non consente di eseguire nativamente un OOP rigoroso e robusto. Tuttavia, non penso che l'OOP sia una questione di lingua ma piuttosto una questione di architettura.

Se vuoi usare un OOP davvero potente in Javascript / Node, puoi dare un'occhiata al framework open source full-stack Danf . Fornisce tutte le funzionalità necessarie per un codice OOP potente (classi, interfacce, ereditarietà, iniezione di dipendenze, ...). Consente inoltre di utilizzare le stesse classi sia sul lato server (nodo) che su quello client (browser). Inoltre, puoi codificare i tuoi moduli danf e condividerli con chiunque grazie a Npm.


-1

Se stai lavorando da solo e vuoi la cosa più vicina a OOP come potresti trovare in Java o C # o C ++, vedi la libreria javascript, CrxOop. CrxOop fornisce una sintassi piuttosto familiare agli sviluppatori Java.

Fai solo attenzione, l'OOP di Java non è lo stesso di quello trovato in Javascript. Per ottenere lo stesso comportamento di Java, usa le classi di CrxOop, non le strutture di CrxOop, e assicurati che tutti i tuoi metodi siano virtuali. Un esempio della sintassi è,

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

Il codice è puro javascript, non transpiling. L'esempio è tratto da una serie di esempi tratti dalla documentazione ufficiale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.