JavaScript OOP w NodeJS: jak?

118

Jestem przyzwyczajony do klasycznego OOP, jak w Javie.

Jakie są najlepsze praktyki wykonywania OOP w JavaScript przy użyciu NodeJS?

Każda klasa to plik z module.export?

Jak tworzyć klasy?

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

vs. (nie jestem nawet pewien, czy to prawda)

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

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

vs.

this.Class = function() {}

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

...

Jak działałoby dziedziczenie?

Czy istnieją specjalne moduły do ​​implementacji OOP w NodeJS?

Znajduję tysiące różnych sposobów tworzenia rzeczy, które przypominają OOP ... ale nie mam pojęcia, jaki jest najczęściej używany / praktyczny / czysty sposób.

Pytanie dodatkowe : jaki jest sugerowany „styl OOP” do użycia z MongooseJS? (czy dokument MongooseJS może być postrzegany jako klasa i model używany jako instancja?)

EDYTOWAĆ

tutaj jest przykład w JsFiddle prosimy o przesłanie opinii.

//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
źródło
12
Nie ma nic konkretnego na temat OO JS w node.js. Jest tylko OO JS. Twoje pytanie dotyczy tłumaczenia technik Java OOP na JS, co po prostu nie jest w porządku . Myślę, że lepiej, żebyś poświęcił ten sam czas / energię na naukę, jak działa prototypowy model JS i jak możesz go wykorzystać na swoją korzyść
Elias Van Ootegem
1
Nie masz też klas w JavaScript. Możesz tworzyć zachowania podobne do klas za pomocą funkcji, ale generalnie nie jest to dobry pomysł.
m_vdbeek,
1
@AwakeZoldiek Co masz na myśli, mówiąc, że nie jest to „funkcja natywna”?
Esailija
4
@fusio W przypadku dziedziczenia prototypowego ogólnie obiekty / instancje dziedziczą po innych obiektach / instancjach. Tak więc klasy nie są używane, ponieważ nie pracujesz z abstrakcyjnymi definicjami. Tak więc dziedziczenie odbywa się za pośrednictwem prototypełańcucha . I nie, obiekt nie obsługuje członków „ prywatnych ”. Tylko zamknięcia mogą to zaoferować, chociaż moduły / skrypty w Node.js są zaimplementowane jako zamknięcia.
Jonathan Lonowski
1
@Esailija Właściwie nie chciałem sugerować, że zamknięcia mogą tworzyć prywatnych członków. Sugerował tylko, że zamknięcia i zamknięte zmienne są tak blisko, jak to tylko możliwe w JavaScript. Ale z drugiej strony: jedyna " implementacja ", o której wspomniałem, dotyczyła modułów Node, które są oceniane w ramach domknięcia, w którym niektóre z globalnych są zdefiniowane jako unikalne dla każdego skryptu.
Jonathan Lonowski

Odpowiedzi:

116

To jest przykład, który działa po wyjęciu z pudełka. Jeśli chcesz mniej "hacków", powinieneś użyć biblioteki dziedziczenia lub czegoś podobnego.

Cóż, w pliku animal.js napisałbyś:

var method = Animal.prototype;

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

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

module.exports = Animal;

Aby użyć go w innym pliku:

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

var john = new Animal(3);

Jeśli chcesz mieć „podklasę”, to wewnątrz 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;

Zamiast dziedziczenia wertykalnego można również rozważyć opcję „Pożyczanie metod”. Nie musisz dziedziczyć po „klasie”, aby użyć jej metody w swojej klasie. Na przykład:

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

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;
Esailija
źródło
Jaka jest różnica między używaniem Animal.prototype.getAge= function(){}a zwykłym dodawaniem do this.getAge = function(){}środka function Animal() {}? inheritsPodklasa wydaje się trochę zepsuta… z biblioteką „dziedziczenia” masz na myśli coś takiego, jak sugeruje @badsyntax?
fusio
4
@fusio tak, możesz zrobić coś takiego, inherits(Mouse, Animal);co trochę wyczyści konfigurację dziedziczenia. Różnica polega na tym, że tworzysz nową tożsamość funkcji dla każdego tworzonego obiektu zamiast współużytkowania jednej funkcji. Jeśli masz 10 myszy, utworzyłeś 10 tożsamości funkcji (to tylko dlatego, że mysz ma jedną metodę, gdyby miała 10 metod, 10 myszy utworzyłoby 100 tożsamości funkcji, twój serwer szybko zmarnuje większość swojego procesora na GC: P) , nawet jeśli nie będziesz ich używać do niczego. Język nie ma obecnie wystarczającej siły wyrazu, aby to zoptymalizować.
Esailija
Woah. Dzięki :) Wydaje się to dość proste, znalazłem również Details_of_the_Object_Model, gdzie porównują JS z Javą. Jednak aby odziedziczyć, po prostu robią: Mouse.prototype = new Animal().. jak to się ma do twojego przykładu? (np. co to jest Object.create()?)
fusio
@fusio Object.create nie wywołuje konstruktora ... jeśli konstruktor ma efekty uboczne lub takie (może zrobić wszystko, co normalna funkcja, w przeciwieństwie do Javy), to wywołanie go jest niepożądane podczas konfigurowania łańcucha dziedziczenia.
Esailija,
3
Nadal utrzymuję, że dla kogoś, kto zaczyna używać JavaScript, hakowanie takiego rozwiązania nie jest dobrym rozwiązaniem. Jest tak wiele dziwactw i pułapek, które nie są łatwe do debugowania, że ​​nie należy tego doradzać.
m_vdbeek
43

