JavaScript OOP en NodeJS: ¿cómo?

118

Estoy acostumbrado a la POO clásica como en Java.

¿Cuáles son las mejores prácticas para hacer OOP en JavaScript usando NodeJS?

Cada clase es un archivo con module.export?

¿Cómo crear clases?

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

vs. (ni siquiera estoy seguro de que sea correcto)

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

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

vs.

this.Class = function() {}

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

...

¿Cómo funcionaría la herencia?

¿Existen módulos específicos para implementar OOP en NodeJS?

Estoy encontrando mil formas diferentes de crear cosas que se parezcan a POO ... pero no tengo ni idea de cuál es la forma más utilizada / práctica / limpia.

Pregunta adicional : ¿cuál es el "estilo OOP" sugerido para usar con MongooseJS? (¿se puede ver un documento MongooseJS como una clase y un modelo como instancia?)

EDITAR

aquí hay un ejemplo en JsFiddle, por favor envíe sus comentarios.

//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
fuente
12
No hay nada realmente específico sobre OO JS en node.js. Solo hay OO JS. Su pregunta es sobre la traducción de técnicas Java OOP a JS, lo cual no es correcto . Creo que es mejor que dedique el mismo tiempo / energía a aprender cómo funciona el modelo basado en prototipos de JS y cómo puede usarlo para su ventaja
Elias Van Ootegem
1
Además, no tiene clases en JavaScript. Puede crear un comportamiento de clase con funciones, pero generalmente no es una buena idea.
m_vdbeek
1
@AwakeZoldiek ¿Qué quiere decir con que no es una "característica nativa"?
Esailija
4
@fusio Con la herencia de prototipos en general, los objetos / instancias heredan de otros objetos / instancias. Por lo tanto, las clases no se utilizan porque no se trabaja con definiciones abstractas. Entonces, la herencia se realiza a través de una prototypecadena . Y no, el objeto no admite miembros " privados ". Solo los cierres pueden ofrecer eso, aunque los módulos / scripts en Node.js se implementan como cierres.
Jonathan Lonowski
1
@Esailija En realidad, no quise sugerir que los cierres puedan crear miembros privados. Solo estaba sugiriendo que los cierres y las variables adjuntas son lo más cercano que puede obtener en JavaScript. Pero, por otra parte: la única " implementación " que mencioné se refería a los módulos de nodo, que se evalúan dentro de un cierre donde algunos de los globales se definen de forma única para cada script.
Jonathan Lonowski

Respuestas:

116

Este es un ejemplo que funciona fuera de la caja. Si quieres menos "hacky", debes usar la biblioteca de herencia o algo así.

Bueno, en un archivo animal.js escribirías:

var method = Animal.prototype;

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

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

module.exports = Animal;

Para usarlo en otro archivo:

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

var john = new Animal(3);

Si desea una "subclase", 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;

También puede considerar el "Método de préstamo" en lugar de la herencia vertical. No necesitas heredar de una "clase" para usar su método en tu clase. Por ejemplo:

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

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;
Esailija
fuente
¿Cuál es la diferencia entre usar Animal.prototype.getAge= function(){}y simplemente agregar this.getAge = function(){}adentro function Animal() {}? La subclase parece un poco hacky ... con la biblioteca de "herencia", ¿te refieres a algo como inheritslo sugerido por @badsyntax?
fusio
4
@fusio sí, puedes hacer algo así para inherits(Mouse, Animal);limpiar un poco la configuración de herencia. La diferencia es que está creando una nueva identidad de función para cada objeto instanciado en lugar de compartir una función. Si tiene 10 ratones, ha creado 10 identidades de funciones (eso es solo porque el mouse tiene un método, si tuviera 10 métodos, 10 ratones crearían 100 identidades de funciones, su servidor desperdiciaría rápidamente la mayor parte de su CPU en GC: P) , aunque no los usarás para nada. El lenguaje no tiene suficiente poder expresivo para optimizar esto actualmente.
Esailija
Woah. Gracias :) Esto parece bastante simple, también encontré Details_of_the_Object_Model donde comparan JS con Java. Aún así, para heredar simplemente hacen: Mouse.prototype = new Animal().. ¿cómo se compara con su ejemplo? (por ejemplo, ¿qué es Object.create()?)
fusio
@fusio Object.create no invoca al constructor ... si el constructor tiene efectos secundarios o algo así (puede hacer cualquier cosa que pueda hacer una función normal, a diferencia de Java), entonces invocarlo no es deseable al configurar la cadena de herencia.
Esailija
3
Sigo manteniendo que para alguien que está comenzando a usar JavaScript, piratear una solución como esta no es una buena solución. Hay tantas peculiaridades y trampas que no son fáciles de depurar que no debería advertirse.
m_vdbeek
43

