📜 ⬆️ ⬇️

JavaScript pattern pseudo-class (pseudo-classical)

In a pattern, an object is created in the constructor, and its methods are declared in the prototype.

This pattern is used in frameworks such as the Google Closure Library. Native JavaScript objects also use this pattern.

Pseudo-class announcement


Termite Pseudo-class is chosen because there are no such classes in JavaScript, as in other languages ​​like C, Java, PHP, etc., but this pattern is close to the definition of a class.
')
A pseudo-class consists of a constructor function and methods.

For example, the Animal pseudo-class consists of one sit method 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 

  1. When new Animal(name) is called, the object gets the link __proto__ to Animal.prototype (see the right part of the schema).
  2. The animal.sit method changes animal.canWalk in the instance, so our animal can no longer walk, while others can.

image
Pseudo-class scheme:

  1. Methods and properties by default are defined in the prototype.
  2. Methods in prototype use this , which points to the current object, since the value of this depends on the context of the call. Therefore, in animal.sit() this refers to the animal .

Inheritance


Let's create a new class that will inherit from Animal , for example 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 can see, the rabbit has the same structure as Animal - the method is defined in the prototype.

To inherit from Animal , you need to do so Rabbit.prototype.__proto__ == Animal.prototype . This is a natural requirement, since if the method is not found in Rabbit.prototype , we will look for it in the methods of the parent Animal.prototype .

So, for example:

image

To implement this, we must first create an empty Rabbit.prototype object inherited from Animal.prototype and then add methods.

 function Rabbit(name) { this.name = name } Rabbit.prototype = inherit(Animal.prototype) Rabbit.prototype.jump = function() { ... } 

Where inherit creates an empty object with the specified __proto__ :

 function inherit(proto) { function F() {} F.prototype = proto return new F } 

Here’s what happened at the end:

 // 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! 

Do not use new Animal for inheritance.


A well-known, but not correct way to inherit, is when instead of Rabbit.prototype = inherit(Animal.prototype) people do the following:

 // inherit from Animal Rabbit.prototype = new Animal() 

As a result, we get a new Animal in the prototype. Inheritance works, since new Animal naturally inherits Animal.prototype .

... But who said that new Animal() can be called without a name ? A constructor can strictly require arguments and die without them.

In fact, the problem is more conceptual than this. We do not want to create Animal . We just want to inherit from it.

This is why Rabbit.prototype = inherit(Animal.prototype) is preferred. Neat inheritance without side effects.

Calling the superclass constructor


The superclass constructor is now automatically called. We can call it with handles using Animal.apply() for the current object:

 function Rabbit(name) { Animal.apply(this, arguments) } 

This code executes the Animal constructor in the context of the current object, and it sets the name instance.

Redefinition of methods (polymorphism)


To override the parent method, replace it in the child prototype:

 Rabbit.prototype.sit = function() { alert(this.name + ' sits in a rabbity way.') } 

When calling rabbit.sit() sit is searched in the rabbit -> Rabbit.prototype -> Animal.prototype and finds it in Rabbit.prototype before reaching Animal.prototype .

Of course, we can redefine it differently - directly in the object:

 rabbit.sit = function() { alert('A special sit of this very rabbit ' + this.name) } 

Calling the parent method after the override

After overriding the method, we may still need to call the parent method. This is possible if we directly address the prototype of the parent.

 Rabbit.prototype.sit = function() { alert('calling superclass sit:') Animal.prototype.sit.apply(this, arguments) } 

All parent methods are called using apply/call where the current object is passed as this . A simple call to Animal.prototype.sit() will use Animal.prototype like this .

Sugar: removing direct reference to parent

In the previous example, we called the parent class directly. As a constructor: Animal.apply... , or method: Animal.prototype.sit.apply...

In fact, we should not do this. During refractoring, the name can be changed or an intermediate class in the hierarchy can be added.

Typically, programming languages ​​allow you to call parent methods using special keywords, such as parent.method() or super() .

But there is no such thing in JavaScript, but we can model it.

The following function extends inheritance and also sets the parent and constructor without a direct link to it:

 function extend(Child, Parent) { Child.prototype = inherit(Parent.prototype) Child.prototype.constructor = Child Child.parent = Parent.prototype } 

This is how you can use:

 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 a result, we can rename Animal or create an intermediate class GrassEatingAnimal and the changes will affect only Animal and extend(...) .

Private and protected methods (encapsulation)


Protected (protected) methods and properties are supported with a naming convention. Methods that begin with an underscore '_' should not be called from outside (in fact they can).

image

Private methods are not supported.

Static (static) methods and properties


Static methods and properties are defined in the constructor:

 function Animal() { Animal.count++ } Animal.count = 0 new Animal() new Animal() alert(Animal.count) // 2 

Total


Here is our super 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 } 

Using:

 // --------- 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() 

In our framework, you can add a bit of sugar, for example, a function that copies properties from one object to another:

 mixin(Animal.prototype, { run: ..., toString: ...}) 

But in fact, you don’t really need to use this OOP pattern. Only two functions will cope with this.

Source: https://habr.com/ru/post/210596/


All Articles