Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
79 views
in Technique[技术] by (71.8m points)

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.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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,

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...