48

I understand basic JavaScript pseudo-classes:

function Foo(bar) {
    this._bar = bar;
}

Foo.prototype.getBar = function() {
    return this._bar;
};

var foo = new Foo('bar');
alert(foo.getBar()); // 'bar'
alert(foo._bar); // 'bar'

I also understand the module pattern, which can emulate encapsulation:

var Foo = (function() {
    var _bar;

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
})();

Foo.setBar('bar');
alert(Foo.getBar()); // 'bar'
alert(Foo._bar); // undefined

But there are un-OOP-like properties to both of these patterns. The former does not provide encapsulation. The latter does not provide instantiation. Both patterns can be modified to support pseudo-inheritance.

What I'd like to know is if there is any pattern that allows:

  • Inheritance
  • Encapsulation (support for "private" properties/methods)
  • Instantiation (can have multiple instances of the "class", each with its own state)
FtDRbwLXw6
  • 27,774
  • 13
  • 70
  • 107
  • 3
    Have you reviewed some of [these articles?](https://www.google.com/search?q=javascript+oop) – Robert Harvey Sep 26 '12 at 21:09
  • 8
    Yes, I have a bookmark folder with dozens of OOP JS articles. I've read Crockford, Resig, Osmani, and lots of other better-than-me JS developers' opinions on the matter. – FtDRbwLXw6 Sep 26 '12 at 21:12
  • Some JS frameworks such as Prototype provide enhanced support for creating classes. http://prototypejs.org/learn/class-inheritance – Robert Harvey Sep 26 '12 at 21:14
  • I remember seeing answers relating to this for questions on how to create "protected" variables/methods in Javascript. I can't find them right now but you might have better luck searching for that term. (I would advise against trying to hard though, it gets really complicated and not idiomatic) – hugomg Sep 26 '12 at 21:23
  • I discuss some of these patterns of how to deal with this issue in my blog. Please check it out: http://ncombo.wordpress.com/2012/12/30/javascript-prototype-instantiation/ – Jon Dec 30 '12 at 10:17
  • You might be interested in Typescript, or specifically the code it produces when compiling its classes into valid JS. All of the real typing is at compile-time, but it produces prototypes that behave very much like traditional classes. – ssube Sep 05 '14 at 15:36
  • If you're looking for samples of code to mimic OOP behaviors similar to programming languages like java in JavaScript, Checkout [JavaScript Class](https://dashbrace.com/catalog/javascript/class) here. – Shnd Oct 05 '19 at 07:08

8 Answers8

73

what about this :

var Foo = (function() {
    // "private" variables 
    var _bar;

    // constructor
    function Foo() {};

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return _bar;
    };
    Foo.prototype.setBar = function(bar) {
        _bar = bar;
    };

    return Foo;
})();

And now we have instantiation, encapsulation and inheritance.
But, there still is a problem. The private variable is static because it's shared across all instances of Foo. Quick demo :

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'b' :(    

A better approach might be using conventions for the private variables : any private variable should start with an underscore. This convention is well known and widely used, so when another programmer uses or alters your code and sees a variable starting with underscore, he'll know that it's private, for internal use only and he won't modify it.
Here's the rewrite using this convention :

var Foo = (function() {
    // constructor
    function Foo() {
        this._bar = "some value";
    };

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return this._bar;
    };
    Foo.prototype.setBar = function(bar) {
        this._bar = bar;
    };

    return Foo;
})();

Now we have instantiation, inheritance, but we've lost our encapsulation in favor of conventions :

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'a' :) 
alert(b.getBar()); // alerts 'b' :) 

but the private vars are accessible :

delete a._bar;
b._bar = null;
alert(a.getBar()); // alerts undefined :(
alert(b.getBar()); // alerts null :(
mseifert
  • 5,390
  • 9
  • 38
  • 100
