1

I'm starting some work on a rather large JS project and want to make sure I structure the code cleanly and efficiently. I've been reading up on a ton of different approaches to OOP in JS and haven't found one that I really like.

I appreciate the performance improvements offered by using the prototype property, but when you try to add true private methods and variables into the mix it gets rather messy. On the other hand, I really like the closure approach, but I'm not comfortable with the decreased performance. I looked into the module pattern but found it to be pretty verbose. Is there a happy medium somewhere?

To illustrate what I'm struggling with, here are two very small sample classes:

Point Class:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

// Get the x coordinate.
Point.prototype.getX = function() {
  return this.x;
}

// Get the y coordinate.
Point.prototype.getY = function() {
  return this.y;
}

// Get the (x, y) coordinate.
Point.prototype.getXY = function() {
  return {x: this.x, y: this.y};
}

// Set the x coordinate.
Point.prototype.setX = function(x) {
  this.x = x;
}

// Set the y coordinate.
Point.prototype.setY = function(y) {
  this.y = y;
}

// Set the (x, y) coordinate.
Point.prototype.setXY = function(x, y) {
  this.x = x;
  this.y = y;
}

User Class:

function User(guid) {
  var guid = guid;

  // Return the user's GUID.
  this.getGuid = function() {
    return guid;    
  }
}

There will potentially be thousands of point objects in use at any one given time, so I feel like the prototype approach is the best option there. However, if I wanted to add any validation to the setting of coordinates, it could be bypassed by just calling Point.x = <value>. Similarly, I'll likely be calling Point.getXY() the majority of the time, which will be constructing new objects for what are essentially public properties. Should I get rid of the notion of getters and setters for the Point class and just have it create completely open, public objects? This seems wrong in the context of OOP.

For the User class, I want to store a GUID internally such that it can be requested but never modified. This can't be done with the prototype approach (unless I'm missing something), so I'm forced into using some sort of closure. I foresee having on the order of hundreds of users active at once, so the performance isn't as important as it is with the Point class. However, I don't really want to switch between different types of OOP styles throughout the codebase.

So, as a general question, is there an approach to OOP in JS that I'm missing which would allow me to use the prototype property while cleanly privatizing select functions and variables? Or, am I looking in the wrong direction? Should I be approaching this differently?

