JavaScript OOP em NodeJS: como?

118

Estou acostumado com o OOP clássico como em Java.

Quais são as melhores práticas para fazer OOP em JavaScript usando NodeJS?

Cada classe é um arquivo com module.export?

Como criar classes?

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

vs. (nem tenho certeza se está correto)

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

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

vs.

this.Class = function() {}

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

...

Como funcionaria a herança?

Existem módulos específicos para implementar OOP em NodeJS?

Estou descobrindo milhares de maneiras diferentes de criar coisas que se assemelham ao OOP ... mas não tenho ideia de qual é a maneira mais usada / prática / limpa.

Pergunta bônus : qual é o "estilo OOP" sugerido para usar com o MongooseJS? (pode um documento MongooseJS ser visto como uma classe e um modelo usado como uma instância?)

EDITAR

aqui está um exemplo em JsFiddle , forneça 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
fonte
12
Não há nada realmente específico sobre OO JS em node.js. Existe apenas OO JS. Sua pergunta é sobre a tradução de técnicas Java OOP para JS, o que não está certo . Acho melhor você gastar o mesmo tempo / energia aprendendo como o modelo baseado em protótipo de JS funciona e como você pode usá-lo a seu favor
Elias Van Ootegem
1
Além disso, você não tem classes em JavaScript. Você pode criar um comportamento de classe com funções, mas geralmente não é uma boa ideia.
m_vdbeek
1
@AwakeZoldiek O que você quer dizer com não é um "recurso nativo"?
Esailija
4
@fusio Com herança prototípica em geral, objetos / instâncias herdam de outros objetos / instâncias. Portanto, as classes não são usadas porque você não está trabalhando com definições abstratas. Portanto, a herança é feita por meio de uma prototypecadeia . E, não, o objeto não oferece suporte a membros " privados ". Apenas os fechamentos podem oferecer isso, embora os módulos / scripts em Node.js sejam implementados como fechamentos.
Jonathan Lonowski
1
@Esailija Não tive a intenção de sugerir que encerramentos podem criar membros privados. Estava apenas sugerindo que fechamentos e variáveis ​​incluídas são o mais próximo que você pode obter em JavaScript. Mas, para a outra parte: a única " implementação " que mencionei diz respeito aos módulos do Node, que são avaliados dentro de um encerramento onde alguns dos globais são definidos como exclusivos para cada script.
Jonathan Lonowski

Respostas:

116

Este é um exemplo que funciona fora da caixa. Se você quiser menos "hacky", deve usar uma biblioteca de herança ou algo parecido.

Bem, em um arquivo animal.js você escreveria:

var method = Animal.prototype;

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

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

module.exports = Animal;

Para usá-lo em outro arquivo:

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

var john = new Animal(3);

Se você quiser uma "subclasse", dentro de 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;

Além disso, você pode considerar "empréstimo de método" em vez de herança vertical. Você não precisa herdar de uma "classe" para usar seu método em sua classe. Por exemplo:

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

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;
Esailija
fonte
qual é a diferença entre usar Animal.prototype.getAge= function(){}e simplesmente adicionar this.getAge = function(){}dentro function Animal() {}? A subclasse parece um pouco hackeada .. com biblioteca de "herança" você quer dizer algo como inheritssugerido por @badsyntax?
fusio
4
@fusio sim, você pode fazer algo assim para inherits(Mouse, Animal);limpar um pouco a configuração de herança. A diferença é que você está criando uma nova identidade de função para cada objeto instanciado em vez de compartilhar uma função. Se você tiver 10 mouses, terá criado 10 identidades de função (isso é apenas porque o mouse tem um método, se tivesse 10 métodos, 10 mouses criariam 100 identidades de função, seu servidor rapidamente desperdiçaria a maior parte de sua CPU em GC: P) , mesmo que você não os use para nada. A linguagem não tem poder expressivo suficiente para otimizar isso atualmente.
Esailija
Uau. Obrigado :) Isso parece bastante simples, eu também encontrei Details_of_the_Object_Model onde eles comparam JS com Java. Ainda assim, para herdar eles simplesmente fazem: Mouse.prototype = new Animal().. como isso se compara ao seu exemplo? (por exemplo, o que é Object.create()?)
fusio
@fusio Object.create não invoca o construtor ... se o construtor tem efeitos colaterais ou algo parecido (ele pode fazer qualquer coisa que uma função normal pode, ao contrário de Java), então invocá-lo é indesejável ao configurar a cadeia de herança.
Esailija
3
Ainda defendo que, para alguém que está começando a usar JavaScript, hackear uma solução como essa não é uma boa solução. Existem tantas peculiaridades e armadilhas que não são fáceis de depurar que isso não deve ser avisado.
m_vdbeek de
43