gion_13
  • 41,171
  • 10
  • 96
  • 108
  • How do you do inheritance (or protected members) here? – hugomg Sep 26 '12 at 21:20
  • 2
    The private state here would be shared across all instances of the class. You couldn't create two instances that had different states with this code. – FtDRbwLXw6 Sep 26 '12 at 21:21
  • @GGG: I don't know what "ctor" is, but the `_bar` variable here is shared across all instances. Feel free to test it. I did. – FtDRbwLXw6 Sep 26 '12 at 21:22
  • @gion_13: You're only instantiating once. Instantiate twice or more and see what happens when you do something like: `a = new Foo(); b = new Foo(); a.setBar('a'); b.setBar('b'); alert(a.getBar());`. You will not see "a" as expected, but "b" instead, because `_bar` is not unique to each instance. – FtDRbwLXw6 Sep 26 '12 at 21:29
  • @GGG: That's not bad at all, but you seem to lose the ability to access state from within the constructor: [http://jsfiddle.net/xmaSk/](http://jsfiddle.net/xmaSk/)? – FtDRbwLXw6 Sep 26 '12 at 21:46
  • @drrcknlsn what did you change in that example? – Dagg Nabbit Sep 26 '12 at 23:00
  • @GGG: Sorry, I pasted the wrong fiddle. It was [this one](http://jsfiddle.net/TNdAm/1/). I have been messing with it since then, and it was my fault. I didn't pass the parameters through properly. It looks to be working and satisfies all 3 requirements. Why don't people use this pattern? I can't find anything wrong with it. – FtDRbwLXw6 Sep 26 '12 at 23:11
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/17206/discussion-between-ggg-and-drrcknlsn) – Dagg Nabbit Sep 26 '12 at 23:24
  • @GGG The major problem I see with your method is that you have to write the member variable's name 6 times to define and initialize it and that's with out counting setter and getter functions which are necessary if you want to access the member externally. It's too much typing for each member variable. Remember the DRY principal! – jcoffland Dec 27 '12 at 08:48
  • @jcoffland indeed, you'll rarely catch me writing getters and setters in JS. I just modified gion's code as a POC, but later in chat explained that it was a bad idea. I guess that chat log is lost now. – Dagg Nabbit Dec 27 '12 at 20:06
  • @jcoffland now that I look at it again, I guess I like it a little better like this, although I'd really rather deal with prototypes than create separate function instances each time the constructor runs -- http://jsfiddle.net/z3SYa/2/ – Dagg Nabbit Dec 27 '12 at 20:33
  • @GGG I agree that it would be preferable not to recreate the function instances each time but I think your latest version meets the requirements of the original post and is fairly clean. You should consider adding it as a proper answer. – jcoffland Dec 28 '12 at 01:30
  • @jcoffland because it seems like an antipattern, just an elaborate reorganization of this: http://jsfiddle.net/z3SYa/3/ It's really just the old "do everything in the constructor" technique, disguised as something that looks more like a class definition with a constructor inside it. – Dagg Nabbit Dec 28 '12 at 03:06
  • @gion_13 To actually do 'private' and 'static' you can use custom getters for property values. This will keep things 'static' and... psuedo-private. – Eric Hodonsky Oct 15 '13 at 19:06
  • @Relic yes, you're right, there might be other ways to achieve this by using getters and setters, but there's no clean cross-browser way to do it. For example, microsoft says that IE does not implement any non-ecmascript features such as `defineProperty` (http://goo.gl/uH7rQk) and using getters and setters is not supported in IE 8 and lower (http://goo.gl/k5j0Gt). You should post an answer though, to let anyone else viewing this thread know about the other *new* ways. – gion_13 Oct 16 '13 at 07:09
  • @gion_13 Yeah man, I know... but do we still really care about IE8 at this point? Also, getters and setters in ecmascript (javascript's core language) have been around for a how minute so I wouldn't say 'new': http://kangax.github.io/es5-compat-table/ . Oh and as far as posting my answer... way to lazy at the moment to write it out =D – Eric Hodonsky Oct 29 '13 at 00:15
  • Well, for my personal sites, I don't even bother to open them in IE, but you would be surprised to see how many projects at work I have to scrue up because I need to add support for IE8 and lower. So, until windows XP disappears FOREVER :), we (at least I know I do) need to code js like in the 90's. – gion_13 Oct 29 '13 at 08:54
7

I think what you're looking for is the "Revealing Prototype Pattern".

Dan Wahlin has a great blog post: http://weblogs.asp.net/dwahlin/archive/2011/08/03/techniques-strategies-and-patterns-for-structuring-javascript-code-revealing-prototype-pattern.aspx

and even better Pluralsight course on this and other related JavaScript structures: http://pluralsight.com/training/courses/TableOfContents?courseName=structuring-javascript&highlight=dan-wahlin_structuring-javascript-module1!dan-wahlin_structuring-javascript-module2!dan-wahlin_structuring-javascript-module5!dan-wahlin_structuring-javascript-module4!dan-wahlin_structuring-javascript-module3#structuring-javascript-module1

Joe Davis
  • 233
  • 3
  • 11
  • 1
    wth is he tdoing here? If I understand, he is just creating some static variables and its not diferent then the regular prototype pattern. – hugomg Sep 26 '12 at 21:20
  • Without repeating the entire post, in the simplest terms, he is returning a set of public interfaces while relying on closure to keep the state and structure in place. – Joe Davis Sep 26 '12 at 22:00
5

Closures are your friend!

Simply add the following tiny function to your top-level namespace and you're ready to OOP, complete with

  • encapsulation, with static and instance, private and public variables and methods
  • inheritance
  • class-level injection (eg. for singleton services)
  • no constraints, no framework, just plain old Javascript

function clazz(_class, _super) {
    var _prototype = Object.create((_super || function() {}).prototype);
    var _deps = Array.isArray(_class) ? _class : [_class]; _class = _deps.pop();
    _deps.push(_super);
    _prototype.constructor = _class.apply(_prototype, _deps) || _prototype.constructor;
    _prototype.constructor.prototype = _prototype;
    return _prototype.constructor;
}

The above function simply wires up the given class' prototype and eventual parent constructor, and returns the resulting constructor, ready for instantiation.

Now you can most naturally declare your base classes (ie. that extend {}) in a few lines of code, complete with static, instance, public and private properties and methods:

MyBaseClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions declared here are private static variables and methods
    // properties of 'this' declared here are public static variables and methods
    return function MyBaseClass(arg1, ...) { // or: this.constructor = function(arg1, ...) {
        // local variables and functions declared here are private instance variables and methods
        // properties of 'this' declared here are public instance variables and methods
    };
});

