4.6 KiB
Class patterns
There's a special syntax construct and a keyword class
in JavaScript. But before turning to it, we should consider that the term "class" comes the theory of OOP. And it actually has a broader meaning.
In JavaScript there are several well-known programming patterns to make classes even without using the class
construct. And here we'll talk about them first.
The class
construct will come naturally in the next chapter.
When talking about classes, it's important to start from the roots, to evade any ambiguity. So here's the definition of the term.
[cut]
In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).
Functional class pattern
The constructor function below can be considered a class according to the definition:
function User(name) {
this.sayHi = function() {
alert(name;
};
}
let user = new User("John");
user.sayHi(); // John
It follows all parts of the definition:
- It is a program-code-template for creating objects (callable with
new
). - It provides initial values for state (
name
from parameters). - It provides methods (
sayHi
).
This is called functional class pattern. It is rarely used, because prototypes are generally better.
Here's the same class rewritten using prototypes:
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
alert(this.name);
};
let user = new User("John");
user.sayHi(); // John
Now the method sayHi
is shared between all users through prototype. That's more memory-efficient as putting a copy of it into every object like the functional pattern does. Prototype-based classes are also more convenient for inheritance. As we've seen, that's what the language itself uses, and we'll be using them further on.
Internal properties and methods
In the functional class pattern, variables and functions inside User
, that are not assigned to this
, are visible from inside, but not accessible by the outer code.
Here's a bigger example:
function User(name, birthday) {
function calcAge() {
new Date().getFullYear() - birthday.getFullYear();
}
this.sayHi = function() {
alert(name + ', age:' + calcAge());
};
}
let user = new User("John", new Date(2000,0,1));
user.sayHi(); // John
Variables name
, birthday
and the function calcAge()
are internal, private to the object. They are only visible from inside of it. The external code that creates the user
only can see a public method sayHi
.
In short, functional classes provide a shared outer lexical environment for private variables and methods.
Prototype-bases classes do not have it. As we can see, methods are created outside of the constructor, in the prototype. And per-object data like name
is stored in object properties. So, technically they are all available for external code.
But there is a widely known agreement that internal properties are prepended with an underscore "_"
.
Like this:
function User(name, birthday) {
*!*
this._name = name;
this._birthday = birthday;
*/!*
}
*!*
User.prototype._calcAge = function() {
*/!*
return new Date().getFullYear() - this._birthday.getFullYear();
};
User.prototype.sayHi = function() {
alert(this._name + ', age:' + this._calcAge());
};
let user = new User("John", new Date(2000,0,1));
user.sayHi(); // John
Technically, that changes nothing. But most developers recognize the meaning of "_"
and try not to touch prefixed properties and methods in external code.
Prototype-based classes
Prototype-based classes are structured like this:
The code example:
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
alert(this.name + ' eats.');
};
function Rabbit(name) {
this.name = name;
}
*!*
// inherit methods
Object.setPrototypeOf(Rabbit.prototype, Animal.prototype); // (*)
*/!*
Rabbit.prototype.jump = function() {
alert(this.name + ' jumps!');
};
let rabbit = new Rabbit("White Rabbit")
rabbit.eat();
rabbit.jump();
Here the line (*)
sets up the prototype chain. So that rabbit
first searches methods in Rabbit.prototype
, then Animal.prototype
. And then Object.prototype
, because Animal.prototype
is a regular plain object, so it inherits from it, that's not painted for brevity.
The structure of exactly that code piece is:
Todo
call parent method (overrides)