Considering the fact that almost everything in JavaScript is an object, object oriented JavaScript code is very different from other object-capable languages. The JS object system is more of a prototype-based object system instead.
Coming from a C++ background, I was aware of the Object Oriented programming paradigm and had a very rigid idea of how Objects and Classes should work. Exposure to other languages like Java only seemed to further establish this idea. While these languages have their own semantics on how objects and classes work; Javascript, for the new user, is quite a revelation.
First off the bat, JavaScript objects are very different in the way they are created. There is no requirement for a class. Object instances can be created using the new operator:
let Reptile = new Object() {
// ...
}
or with a function constructor
function Reptile() {
// ...
}
Second, JavaScript objects are very flexible. While classic object oriented languages allow only property modification or property slots, JavaScript allows objects to modify their properties and methods; i.e. JavaScript objects have both property and method slots.
My first thought at the discovery was “yeah freedom!” but this came with a cost - a need to understand the prototype property in JavaScript. The knowledge of the prototype is essential to a developer who wishes to implement any semblance of an object oriented system in JavaScript.
All JavaScript objects are created from the Object
constructor:
var Reptile = function(name, canItSwim) {
this.name = name;
this.canItSwim = canItSwim;
}
And the prototype
allows us to add new methods to objects constructors, this means that the following method now exists in all instances of Reptile
.
Reptile.prototype.doesItDrown = function() {
if (this.canItSwim) {
console.log(`${this.name} can swim`);
} else {
console.log(`${this.name} has drowned`);
}
};
Object instances of Reptile
can be now created:
// for this example consider alligators can swim and crocs cannot
let alligator = new Reptile("alligator", true);
alligator.doesItDrown(); // alligator can swim
let croc = new Reptile("croc", false);
croc.doesItDrown(); // croc has drowned
The prototype
of the Reptile
object is now the basis for inheritance, the doesItDrown
method is accessible to both alligator
and croc
because the prototype
of Reptile
has this method. The prototype
property is shared amongst all its instances and is accessible via the __proto__
property of a particular instance.
Now, because of the existence of method slots and a common prototype
instance property being shared across all instances, some very neat tricks are possible which are very weird to C++ folks:
croc.__proto__.doesItDrown = function() {
console.log(`the croc never drowns`);
};
croc.doesItDrown(); // the croc never drowns
alligator.doesItDrown(); // the croc never drowns
Change one instance’s prototype
property or method, all instances of the object are affected. This means we could be deleting stuff as well. A croc tired of drowning could potentially do this:
delete croc.__proto__.doesItDrown
alligator.doesItDrown();
//TypeError: alligator.doesItDrown
// is not a function
Now no one gets to swim.
This is just a silly example to show how fundamental the prototype
is to the Object system in JavaScript and how it can be quite jarring to people from other object oriented languages.
With the ES6 syntax, JavaScript has been provided the feature to create classes.
However, the concept of true classes does not exist in JavaScript but it is emulated through prototype
and the class syntax is just syntactic sugar around it. Therefore, understanding this behavior is important to realize the convenience and limitations of ES6 classes.
With the new class
syntax, Reptile
would be defined as:
class Reptile {
constructor (name, canItSwim) {
this.name = name;
this.canItSwim = canItSwim;
}
doesItDrown () {
if(this.canItSwim)
console.log(`${this.name} can swim`);
else
console.log(`${this.name} has drowned`);
}
}
let alligator = new Reptile("alligator", true);
alligator.doesItDrown(); //alligator can swim
This does not mean it brings nothing new to the offer for prototype
users, some pitfalls can be avoided by using ES6 classes, like making the new
keyword mandatory for creating instances.
let croc = Reptile("croc", false);
//TypeError: Class constructor Reptile cannot be invoked without 'new'
This is actually a good thing, since it prevents accessing the wrong context when using the object properties and methods, which is usually the global scope or the window object.
Though JavaScript right now does certainly lack features like truly private members. It has made creating objects via class syntax, instead of prototypes closely resemble classes from other OO languages like C++/Java.
PS. There has been a proposal to TC39 for creating truly private members in JavaScript classes, you can follow it here and contribute your opinion. If it were to be included in the next revision, then we’d have something like:
class Foo {
#a; #b; // # indicates private members here
#sum = function() { return #a + #b; };
}
// personally this format reminds me of $variable in PHP.
// I'm not sure if that's a good thing
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!