Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No ways to have class-based objects in javascript?

The javascript prototype-based object-oriented programming style is interesting, but there are a lot of situations where you need the ability to create objects from a class.

For instance in a vector drawing application, the workspace will usually be empty at the beginning of the drawing : I cannot create a new "line" from an existing one. More generally, every situation where objects are being dynamically created require the use of classes.

I've read a lot of tutorials and the book "Javascript : the good parts", but yet it seems to me that there is no way to define classes that respect 1) encapsulation and 2) efficient member methods declaration (I mean : member methods that are being defined once, and shared among every class instances).

To define private variables, closures are being used :

function ClassA() {     var value = 1;     this.getValue = function()     {         return value;     } } 

The problem here is that every instance of "ClassA" will have its own copy of the member function "getValue", which is not efficient.

To define member functions efficiently, prototype is being used :

function ClassB() {     this.value = 1; }  ClassB.prototype.getValue = function() {     return this.value; } 

The problem here is that the member variable "value" is public.

I don't think that this issue can be solved easily, since "private" variables need to be defined DURING object creation (so that the object can have access to its context of creation, without exposing thoses values) whereas prototype-based member functions definition has to be done AFTER object creation, so that prototype makes sense ("this.prototype" does not exists, I've checked).

Or am I missing something ?


EDIT :

First of all, thank you for your interesting answers.

I just wanted to add a little precision to my initial message :

What I really want to do is to have 1) private variables (encapsulation is good, because people only have access to what they need) and 2) efficient member methods declaration (avoid copies).

It seems that simple private variables declaration can really only be achieved via closure in javascript, that's essentially why I focused on the class based approach. If there is a way to achieve simple private variables declaration with a prototype based approach, that's okay for me, I'm not a fierce class-based approach proponnent.

After reading the answers, it seems like the simple solution is to forget about privates, and use a special coding conventions to detter other programmers from accessing "private" variables directly...

And I agree, my title / first sentence was misleading regarding the issue I wanted to discuss here.

like image 519
thomasc Avatar asked Jun 09 '13 09:06

thomasc


People also ask

Why JavaScript is not class-based?

In JavaScript, there are no classes in the class-based OOP sense of the word. JavaScript works with objects. If you want to encapsulate a few functions and properties together, you would create an object containing functions and properties, and not a class.

Are there no classes in JavaScript?

JavaScript didn't originally have classes. Classes were added with the introduction of ECMASCRIPT 6 (es6), a new and improved version of JavaScript (ECMASCRIPT 5 being the older version). A typical JavaScript class is an object with a default constructor method.

What are the options you have in JavaScript to create classes?

Class methods are created with the same syntax as object methods. Use the keyword class to create a class. Always add a constructor() method. Then add any number of methods.

Can objects have methods in JavaScript?

Objects in JavaScript are collections of key/value pairs. The values can consist of properties and methods, and may contain all other JavaScript data types, such as strings, numbers, and Booleans.


2 Answers

Shh, come here! Wanna hear a secret?

Classical inheritance is a tested and tried approach.

It is useful to implement it in JavaScript often. Classes are a nice concept to have and having templates for modeling our world after objects is awesome.

Classical inheritance is just a pattern. It's perfectly OK to implement classical inheritance in JavaScript if it's the pattern you need for your use case.

Prototypical inheritance focuses on sharing functionality and that's awesome (dinasaur drumstick awesome), but in some cases you want to share a data-scheme and not functionality. That's a problem prototypical inheritance does not address at all.

So, you're telling me classes are not evil like everyone keeps telling me?

No, they are not. What the JS community frowns upon is not the concept of classes, it's limiting yourself to just classes for code reuse. Just like the language does not enforce strong or static typing, it doesn't enforce schemes on object structure.

In fact, behind the scene clever implementations of the language can turn your normal objects to something resembling classical inheritance classes.

So, how do classes work in JavaScript

Well, you really only need a constructor:

function getVehicle(engine){     return { engine : engine }; }  var v = getVehicle("V6"); v.engine;//v6 

We now have a vehicle class. We didn't need to define a Vehicle class explicitly using a special keyword. Now, some people don't like to do things this way and are used to the more classical way. For this JS provides (silly imho) syntactic sugar by doing:

function Vehicle(engine){      this.engine = engine; } var v = new Vehicle("V6"); v.engine;//v6 

That's the same thing as the example above for the most part.

So, what are we still missing?

