Psst, over here! Want to learn a secret?
The concept of classical inheritance has stood the test of time as a reliable approach.
Implementing classical inheritance in JavaScript can be quite beneficial. Classes offer a great way to model objects and templates for structuring our world.
Classical inheritance is essentially a pattern. It's perfectly acceptable to use classical inheritance in JavaScript if it suits your specific requirements.
While prototypical inheritance emphasizes sharing functionality, there are instances where sharing a data scheme becomes essential, a challenge that prototypical inheritance fails to address adequately.
Are classes really as bad as people make them out to be?
No, they aren't. What the JavaScript community disapproves of is not the idea of classes per se, but rather sticking solely to classes for code reuse. Just as the language does not impose strong or static typing, it also doesn't enforce particular schemes on object structures.
In reality, underneath the surface, clever implementations of the language can transform ordinary objects into something resembling classical inheritance classes.
How do classes function in JavaScript?
All you really need is a constructor:
function getVehicle(engine){
return { engine : engine };
}
var v = getVehicle("V6");
v.engine;//V6
Voila! We have a vehicle class without explicitly defining a Vehicle class using a special keyword. However, some individuals prefer the more traditional approach. For them, JavaScript provides (arguably) syntactic sugar by doing:
function Vehicle(engine){
this.engine = engine;
}
var v = new Vehicle("V6");
v.engine;//V6
This accomplishes pretty much the same outcome as the previous example.
But what's still missing?
Inheritance and private members.
Did you know that basic subtyping is relatively easy in JavaScript?
JavaScript's interpretation of typing differs from that of other languages. So, what does being a subtype of a certain type entail in JS?
var a = {x:5};
var b = {x:3,y:3};
Is the type of b
a subtype of the type of a
? Let's say it follows the (strong) behavioral subtyping principle (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.
Moreover,
All these rules are at our discretion to uphold. We have the freedom to adhere to them strictly or loosely, but the choice is ours.
Therefore, as long as we follow these guidelines when implementing our inheritance, we are effectively applying strong behavioral subtyping, which is a potent form of subtyping (see note*).
>>>>> End technical part
Additionally, one can observe that structural subtyping remains valid.
How does this translate 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 overly complex, right? And what about private members?
function getVehicle(engine){
var secret = "Hello"
return {
engine : engine,
getSecret : function() {
return secret;
}
};
}
Here, secret
functions as a closure variable, maintaining privacy similar to private variables in languages like Java but impervious to external access.
Can functions have privates too?
Excellent question!
To incorporate a private variable in a function shared on the prototype, one must first grasp how JS closures and functions operate.
Functions in JavaScript are treated as first-class entities, allowing them to be passed around.
function getPerson(name){
var greeting = "Hello " + name;
return {
greet : function() {
return greeting;
}
};
}
var a = getPerson("thomasc");
a.greet(); //Hello thomasc
As demonstrated above, you can pass the function bound to a
to other objects, enabling loose coupling—a fantastic feature.
var b = a.greet;
b(); //Hello thomasc
Curious how b
knows the person's name is "thomasc"? That's the magic of closures. Pretty neat, isn't it?
You might be concerned about performance. Let me reassure you about embracing the optimizing JIT.
In practice, duplicating functions like this isn't a significant concern. JavaScript excels in functionality! Closures present a fascinating concept; once understood and mastered, the benefits outweigh any minor performance impact. Rest assured, JS continues to improve its speed, negating such anxieties.
If this seems intricate, an alternative strategy involves mutual consent among developers regarding private variables—simple agreements dictating, "If my variable begins with _
, please refrain from altering it." This would manifest as:
function getPerson(name){
var greeter = {
greet : function() {
return "Hello" +greeter._name;
}
};
greeter._name = name;
return greeter;
}
Or adopting a classical approach:
function Person(name){
this._name = name;
this.greet = function(){
return "Hello "+this._name;
}
}
Alternatively, to cache the function on the prototype instead of reproducing copies:
function Person(name){
this._name = name;
}
Person.prototype.greet = function(){
return "Hello "+this._name;
}
To summarize:
Classical inheritance patterns can be advantageous for sharing specific data types
Prototypical inheritance is equally powerful, especially for sharing functionality
TheifMaster hit the nail on the head. The notion of keeping privates truly private isn't as crucial in JavaScript as many believe, provided your code establishes a clear interface. As consenting adults, we can navigate this aspect smoothly :)
*One might wonder: Were you attempting to deceive with the history rule? After all, property access lacks encapsulation.
I confirm, no deception intended. Even without explicitly privatizing fields, adhering to a contract that avoids direct access suffices. Often, labeling variables with _
as suggested by TheifMaster establishes clarity. Moreover, I don't regard the history rule as critical in numerous scenarios, presuming property access treatment remains consistent. Ultimately, the power lies in our hands.