Xenethyl
  • 3,179
  • 21
  • 31
  • Use Dart or GWT if you really want privacy. Do you really want it? – Esailija Jul 01 '12 at 17:16
  • @Esailija I think privacy will be important throughout this codebase, so I would like to plan accordingly. I feel as though Dart and GWT are overkill as a solution to this problem, however. – Xenethyl Jul 01 '12 at 17:58
  • You can only use conventions like `_` prefix to mark a method as "internal" but there is no real privacy in javascript. Closures are nothing like it, you will create every function object over again anytime you make a new point. If there's 50 methods and you create 1000 points, that's 49950 extra function objects created. On top of that the closures own the data not the objects which can have interesting results if you are not constantly aware of it. Note that you can create "private methods" without any problem but that's not a huge win. – Esailija Jul 01 '12 at 18:20
  • @Esailija I agree with and understand everything you said. This project will potentially be extensible by third parties upon release, so I'm trying to keep an eye towards proper encapsulation and privacy. If you disregard the performance implications of creating tons of function objects, the closure approach does exactly what I would like. So, I was hoping maybe there was some hybrid approach that I hadn't heard of that would get me both performance and privacy. – Xenethyl Jul 01 '12 at 18:31
  • The best you can do without sacrificing anything is private *methods* . See http://jsfiddle.net/QSqsA/2/ . However if you want enforced private *data* as well, you have to use the techniques that are IMO unacceptable for a 3rd party library to use anyway :x – Esailija Jul 01 '12 at 18:41
  • @Esailija So your recommendation would be to mark private methods and variables while using the prototype-based approach? In my example of the `User` class, this would allow a third-party plugin to overwrite a GUID. Does (convenient) OOP in JS really come down to hoping and assuming external code plays nice? – Xenethyl Jul 01 '12 at 20:08
  • 1
    Well, it has worked for popular libraries ([Google Closure](http://stackoverflow.com/questions/2572254/why-does-googles-closure-library-not-use-real-private-members), jQuery and its plugins etc). I'm sure if someone would disregard the conventions and documentation and overwrite it, they would have probably used reflection to do that in other languages anyway? – Esailija Jul 01 '12 at 20:15
  • @Esailija Point well-taken. If you wouldn't mind writing up a short response regarding a prototype approach with marked private variables and functions, I'll go ahead and accept it. It feels wrong going this route in the context of OOP, but I can see why it's desirable. – Xenethyl Jul 01 '12 at 21:07

4 Answers4

1

You can only use documentation and conventions like _ prefix to mark a method as "internal" but there is no real, enforced private members of objects in javascript.

Closures are not acceptable -- they will own the data not the objects which can have "interesting" results, especially when one wants to extend a "class" using closures this way. The other point is that you will use O(n) memory for storing all the function objects which is just unacceptable if it's out of control for the user of the library who could very easily use the library in a way where it would lead to a memory problem.

You can use underscore prefix convention simply like this:

function Point(x, y) {
  this._x = x;
  this._y = y;
}

// Get the x coordinate.
Point.prototype.getX = function() {
  return this._x;
}

// Get the y coordinate.
Point.prototype.getY = function() {
  return this._y;
}
Esailija
  • 138,174
  • 23
  • 272
  • 326
0

I don't know your background but probably oop, so check out coffee-script

Chen Kinnrot
  • 20,609
  • 17
  • 79
  • 141
  • I'd like to develop a solution to this without the use of external libraries, if possible. Thanks for the recommendation, though. – Xenethyl Jul 01 '12 at 17:53
0

Since prototype based Object Orientation isn't really true Object Orientation, I think you're probably looking in the wrong place.

Prototype based objects are clones of whatever they subclass. So if you declare that your object has a property getXY() then its wide open to anything that is cloned from that.

So I guess the real question is, what do you think private functions would buy you in this implementation?

If you really need private functions, I'd encourage you to deal with the mess that you have to deal with in the closure pattern.

Micah Adams
  • 9
  • 1
  • 4
  • Private functions and variables allow me to secure the information that would be encapsulated in each `Point`. For example, if I decide eventually that negative coordinates are invalid, I'd like to be able to enforce that from within the class. The only way I see to accomplish this is via closures, but I'm wary of the performance implications. – Xenethyl Jul 01 '12 at 17:55
0

You can use the closure approach during development, so you know that nobody bypasses validations, and then switch to the faster prototype approach when releasing the code.

var Release = true;
if (Release) {
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }

    // Get the x coordinate.
    Point.prototype.getX = function () {
        return this.x;
    }

    // Get the y coordinate.
    Point.prototype.getY = function () {
        return this.y;
    }

    // Get the (x, y) coordinate.
    Point.prototype.getXY = function () {
        return {
            x: this.x,
            y: this.y
        };
    }

    // Set the x coordinate.
    Point.prototype.setX = function (x) {
        this.x = x;
    }

    // Set the y coordinate.
    Point.prototype.setY = function (y) {
        this.y = y;
    }

    // Set the (x, y) coordinate.
    Point.prototype.setXY = function (x, y) {
        this.x = x;
        this.y = y;
    }
} else {
    function Point(x, y) {
        var _x = x,
            _y = y;
        return {
            // Get the x coordinate.
            getX: function () {
                return _x;
            },

            // Get the y coordinate.
            getY: function () {
                return _y;
            },

            // Get the (x, y) coordinate.
            getXY: function () {
                return {
                    x: _x,
                    y: _y
                };
            },

            // Set the x coordinate.
            setX: function (x) {
                _x = x;
            },

            // Set the y coordinate.
            setY: function (y) {
                _y = y;
            },

            // Set the (x, y) coordinate.
            setXY: function (x, y) {
                _x = x;
                _y = y;
            }
        }
    }
}
user1494736
  • 2,425
  • 16
  • 8