Inheritance and private members.

What if I told you basic subtyping is very simple in JavaScript?

JavaScript's notion of typing is different than what we're used to in other languages. What does it mean to be a sub-type of some type in JS?

var a = {x:5}; var b = {x:3,y:3}; 

Is the type of b a sub type of the type of a? Let's say if it is according to (strong) behavioral subtyping (the LSP):

<<<< Begin technical part

  • Contravariance of method arguments in the subtype - Is fully preserved in this sort of inheritance.
  • Covariance of return types in the subtype - Is fully preserved in this sort of inheritance.
  • No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the supertype. - Is fully preserved in this sort of inheritance.

Also,

  • Preconditions cannot be strengthened in a subtype.
  • Postconditions cannot be weakened in a subtype.
  • Invariants of the supertype must be preserved in a subtype.
  • The history rule

All of these are again, are up to us to keep. We can keep them as tightly or loosly as we want, we don't have to, but we surely can.

So matter of fact, as long as we abide to these rules above when implementing our inheritance, we're fully implementing strong behavioral subtyping, which is a very powerful form of subtyping (see note*).

>>>>> End technical part

Trivially, one can also see that structural subtyping holds.

How would this apply to our Car example?

function getCar(typeOfCar){     var v = getVehicle("CarEngine");     v.typeOfCar = typeOfCar;     return v; } v = getCar("Honda"); v.typeOfCar;//Honda; v.engine;//CarEngine 

Not too hard, was it? What about private members?

function getVehicle(engine){     var secret = "Hello"     return {         engine : engine,         getSecret : function() {             return secret;         }     }; } 

See, secret is a closure variable. It's perfectly "private", it works differently than privates in languages like Java, but it's impossible to access from the outside.

What about having privates in functions?

Ah! That's a great question.

If we want to use a private variable in a function we share on the prototype we need to firrst understand how JS closures and functions work.

In JavaScript functions are first class. This means you can pass functions around.

function getPerson(name){     var greeting = "Hello " + name;     return {         greet : function() {             return greeting;         }     }; }  var a = getPerson("thomasc"); a.greet(); //Hello thomasc 

So far so good, but we can pass that function bounded to a around to other objects! This lets you do very loose decoupling which is awesome.

var b = a.greet; b(); //Hello thomasc 

Wait! How did b know the person's name is thomasc? That's just the magic of closures. Pretty awesome huh?

You might be worried about performance. Let me tell you how I learned to stop worrying and started to love the optimizing JIT.

In practice, having copies of functions like that is not a big issue. Functions in javascript are all about well, functionality! Closures are an awesome concept, once you grasp and master them you see it's well worth it, and the performance hit really isn't that meaningful. JS is getting faster every day, don't worry about these sort of performance issues.

If you think it's complicated, the following is also very legitimate. A common contract with other developers simply says "If my variable starts with _ don't touch it, we are both consenting adults". This would look something like:

function getPerson(name){     var greeter = {         greet : function() {             return "Hello" +greeter._name;         }     };     greeter._name = name;     return greeter; } 

Or in classical style

function Person(name){     this._name = name;     this.greet = function(){        return "Hello "+this._name;     } } 

Or if you'd like to cache the function on the prototype instead of instantiate copies:

function Person(name){     this._name = name; } Person.prototype.greet =  function(){        return "Hello "+this._name; } 

So, to sum it up:

  • You can use classical inheritance patterns, they are useful for sharing types of data

  • You should also use prototypical inheritance, it is just as potent, and much more in cases you want to share functionality.

  • TheifMaster pretty much nailed it. Having privates private is really not a big deal as one might think in JavaScript, as long as your code defines a clear interface this should not be problematic at all. We're all concenting adults here :)

*The clever reader might think: Huh? Weren't you tricking me there with the history rule? I mean, property access isn't encapsulated.

I say no, I was not. Even if you don't explicitly encapsulate the fields as private, you can simply define your contract in a way that does not access them. Often like TheifMaster suggested with _. Also, I think the history rule is not that big of a deal in a lot of such scenarios as long as we're not changing the way property access treats properties of the parent object. Again, it's up to us.

like image 84
Benjamin Gruenbaum Avatar answered Sep 30 '22 06:09

Benjamin Gruenbaum


I don't want to be discouraging since you seem to be a fairly new member of StackOverflow, however I'm going to have to be a little in your face and say that it's a really bad idea to try to implement classical inheritance in JavaScript.

