JavaScript OOP in NodeJS: how?

151,186

Solution 1

This is an example that works out of the box. If you want less "hacky", you should use inheritance library or such.

Well in a file animal.js you would write:

var method = Animal.prototype;

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

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

module.exports = Animal;

To use it in other file:

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

var john = new Animal(3);

If you want a "sub class" then inside 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;

Also you can consider "Method borrowing" instead of vertical inheritance. You don't need to inherit from a "class" to use its method on your class. For instance:

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

 }

 method.add = Array.prototype.push;

 ...

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

Solution 2

As Node.js community ensure new features from the JavaScript ECMA-262 specification are brought to Node.js developers in a timely manner.

You can take a look at JavaScript classes. MDN link to JS classes In the ECMAScript 6 JavaScript classes are introduced, this method provide easier way to model OOP concepts in Javascript.

Note : JS classes will work in only strict mode.

Below is some skeleton of class,inheritance written in Node.js ( Used Node.js Version v5.0.0 )

Class declarations :

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Inheritance :

'use strict';
class Base{

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

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

var childObj = new Child();

Solution 3

I suggest to use the inherits helper that comes with the standard util module: http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

There is an example of how to use it on the linked page.

Solution 4

This is the best video about Object-Oriented JavaScript on the internet:

The Definitive Guide to Object-Oriented JavaScript

Watch from beginning to end!!

Basically, Javascript is a Prototype-based language which is quite different than the classes in Java, C++, C#, and other popular friends. The video explains the core concepts far better than any answer here.

With ES6 (released 2015) we got a "class" keyword which allows us to use Javascript "classes" like we would with Java, C++, C#, Swift, etc.

Screenshot from the video showing how to write and instantiate a Javascript class/subclass: enter image description here

Solution 5

In the Javascript community, lots of people argue that OOP should not be used because the prototype model does not allow to do a strict and robust OOP natively. However, I don't think that OOP is a matter of langage but rather a matter of architecture.

If you want to use a real strong OOP in Javascript/Node, you can have a look at the full-stack open source framework Danf. It provides all needed features for a strong OOP code (classes, interfaces, inheritance, dependency-injection, ...). It also allows you to use the same classes on both the server (node) and client (browser) sides. Moreover, you can code your own danf modules and share them with anybody thanks to Npm.

Share:
151,186
fusio
Author by

fusio

Updated on May 31, 2020

Comments

  • fusio
    fusio almost 4 years

    I am used to the classical OOP as in Java.

    What are the best practices to do OOP in JavaScript using NodeJS?

    Each Class is a file with module.export?

    How to create Classes?

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

    vs. (I am not even sure it is correct)

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

    vs.

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

    How would inheritance work?

    Are there specific modules for implementing OOP in NodeJS?

    I am finding a thousand different ways to create things that resemble OOP.. but I have no clue what is the most used/practical/clean way.

    Bonus question: what is the suggested "OOP style" to use with MongooseJS? (can a MongooseJS document be seen as a Class and a model used as an instance?)

    EDIT