Como la comunidad de Node.js, se asegura de que las nuevas características de la especificación JavaScript ECMA-262 lleguen a los desarrolladores de Node.js de manera oportuna.

Puedes echar un vistazo a las clases de JavaScript . Enlace MDN a clases JS En las clases de JavaScript ECMAScript 6 se introducen, este método proporciona una manera más fácil de modelar conceptos de POO en Javascript.

Nota : Las clases JS funcionarán solo en modo estricto .

A continuación se muestra el esqueleto de la clase, la herencia escrita en Node.js (versión usada de Node.js v5.0.0 )

Declaraciones de clase:

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Herencia :

'use strict';
class Base{

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

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

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

Sugiero usar el inheritsayudante que viene con el utilmódulo estándar : http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

Hay un ejemplo de cómo usarlo en la página vinculada.

mala sintaxis
fuente
Esta es la respuesta más útil con respecto al entorno principal de NodeJS.
Philzen
1
Parece ahora obsoleto. Desde el enlace de respuesta: Nota: no se recomienda el uso de util.inherits (). Utilice la clase ES6 y amplíe las palabras clave para obtener compatibilidad con la herencia de nivel de idioma. También tenga en cuenta que los dos estilos son semánticamente incompatibles.
Frosty Z
11

Este es el mejor video sobre JavaScript orientado a objetos en Internet:

La guía definitiva para JavaScript orientado a objetos

¡Mira de principio a fin!

Básicamente, Javascript es un lenguaje basado en prototipos que es bastante diferente a las clases de Java, C ++, C # y otros amigos populares. El video explica los conceptos básicos mucho mejor que cualquier respuesta aquí.

Con ES6 (lanzado en 2015) obtuvimos una palabra clave de "clase" que nos permite usar "clases" de Javascript como lo haríamos con Java, C ++, C #, Swift, etc.

Captura de pantalla del video que muestra cómo escribir y crear una instancia de una clase / subclase de Javascript: ingrese la descripción de la imagen aquí

etayluz
fuente
Le agradezco que haya proporcionado una respuesta para ES6. ¡Gracias! Desafortunadamente, no tengo los datos para ver un video de 27 minutos. Continuaré mi búsqueda de orientación escrita.
tim.rohrer
Gracias por el video. Me ayudé a aclarar muchas preguntas que tenía sobre javascript.
Kishore Devaraj
4

En la comunidad de Javascript, mucha gente argumenta que la POO no debería usarse porque el modelo prototipo no permite hacer una POO estricta y robusta de forma nativa. Sin embargo, no creo que la POO sea una cuestión de idioma, sino más bien una cuestión de arquitectura.

Si desea utilizar una POO realmente sólida en Javascript / Node, puede echar un vistazo al marco de código abierto de pila completa Danf . Proporciona todas las características necesarias para un código OOP sólido (clases, interfaces, herencia, inyección de dependencia, ...). También le permite utilizar las mismas clases en el lado del servidor (nodo) y del cliente (navegador). Además, puede codificar sus propios módulos danf y compartirlos con cualquiera gracias a Npm.

Gnucki
fuente
-1

Si está trabajando por su cuenta y desea lo más parecido a OOP como lo encontraría en Java o C # o C ++, consulte la biblioteca de javascript, CrxOop. CrxOop proporciona una sintaxis algo familiar para los desarrolladores de Java.

Solo tenga cuidado, la POO de Java no es la misma que se encuentra en Javascript. Para obtener el mismo comportamiento que en Java, use las clases de CrxOop, no las estructuras de CrxOop, y asegúrese de que todos sus métodos sean virtuales. Un ejemplo de sintaxis es,

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

El código es javascript puro, sin transpiración. El ejemplo se toma de varios ejemplos de la documentación oficial.

ua692875
fuente