Ponieważ społeczność Node.js zapewnia, że ​​nowe funkcje ze specyfikacji JavaScript ECMA-262 zostaną dostarczone programistom Node.js. w odpowiednim czasie.

Możesz przyjrzeć się klasom JavaScript . Łącze w MDN do klas JS W klasach JavaScript ECMAScript 6 jest wprowadzonych, ta metoda zapewnia łatwiejszy sposób modelowania koncepcji OOP w JavaScript.

Uwaga : klasy JS będą działać tylko w trybie ścisłym .

Poniżej znajduje się szkielet klasy, dziedziczenie napisane w Node.js (używana wersja Node.js v5.0.0 )

Deklaracje klas:

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Dziedziczenie:

'use strict';
class Base{

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

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

var childObj = new Child();
Piyush Sagar
źródło
14

Sugeruję użycie inheritspomocnika, który jest dostarczany ze standardowym utilmodułem: http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

Jest przykład, jak go używać na połączonej stronie.

badsyntax
źródło
To jest najbardziej pomocna odpowiedź w odniesieniu do podstawowego środowiska NodeJS.
Philzen
1
Wygląda teraz na przestarzałe. Z linku do odpowiedzi: Uwaga: nie zaleca się używania funkcji util.inherits (). Użyj klasy ES6 i rozszerz słowa kluczowe, aby uzyskać obsługę dziedziczenia na poziomie języka. Należy również zauważyć, że te dwa style są semantycznie niezgodne.
Frosty Z
11

Oto najlepszy film o JavaScript zorientowanym obiektowo w Internecie:

Ostateczny przewodnik po JavaScript zorientowanym obiektowo

Oglądaj od początku do końca !!

Zasadniczo Javascript jest językiem opartym na prototypie, który różni się znacznie od klas w Javie, C ++, C # i innych popularnych znajomych. Film wyjaśnia podstawowe pojęcia o wiele lepiej niż jakakolwiek odpowiedź tutaj.

W ES6 (wydanym 2015) otrzymaliśmy słowo kluczowe „class”, które pozwala nam używać „klas” Javascript, tak jak w przypadku Java, C ++, C #, Swift itp.

Zrzut ekranu z wideo pokazujący, jak napisać i utworzyć instancję klasy / podklasy Javascript: wprowadź opis obrazu tutaj

etayluz
źródło
Doceniam, że udzieliłeś odpowiedzi na temat ES6. Dziękuję Ci! Niestety nie mam danych, aby obejrzeć 27 minutowy film. Kontynuuję poszukiwania pisemnych wskazówek.
tim.rohrer
Dzięki za film. Pomogłem mi wyjaśnić wiele pytań dotyczących javascript.
Kishore Devaraj
4

W społeczności Javascript wiele osób twierdzi, że OOP nie powinno być używane, ponieważ model prototypowy nie pozwala na natywne wykonanie ścisłego i niezawodnego OOP. Jednak nie uważam, że OOP jest kwestią języka, ale raczej kwestią architektury.

Jeśli chcesz użyć naprawdę mocnego OOP w Javascript / Node, możesz rzucić okiem na pełną platformę open source Danf . Zapewnia wszystkie potrzebne funkcje dla silnego kodu OOP (klasy, interfejsy, dziedziczenie, iniekcję zależności, ...). Pozwala także na używanie tych samych klas zarówno po stronie serwera (węzła), jak i klienta (przeglądarki). Co więcej, możesz kodować własne moduły danf i dzielić się nimi z każdym dzięki Npm.

Gnucki
źródło
-1

Jeśli pracujesz na własną rękę i chcesz, aby było to jak najbardziej zbliżone do OOP, tak jak w Javie, C # lub C ++, zajrzyj do biblioteki javascript CrxOop. CrxOop zapewnia składnię nieco znaną programistom Java.

Tylko uważaj, OOP Java to nie to samo, co w Javascript. Aby uzyskać to samo zachowanie, co w Javie, użyj klas CrxOop, a nie struktur CrxOop i upewnij się, że wszystkie metody są wirtualne. Przykładem składni jest:

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

Kod jest w czystym javascript, bez transpilacji. Przykład zaczerpnięto z szeregu przykładów z oficjalnej dokumentacji.

ua692875
źródło