JavaScript Classes
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
Related videos on Youtube
FtDRbwLXw6
Updated on January 30, 2020Comments
-
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 over 11 yearsHave you reviewed some of these articles?
-
FtDRbwLXw6 over 11 yearsYes, 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 over 11 yearsSome JS frameworks such as Prototype provide enhanced support for creating classes. prototypejs.org/learn/class-inheritance
-
Rohan Desai over 11 yearsI 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 over 11 yearsI 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 almost 10 yearsYou 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 over 4 yearsIf 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 over 11 yearswth 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 over 11 yearsHow do you do inheritance (or protected members) here?
-
FtDRbwLXw6 over 11 yearsThe 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 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 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 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 over 11 yearsWithout 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 over 11 years@drrcknlsn what did you change in that example?
-
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 over 11 years
-
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 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 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 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 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 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 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 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 over 10 yearsWell, 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 about 7 yearsThis is really a comment, plus a link-only answer. Should be posted as a comment instead.