- Pseudo-class declaration
- Inheritance
- Calling superclass constructor
- Overriding a method (polymorphism)
- Private/protected methods (encapsulation)
- Static methods and properties
- Summary
In pseudo-classical pattern, the object is created by a constructor function and it’s methods are put into the prototype.
Pseudo-classical pattern is used is frameworks, for example in Google Closure Library. Native JavaScript objects also follow this pattern.
Pseudo-class declaration
The term ”pseudo-class” is chosen, because there are actually no classes in JavaScript, like those in C, Java, PHP etc. But the pattern is somewhat close to them.
The article assumes you are familiar with how the prototypal inheritance works.
That is described in the article Prototypal inheritance .
A pseudo-class consists of the constructor function and methods.
For example, here’s the Animal
pseudo-class with single method sit
and two properties.
function Animal(name) { this.name = name } Animal.prototype = { canWalk: true, sit: function() { this.canWalk = false alert(this.name + ' sits down.') } } var animal = new Animal('Pet') // (1) alert(animal.canWalk) // true animal.sit() // (2) alert(animal.canWalk) // false
- When
new Animal(name)
is called, the new object recieves__proto__
reference toAnimal.prototype
, see that on the left part of the picture. - Method
animal.sit
changesanimal.canWalk
in the instance, so now this animal object can’t walk. But other animals still can.
The scheme for a pseudo-class:
- Methods and default properties are in prototype.
- Methods in
prototype
usethis
, which is the current object because the value ofthis
only depend on the calling context, soanimal.sit()
would setthis
toanimal
.
There are dangers in the scheme. See the task below.
You are a team lead on a hamster farm. A fellow programmer got a task to create Hamster constructor and prototype.
Hamsters should have a food
storage and the found
method which adds to it.
He brings you the solution (below). The code looks fine, but when you create two hamsters, then feed one of them - somehow, both hamsters become full.
What’s up? How to fix it?
function Hamster() { } Hamster.prototype = { food: [], found: function(something) { this.food.push(something) } } // Create two speedy and lazy hamsters, then feed the first one speedy = new Hamster() lazy = new Hamster() speedy.found("apple") speedy.found("orange") alert(speedy.food.length) // 2 alert(lazy.food.length) // 2 (!??)
Let’s get into details what happens in speedy.found("apple")
:
- The interpreter searches
found
inspeedy
. Butspeedy
is an empty object, so it fails. - The interpreter goes to
speedy.__proto__ (==Hamster.prototype)
and luckily getsfound
and runs it. - At the pre-execution stage,
this
is set tospeedy
object, because of dot-syntax:speedy.found
. this.food
is not found inspeedy
, but is found inspeedy.__proto__
.- The “apple” is appended to
speedy.__proto__.food
.
Hamsters share the same belly! Or, in terms of JavaScript, the food
is modified in __proto__
, which is shared between all hamster objects.
Note that if there were a simple assignment in found()
, like this.food = something
, then step 4-5 would not lookup food
anywhere, but assign something
to this.food
directly.
Fixing the issue
To fix it, we need to ensure that every hamster has it’s own belly. This can be done by assigning it in the constructor:
function Hamster() { this.food = [] } Hamster.prototype = { found: function(something) { this.food.push(something) } } speedy = new Hamster() lazy = new Hamster() speedy.found("apple") speedy.found("orange") alert(speedy.food.length) // 2 alert(lazy.food.length) // 0(!)
Inheritance
Let’s create a new class and inherit it from Animal
.
Here you are.. A Rabbit!
function Rabbit(name) { this.name = name } Rabbit.prototype.jump = function() { this.canWalk = true alert(this.name + ' jumps!') } var rabbit = new Rabbit('John')
As you see, the same structure as Animal
. Methods in prototype.
To inherit from Animal
, we need Rabbit.prototype.__proto__ == Animal.prototype
. This is a very natural requirement, because if a method is not find in Rabbit.prototype
, it should be searched in the parental method store, which is Animal.prototype
.
That’s how it should look like:
To implement the chain, we need to create initial Rabbit.prototype
as an empty object inheriting from Animal.prototype
and then add methods.
function Rabbit(name) { this.name = name } *!* Rabbit.prototype = inherit(Animal.prototype) */!* Rabbit.prototype.jump = function() { ... }
In the code above, inherit
is a function which creates an empty object with given __proto__
.
function inherit(proto) { function F() {} F.prototype = proto return new F }
See Prototypal inheritance for details.
And finally, the full code of two objects:
// Animal function Animal(name) { this.name = name } // Animal methods Animal.prototype = { canWalk: true, sit: function() { this.canWalk = false alert(this.name + ' sits down.') } } // Rabbit function Rabbit(name) { this.name = name } // inherit Rabbit.prototype = inherit(Animal.prototype) // Rabbit methods Rabbit.prototype.jump = function() { this.canWalk = true alert(this.name + ' jumps!') } // Usage var rabbit = new Rabbit('Sniffer') rabbit.sit() // Sniffer sits. rabbit.jump() // Sniffer jumps!
There is a well-known, but wrong way of inhereting, when instead of Rabbit.prototype = inherit(Animal.prototype)
people use:
// inherit from Animal Rabbit.prototype = new Animal()
As a result, we get a new Animal
object in prototype
. Inheritance works here, because new Animal
naturally inherits Animal.prototype
.
… But who said that new Animal()
can be called like without the name
? The constructor may strictly require arguments and die without them.
Actually, the problem is more conceptual than that. We don’t want to create an Animal
. We just want to inherit from it.
That’s why Rabbit.prototype = inherit(Animal.prototype)
is preferred. The neat inheritance without side-effects.
Calling superclass constructor
The “superclass” constructor is not called automatically. We can call it manually by applying the Animal
function to current object:
function Rabbit(name) { Animal.apply(this, arguments) }
That executes Animal
constructor in context of the current object, so it sets the name
in the instance.
Overriding a method (polymorphism)
To override a parent method, replace it in the prototype of the child:
Rabbit.prototype.sit = function() { alert(this.name + ' sits in a rabbity way.') }
A call to rabbit.sit()
searches sit
on the chain rabbit -> Rabbit.prototype -> Animal.prototype
and finds it in Rabbit.prototype
without ascending to Animal.prototype
.
Of course, we can even more specific than that. A method can be overridden directly in the object:
rabbit.sit = function() { alert('A special sit of this very rabbit ' + this.name) }
Calling a parent method after overriding
When a method is overwritten, we may still want to call the old one. It is possible if we directly ask parent prototype for it.
Rabbit.prototype.sit = function() { alert('calling superclass sit:') *!* Animal.prototype.sit.apply(this, arguments) */!* }
All parent methods are called with apply/call
to pass current object as this
. A simple call Animal.prototype.sit()
would use Animal.prototype
as this
.
Sugar: removing direct reference to parent
In the examples above, we call parent class directly. Either it’s constructor: Animal.apply...
, or methods: Animal.prototype.sit.apply...
.
Normally, we shouldn’t do that. Refactoring may change parent name or introduce intermediate class in the hierarchy.
Usually programming languages allow to call parent methods using a special key
word, like parent.method()
or super()
.
JavaScript doesn’t have such feature, but we could emulate it.
The following function extend
forms inheritance and also assigns parent
and constructor
to call parent without a direct reference:
function extend(Child, Parent) { Child.prototype = inherit(Parent.prototype) Child.prototype.constructor = Child Child.parent = Parent.prototype }
Usage:
function Rabbit(name) { *!*Rabbit.parent.constructor*/!*.apply(this, arguments) // super constructor } extend(Rabbit, Animal) Rabbit.prototype.run = function() { *!*Rabbit.parent.run*/!*.apply(this, arguments) // parent method alert("fast") }
As the result, we can now rename Animal
, or create an intermediate class GrassEatingAnimal
and the changes will only touch Animal
and extend(...)
.
Private/protected methods (encapsulation)
Protected methods and properties are supported by naming convention. So, that a method, starting with underscore '_'
should not be called from outside (technically it is callable).
Private methods are usually not supported.
Static methods and properties
A static property/method are assigned directly to constructor:
function Animal() { Animal.count++ } Animal.count = 0 new Animal() new Animal() alert(Animal.count) // 2
Summary
And finally, the whole suppa-mega-oop framework.
function extend(Child, Parent) { Child.prototype = inherit(Parent.prototype) Child.prototype.constructor = Child Child.parent = Parent.prototype } function inherit(proto) { function F() {} F.prototype = proto return new F }
Usage:
// --------- the base object ------------ function Animal(name) { this.name = name } // methods Animal.prototype.run = function() { alert(this + " is running!") } Animal.prototype.toString = function() { return this.name } // --------- the child object ----------- function Rabbit(name) { Rabbit.parent.constructor.apply(this, arguments) } // inherit extend(Rabbit, Animal) // override Rabbit.prototype.run = function() { Rabbit.parent.run.apply(this) alert(this + " bounces high into the sky!") } var rabbit = new Rabbit('Jumper') rabbit.run()
Frameworks may add a bit more sugar, like function mixin
which copies many properties from one object to another:
mixin(Animal.prototype, { run: ..., toString: ...})
But in fact you don’t need much to use this OOP pattern. Just two tiny functions will do.