Como a comunidade Node.js garante que novos recursos da especificação JavaScript ECMA-262 sejam trazidos aos desenvolvedores Node.js em tempo hábil.

Você pode dar uma olhada nas classes JavaScript . Link MDN para classes JS Nas classes ECMAScript 6 JavaScript são introduzidas, este método fornece uma maneira mais fácil de modelar conceitos OOP em Javascript.

Nota : as classes JS funcionarão apenas no modo estrito .

Abaixo está algum esqueleto de classe, herança escrita em Node.js (versão Node.js usada v5.0.0 )

Declarações de classe:

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Herança:

'use strict';
class Base{

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

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

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

Sugiro usar o inheritsauxiliar que vem com o utilmódulo padrão : http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

Há um exemplo de como usá-lo na página vinculada.

sintaxe ruim
fonte
Esta é a resposta mais útil em relação ao ambiente NodeJS principal.
Philzen
1
Agora parece obsoleto. Do link de resposta: Observação: o uso de util.inherits () não é recomendado. Use a classe ES6 e estenda as palavras-chave para obter suporte à herança de nível de idioma. Observe também que os dois estilos são semanticamente incompatíveis.
Frosty Z,
11

Este é o melhor vídeo sobre JavaScript orientado a objetos na Internet:

O guia definitivo para JavaScript orientado a objetos

Assista do começo ao fim !!

Basicamente, Javascript é uma linguagem baseada em protótipo que é bastante diferente das classes em Java, C ++, C # e outros amigos populares. O vídeo explica os conceitos básicos muito melhor do que qualquer resposta aqui.

Com o ES6 (lançado em 2015), obtivemos uma palavra-chave "class" que nos permite usar "classes" Javascript como faríamos com Java, C ++, C #, Swift, etc.

Captura de tela do vídeo mostrando como escrever e instanciar uma classe / subclasse Javascript: insira a descrição da imagem aqui

Etayluz
fonte
Agradeço por você ter fornecido uma resposta para ES6. Obrigado! Infelizmente, não tenho dados para assistir a um vídeo de 27 minutos. Vou continuar minha busca por orientação escrita.
tim.rohrer
Obrigado pelo vídeo. Ajudei-me a esclarecer muitas dúvidas que tinha sobre javascript.
Kishore Devaraj
4

Na comunidade Javascript, muitas pessoas argumentam que OOP não deve ser usado porque o modelo de protótipo não permite fazer um OOP rígido e robusto nativamente. No entanto, não acho que OOP seja uma questão de linguagem, mas sim uma questão de arquitetura.

Se você deseja usar um OOP realmente forte em Javascript / Node, pode dar uma olhada no framework de código aberto Danf full stack . Ele fornece todos os recursos necessários para um código OOP forte (classes, interfaces, herança, injeção de dependência, ...). Ele também permite que você use as mesmas classes nos lados do servidor (nó) e do cliente (navegador). Além disso, você pode codificar seus próprios módulos danf e compartilhá-los com qualquer pessoa, graças ao Npm.

Gnucki
fonte
-1

Se você está trabalhando por conta própria e deseja a coisa mais próxima de OOP como encontraria em Java, C # ou C ++, consulte a biblioteca javascript, CrxOop. CrxOop fornece sintaxe um tanto familiar para desenvolvedores Java.

Apenas tome cuidado, a OOP do Java não é a mesma encontrada no Javascript. Para obter o mesmo comportamento do Java, use as classes do CrxOop, não as estruturas do CrxOop, e certifique-se de que todos os seus métodos sejam virtuais. Um exemplo da sintaxe é,

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

O código é javascript puro, sem transpilar. O exemplo é retirado de vários exemplos da documentação oficial.

ua692875
fonte