JavaScript Classes

42,477

Solution 1

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 :(

Solution 2

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

Solution 3

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!

Solution 4

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?

Solution 5

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

Share:
42,477

Related videos on Youtube

FtDRbwLXw6
Author by

FtDRbwLXw6

Updated on January 30, 2020

Comments

  • FtDRbwLXw6
    FtDRbwLXw6 over 4 years

    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)
    • Yinda Yin
      Yinda Yin over 11 years
      Have you reviewed some of these articles?
    • FtDRbwLXw6
      FtDRbwLXw6 over 11 years
      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.
    • Yinda Yin
      Yinda Yin over 11 years
      Some JS frameworks such as Prototype provide enhanced support for creating classes. prototypejs.org/learn/class-inheritance
    • Rohan Desai
      Rohan Desai over 11 years
      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)
    • Jon
      Jon over 11 years
      I discuss some of these patterns of how to deal with this issue in my blog. Please check it out: ncombo.wordpress.com/2012/12/30/…
    • ssube
      ssube almost 10 years
      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.
    • Shnd
      Shnd over 4 years
      If you're looking for samples of code to mimic OOP behaviors similar to programming languages like java in JavaScript, Checkout JavaScript Class here.
  • Rohan Desai
    Rohan Desai over 11 years
    wth is he tdoing here? If I understand, he is just creating some static variables and its not diferent then the regular prototype pattern.
  • Rohan Desai
    Rohan Desai over 11 years
    How do you do inheritance (or protected members) here?
  • FtDRbwLXw6
    FtDRbwLXw6 over 11 years
    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
    FtDRbwLXw6 over 11 years
    @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
    FtDRbwLXw6 over 11 years
    @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
    FtDRbwLXw6 over 11 years
    @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/?
  • Joe Davis
    Joe Davis over 11 years
    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.
  • Dagg Nabbit
    Dagg Nabbit over 11 years
    @drrcknlsn what did you change in that example?
  • FtDRbwLXw6
    FtDRbwLXw6 over 11 years
    @GGG: Sorry, I pasted the wrong fiddle. It was this one. 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.
  • Dagg Nabbit
    Dagg Nabbit over 11 years
  • jcoffland
    jcoffland over 11 years
    @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!
  • Dagg Nabbit
    Dagg Nabbit over 11 years
    @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
    Dagg Nabbit over 11 years
    @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 -- jsfiddle.net/z3SYa/2
  • jcoffland
    jcoffland over 11 years
    @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.
  • Dagg Nabbit
    Dagg Nabbit over 11 years
    @jcoffland because it seems like an antipattern, just an elaborate reorganization of this: 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.
  • Eric Hodonsky
    Eric Hodonsky over 10 years
    @gion_13 To actually do 'private' and 'static' you can use custom getters for property values. This will keep things 'static' and... psuedo-private.
  • gion_13
    gion_13 over 10 years
    @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 (goo.gl/uH7rQk) and using getters and setters is not supported in IE 8 and lower (goo.gl/k5j0Gt). You should post an answer though, to let anyone else viewing this thread know about the other new ways.
  • Eric Hodonsky
    Eric Hodonsky over 10 years
    @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': 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
  • gion_13
    gion_13 over 10 years
    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.
  • Jean-François Corbett
    Jean-François Corbett about 7 years
    This is really a comment, plus a link-only answer. Should be posted as a comment instead.