    here is an example in JsFiddle please provide 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
    fusio almost 11 years
    what is the difference between using Animal.prototype.getAge= function(){} and simply adding this.getAge = function(){} inside function Animal() {} ? The sub-class seem a bit hacky.. with "inheritance" library you mean something like inherits as suggested by @badsyntax ?
  • Esailija
    Esailija almost 11 years
    @fusio yes, you can do something like inherits(Mouse, Animal); that cleans up the inheritance set up a bit. The difference is that you are creating new function identity for each instantiated object instead of sharing one function. If you have 10 mice, you have created 10 function identities (that is only because mouse has one method, if it had 10 methods, 10 mice would create 100 function identities, your server would quickly waste most of its CPU on GC :P), even though you will not use them for anything. The language doesn't have enough expressive power to optimize this away currently.
  • fusio
    fusio almost 11 years
    Woah. Thanks :) This seems quite simple, I also found Details_of_the_Object_Model where they compare JS with Java. Still, to inherit they simply do: Mouse.prototype = new Animal().. how does it compare with your example? (e.g. what is Object.create()?)
  • Esailija
    Esailija almost 11 years
    @fusio Object.create doesn't invoke the constructor... if the constructor has side effects or such (it can do anything a normal function can, unlike in Java), then invoking it is undesirable when setting up inheritance chain.
  • badsyntax
    badsyntax almost 11 years
    Naming a variable 'method', which references the prototype, makes no sense at all.
  • Esailija
    Esailija almost 11 years
    @badsyntax well yes it does. It's word against word, what you gonna do?
  • m_vdbeek
    m_vdbeek almost 11 years
    I still maintain that for someone that is starting to use JavaScript, hacking a solution like this isn't a good solution. There are so many quirks and pitfalls that aren't easy to debug that this shouldn't be advised.
  • Esailija
    Esailija almost 11 years
    @AwakeZoldiek Can you give an example then?
  • m_vdbeek
    m_vdbeek almost 11 years
    Yes, you subclass an array and break every for ... in loop or declare two methods with the same name at different places in the prototype chain and don't understand which one is called. There are many other use cases like this ...
  • Esailija
    Esailija almost 11 years
    @AwakeZoldiek That doesn't even make sense. Why would subclassing an array break for..in loop even if you were idiotic enough to use for..in for arrays?
  • m_vdbeek
    m_vdbeek almost 11 years
    No, you misunderstood me. If you create an Object called "MyGreatObject" that has an array's behaviour (constructor) + some added properties, you wouldn't be able to loop through them with for ... in. Also, if done wrong, some array subclassing implementations miss some of the array's native methods and behaviours. There are many blogs on this subject but this is beyond the scope of this question.
  • Esailija
    Esailija almost 11 years
    @AwakeZoldiek If this way has many pitfalls and quirks, why did you feel the need to come up with something like this as an example? Trying to extend a built-in is pretty hard-core thing to attempt and not advocated by me at all.
  • fusio
    fusio almost 11 years
    @AwakeZoldiek could you give me an example of a non-hacky solution to create something structured as the following: a Canvas is an object that contains Shapes. A Shape is an object that can either be a ComplexShape object which contains a Frame object and a Material object, or a simpler shape (e.g. Rectangle object, Circle object, ..). In Java I would simply create a Shape class and then extend it to create the various Rectangle, Circle,.. and the ComplexShape class which would contain in its fields a Frame and a Material. Easy. In JS?
  • Esailija
    Esailija almost 11 years
    @fusio why don't you try to write that taxonomy in JS with what you have learned and show it to us and maybe we will see if you did something wrong.
  • m_vdbeek
    m_vdbeek almost 11 years
    @Esailija : I didn't say you were advocating it, but imo if fusio every encounters an example like this or tries to implement functionalities like these, it would most like be difficult to debug.
  • Esailija
    Esailija almost 11 years
    @AwakeZoldiek in my experience in most questions the problem has been this binding with event handlers and unsurprisingly some people using variables thinking they are object properties, which I strongly advocate against with multiple researched reasons anyway. None of these problems are inherent with constructors and prototypes as shown in the answer. "Subclassing a built-in" is not an everyday activity or a common thing to do at all, so I feel surprised you make that as first example when there were supposed to be many pitfalls.
  • Esailija
    Esailija almost 11 years
    You can take a good sample here stackoverflow.com/questions/tagged/oop+javascript
  • Esailija
    Esailija almost 11 years
    @fusio looks good to me although you should have made a now post possibly on codereview.stackexchange.com. To answer your comment question, _super.constructor.apply( this, arguments ) is more general than Shape.call(this, id): it has no hardcoded constructor name and works for any number of arguments. Note that my method assumes Node.JS and unique file for each "class", the way it works on jsfiddle and with everything thrown together is different because the variables I make would be shared instead of being unique to each file.
  • fusio
    fusio almost 11 years
    Oh, cool! Did not even know existed something like that. Yes, indeed in Node it would be different.
  • Philzen
    Philzen over 9 years
    This is the most helpful answer with regards to the core NodeJS environment.
  • Frosty Z
    Frosty Z over 7 years
    Looks now deprecated. From answer link: Note: usage of util.inherits() is discouraged. Please use the ES6 class and extends keywords to get language level inheritance support. Also note that the two styles are semantically incompatible.
  • tim.rohrer
    tim.rohrer about 6 years
    I appreciate you having provided an answer for ES6. Thank you! Unfortunately, I don't have the data to watch a 27 minute video. I'll continue my search for written guidance.
  • Kishore Devaraj
    Kishore Devaraj over 5 years
    Thanks for the video. I helped me to clear lot of questions i had about javascript.
  • Nicolas Raoul
    Nicolas Raoul about 5 years
    This answer is outdated, better read stackoverflow.com/a/34847083/226958 below.