Extending a class? All the more natural as well:

MySubClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions are private static variables and methods
    // properties of this are public static variables and methods
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        // local variables and functions are private instance variables and methods
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // properties of 'this' are public instance variables and methods
    };
}, MyBaseClass); // extend MyBaseClass

In other words, pass the parent class constructor to the clazz function, and add _super.call(this, arg1, ...) to the child class' constructor, which calls the parent class' constructor with the required arguments. As with any standard inheritance scheme, the parent constructor call must come first in the child constructor.

Note that you're free to either explicitly name the contructor with this.constructor = function(arg1, ...) {...}, or this.constructor = function MyBaseClass(arg1, ...) {...} if you need simple access to the constructor from the code inside the constructor, or even simply return the constructor with return function MyBaseClass(arg1, ...) {...} as in the above code. Whichever you feel most comfortable with.

Simply instantiate objects from such classes as you normally would from a constructor: myObj = new MyBaseClass();

Notice how closures nicely encapsulate all of a class' functionality, including its prototype and constructor, providing a natural namespace for static and instance, private and public properties and methods. The code within a class closure is completely free of constraints. No framework, no constraints, just plain old Javascript. Closures rule!

Oh, and if you want to inject singleton dependencies (eg. services) into your class (ie. prototype), clazz will do this for you à la AngularJS:

DependentClass = clazz([aService, function(_service, _super) { // class closure, 'this' is the prototype
    // the injected _service dependency is available anywhere in this class
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // the injected _service dependency is also available in the constructor
    };
}], MyBaseClass); // extend MyBaseClass

As the above code attempts to illustrate, to inject singletons into a class simply place the class closure as the last entry into an array with all its dependencies. Also add the corresponding parameters to the class closure in front of the _super parameter and in the same order as in the array. clazz will inject the dependencies from the array as arguments into the class closure. The dependencies are then available anywhere within the class closure, including the constructor.

In fact, since the dependencies are injected into the prototype, they are available to static methods even before any object is instantiated from the class. This is very powerful for wiring up your apps or unit and end-to-end tests. It also removes the need to inject singletons into constructors, which otherwise unnecessarily clobbers the constructor's code.

