JavaScript OOP in NodeJS: wie?

117

Ich bin an die klassische OOP wie in Java gewöhnt.

Was sind die Best Practices für OOP in JavaScript mit NodeJS?

Jede Klasse ist eine Datei mit module.export?

Wie erstelle ich Klassen?

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

(Ich bin mir nicht mal sicher, ob es richtig ist)

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

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

vs.

this.Class = function() {}

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

...

Wie würde die Vererbung funktionieren?

Gibt es spezielle Module zur Implementierung von OOP in NodeJS?

Ich finde tausend verschiedene Wege, um Dinge zu erschaffen, die OOP ähneln. Aber ich habe keine Ahnung, was der am häufigsten verwendete / praktische / saubere Weg ist.

Bonusfrage : Was ist der empfohlene "OOP-Stil" für MongooseJS? (Kann ein MongooseJS-Dokument als Klasse und als Modell als Instanz verwendet werden?)

BEARBEITEN

Hier ist ein Beispiel in JsFiddle. Bitte geben Sie 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)
fusio
quelle
12
OO JS in node.js ist nicht wirklich spezifisch. Es gibt nur OO JS. Ihre Frage betrifft die Übersetzung von Java OOP-Techniken in JS, was einfach nicht richtig ist . Ich denke, es ist besser, Sie haben die gleiche Zeit / Energie aufgewendet, um zu lernen, wie das prototypbasierte Modell von JS funktioniert und wie Sie es zu Ihrem Vorteil nutzen können
Elias Van Ootegem
1
Außerdem haben Sie keine Klassen in JavaScript. Sie können mit Funktionen klassenähnliches Verhalten erstellen, dies ist jedoch im Allgemeinen keine gute Idee.
m_vdbeek
1
@AwakeZoldiek Was meinst du damit, dass es sich nicht um eine "native Funktion" handelt?
Esailija
4
@fusio Bei der prototypischen Vererbung im Allgemeinen erben Objekte / Instanzen von anderen Objekten / Instanzen. Klassen werden also nicht verwendet, weil Sie nicht mit abstrakten Definitionen arbeiten. Die Vererbung erfolgt also über eine prototypeKette . Und nein, Objekt unterstützt keine " privaten " Mitglieder. Dies können nur Closures bieten, obwohl Module / Skripte in Node.js als Closures implementiert sind.
Jonathan Lonowski
1
@Esailija Ich wollte eigentlich nicht vorschlagen, dass Schließungen private Mitglieder schaffen können. Ich wollte nur vorschlagen, dass Abschlüsse und eingeschlossene Variablen so nah wie möglich in JavaScript sind. Aber zum anderen Teil: Die einzige " Implementierung ", die ich erwähnte, betraf Knotenmodule , die innerhalb eines Abschlusses ausgewertet werden, in dem einige der globalen Elemente für jedes Skript eindeutig definiert sind.
Jonathan Lonowski

Antworten:

116

Dies ist ein Beispiel, das sofort funktioniert. Wenn Sie weniger "hacky" wollen, sollten Sie eine Vererbungsbibliothek oder ähnliches verwenden.

Nun, in eine Datei animal.js würden Sie schreiben:

var method = Animal.prototype;

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

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

module.exports = Animal;

So verwenden Sie es in einer anderen Datei:

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

var john = new Animal(3);

Wenn Sie eine "Unterklasse" möchten, dann in 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;

Sie können auch "Methodenausleihe" anstelle der vertikalen Vererbung in Betracht ziehen. Sie müssen nicht von einer "Klasse" erben, um ihre Methode für Ihre Klasse zu verwenden. Zum Beispiel:

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

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;
Esailija
quelle
Was ist der Unterschied zwischen dem Verwenden Animal.prototype.getAge= function(){}und dem einfachen Hinzufügen im this.getAge = function(){}Inneren function Animal() {}? Die Unterklasse scheint ein bisschen hackig zu sein. Mit "Vererbungs" -Bibliothek meinen Sie so etwas wie inheritsvon @badsyntax vorgeschlagen?
Fusio
4
@fusio Ja, Sie können so etwas tun inherits(Mouse, Animal);, um die Vererbung ein wenig zu bereinigen. Der Unterschied besteht darin, dass Sie für jedes instanziierte Objekt eine neue Funktionsidentität erstellen, anstatt eine Funktion gemeinsam zu nutzen. Wenn Sie 10 Mäuse haben, haben Sie 10 Funktionsidentitäten erstellt (das liegt nur daran, dass die Maus eine Methode hat. Wenn sie 10 Methoden hätte, würden 10 Mäuse 100 Funktionsidentitäten erstellen, würde Ihr Server schnell den größten Teil seiner CPU auf GC: P verschwenden.) , obwohl Sie sie für nichts verwenden werden. Die Sprache hat derzeit nicht genug Ausdruckskraft, um dies zu optimieren.
Esailija
Woah. Danke :) Das scheint ganz einfach zu sein, ich habe auch Details_of_the_Object_Model gefunden, wo sie JS mit Java vergleichen. Um zu erben, tun sie einfach: Mouse.prototype = new Animal().. wie ist es mit Ihrem Beispiel zu vergleichen? (zB was ist Object.create()?)
Fusio
@fusio Object.create ruft den Konstruktor nicht auf ... Wenn der Konstruktor Nebenwirkungen oder ähnliches hat (er kann im Gegensatz zu Java alles tun, was eine normale Funktion kann), ist das Aufrufen beim Erstellen einer Vererbungskette unerwünscht.
Esailija
3
Ich behaupte immer noch, dass für jemanden, der anfängt, JavaScript zu verwenden, das Hacken einer solchen Lösung keine gute Lösung ist. Es gibt so viele Macken und Fallstricke, die nicht einfach zu beheben sind, dass dies nicht empfohlen werden sollte.
m_vdbeek
42