Note: When I say that it's a bad idea to implement classical inheritance in JavaScript I mean that trying to simulate actual classes, interfaces, access modifiers, etc. in JavaScript is a bad idea. Nevertheless, classical inheritance as a design pattern in JavaScript is useful as it's just syntactic sugar for prototypal inheritance (e.g. maximally minimal classes). I use this design pattern in my code all the time (a la augment).

JavaScript is a prototypal object-oriented programming language. Not a classical object-oriented programming language. Sure, you can implement classical inheritance on top of JavaScript but before you do keep the following things in mind:

  1. You're going against the spirit of the language, which means that you'll be faced with problems. Lots of problems - performance, readability, maintainability, etc.
  2. You don't need classes. Thomas, I know that you truly believe that you need classes but trust me on this. You don't.

For your sake I'll provide two answers to this question. The first one will show you how to do classical inheritance in JavaScript. The second one (which I recommend) will teach you to embrace prototypal inheritance.

Classical Inheritance in JavaScript

Most programmers start with trying to implement classical inheritance in JavaScript. Even JavaScript Gurus like Douglas Crockford tried to implement classical inheritance in JavaScript. I too tried to implement classical inheritance in JavaScript.

First I created a library called Clockwork and then augment. However I wouldn't recommend you to use either of these libraries because they go against the spirit of JavaScript. The truth is that I was still an amateur JavaScript programmer when I wrote these classical inheritance libraries.

The only reason I mention this is because everyone is an amateur at some point of time, and although I would prefer that you didn't use classical inheritance patterns in JavaScript, I can't expect you to understand why prototypal inheritance matters just yet.

You can't learn how to cycle without falling down a few times. I believe you're still in the learning phase with respect to prototypal inheritance. Your need for classical inheritance is like the training wheels on cycles.

Nevertheless, training wheels are important. If you want there are some classical inheritance libraries out there which should make you more comfortable writing code in JavaScript. One such library is jTypes. Just remember to take off the training wheels when you are confident of your skills as a JavaScript programmer.

Note: Personally I don't like jTypes one bit.

Prototypal Inheritance in JavaScript

I'm writing this section as a milestone for you so that you can come back later and know what to do next when you are ready to learn about true prototypal inheritance.

First of all the following line is wrong:

The javascript prototype-based object-oriented programming style is interesting, but there are a lot of situations where you need the ability to create objects from a class.

This is wrong because:

  1. You will never need to create objects from a class in JavaScript.
  2. There is no way to create a class in JavaScript.

Yes it's possible to simulate classical inheritance in JavaScript. However you're still inheriting properties from objects and not classes. For example, ECMAScript Harmony classes are just syntactic sugar for the classical pattern of prototypal inheritance.

In the same context the example you gave is also wrong:

For instance in a vector drawing application, the workspace will usually be empty at the beginning of the drawing : I cannot create a new "line" from an existing one. More generally, every situation where objects are being dynamically created require the use of classes.

Yes you can create a new line from an existing one even though the workspace is empty in the beginning. What you need to understand is that the line is not actually drawn though.

var line = {     x1: 0,     y1: 0,     x2: 0,     y2: 0,     draw: function () {         // drawing logic     },     create: function (x1, y1, x2, y2) {         var line = Object.create(this);         line.x1 = x1;         line.y1 = y1;         line.x2 = x2;         line.y2 = y2;         return line;     } }; 

Now you can draw your the above line by simply calling line.draw or else you could create a new line from it:

var line2 = line.create(0, 0, 0, 100); var line3 = line.create(0, 100, 100, 100); var line4 = line.create(100, 100, 100, 0); var line5 = line.create(100, 0, 0, 0);  line2.draw(); line3.draw(); line4.draw(); line5.draw(); 

The lines line2, line3, line4 and line5 form a 100x100 square when drawn.

Conclusion

So you see you really don't need classes in JavaScript. Objects are enough. Encapsulation can be easily achieved using functions.

That being said you can't have public functions of each instance access the private state of the object without each instance having its own set of public functions.

This is not a problem however because:

  1. You don't really need private state. You may think that you do, but you really don't.
  2. If you really want to make a variable private then as ThiefMaster mentioned just prefix the variable name with an underscore and tell your users not to mess with it.
like image 42
Aadit M Shah Avatar answered Sep 30 '22 05:09

Aadit M Shah