Check this fiddle: http://jsfiddle.net/5uzmyvdq/1/

Feedback and suggestions most welcome!

3

Javascript is certainly OOP. You always have polymorphism, however you have to sacrifice either encapsulation or instantiation which is the problem you ran into.

Try this to just brush up on your options. http://www.webmonkey.com/2010/02/make_oop_classes_in_javascript/ Also an old question that I had bookmarked: Is JavaScript object-oriented?

Community
  • 1
  • 1
user1701047
  • 737
  • 5
  • 7
3

JavaScript classes are introduced in ECMAScript 6 and are syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript. JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.

You can see more in this link Mozilla Community

Github

0

I was thinking about this particular subject recently and the limitations of the various approaches. The best solution I've been able to come up with is below.

It seems to solve the problems with inheritance, instantiation and ecapsulation (at least from tests on Google Chrome v.24) although probably at a cost in memory usage.

function ParentClass(instanceProperty) {
  // private
  var _super = Object.create(null),
      privateProperty = "private " + instanceProperty;
  // public
  var api = Object.create(_super);
  api.constructor = this.constructor;
  api.publicMethod = function() {
     console.log( "publicMethod on ParentClass" );
     console.log( privateProperty );
  };
  api.publicMethod2 = function() {
     console.log( "publicMethod2 on ParentClass" );
     console.log( privateProperty );
  };
  return api;
}

function SubClass(instanceProperty) {
    // private
    var _super = ParentClass.call( this, instanceProperty ),
        privateProperty = "private sub " + instanceProperty;
    // public
    var api = Object.create(_super);
    api.constructor = this.constructor;
    api.publicMethod = function() {
       _super.publicMethod.call(this); // call method on ParentClass
        console.log( "publicMethod on SubClass" );
        console.log( privateProperty );
    }
    return api;
}

var par1 = new ParentClass(0),
    par2 = new ParentClass(1),
    sub1 = new SubClass(2),
    sub2 = new SubClass(3);

par1.publicMethod();
par2.publicMethod();
sub1.publicMethod();
sub2.publicMethod();
par1.publicMethod2();
par2.publicMethod2();
sub1.publicMethod2();
sub2.publicMethod2();
BrettJephson
  • 416
  • 5
  • 13
0

One problem with a lot of JS classes out there is that they do not secure their fields and methods which means that anyone using it may accidentally replace a method. For example the code:

function Class(){
    var name="Luis";
    var lName="Potter";
}

Class.prototype.changeName=function(){
    this.name="BOSS";
    console.log(this.name);
};

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced");
};
test.changeName();
test.changeName();

will output:

ugly
BOSS
replaced 
replaced 

As you can see the changeName function gets overriden. The following code would secure the class methods and fields and the getters and setters would be used to access them making this more of a "regular" class found in other languages.

function Class(){
    var name="Luis";
    var lName="Potter";

    function getName(){
         console.log("called getter"); 
         return name;
    };

    function setName(val){
         console.log("called setter"); 
         name = val
    };

    function getLName(){
         return lName
    };

    function setLName(val){
        lName = val;
    };

    Object.defineProperties(this,{
        name:{
            get:getName, 
            set:setName, 
            enumerable:true, 
            configurable:false
        },
        lastName:{
            get:getLName, 
            set:setLName, 
            enumerable:true, 
            configurable:false
        }
    });
}

Class.prototype.changeName=function(){
    this.name="BOSS";
};   

Object.defineProperty(Class.prototype, "changeName", {
    writable:false, 
    configurable:false
});

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced")
};
test.changeName();
test.changeName();

This outputs:

called getter
Luis
called setter 
called getter 
ugly 
called setter 
called setter 
called setter 

Now your class methods cannot be replaced by random values or functions and the code in the getters and setters always run when attempting to read or write to field.

jedi
  • 839
  • 12
  • 33
Piacenti
  • 1,188
  • 10
  • 9
-1

This closure allows instantiation and encapsulation but no inheritance.

function Foo(){
    var _bar = "foo";

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
};

a = Foo();
b = Foo();

a.setBar("bar");
alert(a.getBar()); // "bar"
alert(b.getBar()); // "foo"
jdbence
  • 1
  • 1