Da die Node.js-Community sicherstellt, dass neue Funktionen aus der JavaScript ECMA-262-Spezifikation den Entwicklern von Node.js rechtzeitig zur Verfügung gestellt werden.

Sie können sich JavaScript-Klassen ansehen . MDN-Link zu JS-Klassen In der Einführung von JavaScript-Klassen in ECMAScript 6 bietet diese Methode eine einfachere Möglichkeit, OOP-Konzepte in Javascript zu modellieren.

Hinweis : JS-Klassen funktionieren nur im strengen Modus .

Unten finden Sie ein Skelett der Klasse, Vererbung in Node.js (Used Node.js Version v5.0.0 )

Klassenerklärungen:

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Vererbung:

'use strict';
class Base{

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

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

var childObj = new Child();
Piyush Sagar
quelle
14

Ich schlage vor, den inheritsmit dem Standardmodul gelieferten Helfer zu verwenden util: http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

Auf der verlinkten Seite finden Sie ein Beispiel für die Verwendung.

Badsyntax
quelle
Dies ist die hilfreichste Antwort in Bezug auf die NodeJS-Kernumgebung.
Philzen
1
Sieht jetzt veraltet aus. Vom Antwortlink: Hinweis: Von der Verwendung von util.inherits () wird abgeraten. Verwenden Sie die ES6-Klasse und erweitern Sie die Schlüsselwörter, um Unterstützung für die Vererbung auf Sprachebene zu erhalten. Beachten Sie auch, dass die beiden Stile semantisch nicht kompatibel sind.
Frosty Z
10

Dies ist das beste Video über objektorientiertes JavaScript im Internet:

Der endgültige Leitfaden für objektorientiertes JavaScript

Beobachten Sie von Anfang bis Ende!

Grundsätzlich ist Javascript eine Prototyp-basierte Sprache, die sich deutlich von den Klassen in Java, C ++, C # und anderen beliebten Freunden unterscheidet. Das Video erklärt die Kernkonzepte weitaus besser als jede Antwort hier.

Mit ES6 (veröffentlicht 2015) haben wir ein Schlüsselwort "class" erhalten, mit dem wir Javascript-Klassen wie mit Java, C ++, C #, Swift usw. verwenden können.

Screenshot aus dem Video, der zeigt, wie eine Javascript-Klasse / Unterklasse geschrieben und instanziiert wird: Geben Sie hier die Bildbeschreibung ein

etayluz
quelle
Ich freue mich, dass Sie eine Antwort für ES6 gegeben haben. Danke dir! Leider habe ich nicht die Daten, um ein 27-minütiges Video anzusehen. Ich werde meine Suche nach schriftlicher Anleitung fortsetzen.
Tim.rohrer
Danke für das Video. Ich half mir, viele Fragen zu Javascript zu klären.
Kishore Devaraj
4

In der Javascript-Community argumentieren viele Leute, dass OOP nicht verwendet werden sollte, da das Prototypmodell keine strenge und robuste OOP nativ zulässt. Ich denke jedoch nicht, dass OOP eine Frage der Sprache ist, sondern eher eine Frage der Architektur.

Wenn Sie eine wirklich starke OOP in Javascript / Node verwenden möchten, können Sie sich das Full-Stack-Open-Source-Framework Danf ansehen . Es bietet alle erforderlichen Funktionen für einen starken OOP-Code (Klassen, Schnittstellen, Vererbung, Abhängigkeitsinjektion, ...). Außerdem können Sie auf der Server- (Knoten-) und der Client- (Browser-) Seite dieselben Klassen verwenden. Darüber hinaus können Sie Ihre eigenen Danf-Module codieren und dank Npm mit anderen teilen.

Gnucki
quelle
-1

Wenn Sie alleine arbeiten und OOP am nächsten kommen möchten, wie Sie es in Java, C # oder C ++ finden würden, lesen Sie die Javascript-Bibliothek CrxOop. CrxOop bietet eine Syntax, die Java-Entwicklern vertraut ist.

Seien Sie vorsichtig, Javas OOP ist nicht dasselbe wie das in Javascript. Verwenden Sie die CrxOop-Klassen und nicht die CrxOop-Strukturen, um dasselbe Verhalten wie in Java zu erzielen, und stellen Sie sicher, dass alle Ihre Methoden virtuell sind. Ein Beispiel für die Syntax ist:

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

Der Code ist reines Javascript, kein Transpilieren. Das Beispiel stammt aus einer Reihe von Beispielen aus der offiziellen Dokumentation.

ua692875
quelle