When working with inheritance, sometimes there is a desire to have a function for accessing the ancestor method (the parent class method) - in the constructor (similar to the class for Javascript) or in the descendant method, because it happens that the new class overrides it. Not just any function (method), but with a completely clear record, so that the name speaks for itself, and having access to the specified generation of ancestors (not “great-great-great”, but “great 3 times”).
Let us take as a basis the method of prototypal inheritance, which is maximally effective in that it produces a minimum of actions when describing chains of inherited classes and at the same time supports the basic operations and properties of inheritance:
instanceof, constructor . To access the ancestor, it creates the
.superclass property.
(It is argued that this underlying approach to classes was
popularized by Crockford , no links could be found, but it is important that the method is thoughtful and economical. The author is unknown.)
First we give the code of this method with our brief comments.
function inherit2(Inherited, Parent){
(Those who were previously interested in the topic of inheritance in Javascript, of course, recognized it.) It was discussed in great detail in
javascript.ru/tutorial/object/inheritance , and the issue of access to ancestral methods was also highlighted: we "played around" with "
.constructor ", closing the link to itself so that future instances of the class have the correct property
.constructor . But, of course, they lost the correctness of the connections between the inherited classes (yes, it did not exist, it was not laid out in the language) and were forced to create a “bonus” property
.superclass to be able to access the parent class.
')
Why do we need ancestral methods?
Inheritance implies “mashing” (shielding) a property if its name in the descendant matches the name in its ancestors. If you describe a function, and then, for example, you extend the functionality of a method in a class that inherits, then you have to rewrite some of the simplified (or just the previous, or general) functionality — repeat it if you need it. This is already a mess. Well, if you had to completely change the method, but sometimes you need to repeat the old method, and then add it a little. (Therefore, access to ancestors is more important for methods than for properties - properties are always completely rewritten.) This is where the "bonus" property "
.superclass "
comes in, indicating the prototype of the parent.
For inherited classes according to the scheme A => B => C => c01.MethodX
function A(){this.xa =1;} function B(){this.xb =1;} function C(){this.xc =1;} A.prototype.MethodX ='protoA'; inherit2(B, A); B.prototype.MethodX ='protoB'; B.prototype.xx ='protoX'; inherit2(C, B); C.prototype.MethodX = 'protoC'; var c01 = new C();
we obtain such methods of access to the ancestral method:
Alert = console.log; Alert(c01.MethodX);
(To see how this works,
we look at the executable example for inherit2 () (it won't hurt to look at Firebug).)
It is inconvenient to use such calls, but it is convenient to confuse colleagues and followers: first we
contact through
.constructor.prototype , and then through
.superclass , instead of saying what we want; we can not specify the number of the depth of treatment to the ancestor. All in all, a small function of the form is required:
object.ancestor(name, level) -
which would be put in the deepest class and could extend the functionality of new methods by “overwritten” methods (and no one bothers to turn to neighboring methods).
A minor problem will remain that the context of the method (
this ) will be “enriched”, belonging to the very last class to which the base classes (A and B) still do not need to know, but this is a property of reality and then - the author’s desire or unwillingness to completely rewrite ancestor method. We can rewrite in the old-fashioned way, but we can take care in advance of the more complex, unknown to them environment understanding by primitive methods.
We also replace the concept of “superclass” (it is clear where it comes from, from Java, but this is confusing, because usually constructors are called classes, not their prototypes) with ancestor class
_anc (ancestor). This is exactly what the constructor should be relative to the successor, so the logic of the final function will be simpler.
What I would like to get
c01 ['MethodX'] | c01.ancestor ('MethodX', 0) |
c01.constructor.prototype ['MethodX'] | c01.ancestor ('MethodX') |
c01.constructor.prototype._anc.prototype ['MethodX'] | c01.ancestor ('MethodX', 2) |
c01.constructor.prototype._anc.prototype._anc.prototype ['MethodX'] | c01.ancestor ('MethodX', 3) |
... |
At the same time,
c01.constructor could be
c01._anc , if with each
new we would write this, i.e. would create a special function instead of
new (there is a tendency to call such a function
create () ), but we will not do so (for now?) to save
new as an indicator of the creation of an object.
The result is achieved by such a function:
function ancestor(name, level, constr){ level = level || (level ==0 ? 0 : 1); var t =this; return level <= 1 ? (level ? constr && constr.prototype[name] || t.constructor.prototype[name] : t[name]) : arguments.callee.call(this , name , level -1 , constr && (constr.prototype._anc || constr.prototype.constructor) || t._anc
The
inherit3 () source code
has also changed a bit to use the
._anc () function.
function inherit3(Inherited, Parent){ var F = function(){}; F.prototype = Parent.prototype; (Inherited.prototype = new F())._anc = Parent; Inherited.prototype.constructor = Inherited; }
The logic of the access to the ancestors.
We refer not to the current method definition, but to the one that was at the
level of inheritance levels before.
If we do not fall into the exact definition of a method, the newest of the parent methods taken at the
level of steps before the last generation is retrieved along the chain of prototypes.
If there are no prototypes, we fall into the “bomb of kindness” (disservice) of the function when there should be no error of the missing property, but we always have the first method defined in the chain. If there is no property at all, we get
undefined at any level.
If you do not want to have a bomb of kindness, we write a couple of fake properties when a property is not found in place of
constr.prototype._anc - for example, (
constr.prototype._anc ||
constr.fail.fail ) or simply (
constr.prototype._anc || fail ) .
function A(){this.prop =1;} function B(){this.prop =1;} function C(){this.prop =1;} A.prototype.protoProp ='protoA'; inherit3(B, A); B.prototype.protoProp ='protoB'; inherit3(C, B); C.prototype.protoProp ='protoC'; var c01 = new C(); A.prototype.ancestor = ancestor; Alert(c01['protoProp'], c01.ancestor('protoProp', 0))
Look at the work of the script, twist in Firebug -
on a demo (he - with a bomb of kindness).
The prototype of the descendant is required to write after inheritance, and not anywhere - a consequence of working with prototypes in
inherit2 () . The prototype of a common ancestor can be changed anywhere, as shown by assigning an
ancestor () method after all inheritances. In the examples, properties are used everywhere, not methods - for short, but there are no problems with specifying or executing methods in formats
c01.ancestor('protoMethod', 3)
What needs to be improved?
Everything, the task of the article is completed, can I go to sleep? No, development never stops, and the demanding reader did not receive moral satisfaction after receiving 2 separate functions and a number of conditions of use. In this method, the displacement of the prototype from ancestor to descendant is confusing. After all, often objects already have prototypes, they need not to be rewritten, but to extend (extend), otherwise on the 3rd floor of inheritance we get problems and the need to rework the method.
Why we didn’t immediately prepare the finished code as food for our readers, but force them to walk on semi-finished products? Because this code is needed for thinking developers, they need to know how the access method works. In the first part a skeleton is built, without any beautiful and other meat. The skeleton structure is best seen when there is no meat on it. Continue on the desired improvements.
In the examples, we see that we do not rewrite the prototype (it would be wrong), but add new properties to it. This suggests the use of the
extend () function to
extend the prototype after inheritance. So, it would be nice to have the extension function "at hand." It is in jQuery and in many other libraries, or for the sake of speed, we can define our function in order to include it in our inheritance method. For a basis, you can take this format,
found on the Internet :
extend = function(obj, extObj){
It is good because “in between” allows you to use several arguments to extend the first one. In the meantime, even this function will allow us to write not "
C.prototype.protoProp = 'protoC'; ", but
extend(C.prototype, {protoProp: 'protoC'});
Note that we no longer need the
.superclass ; it is thrown out of the code.
In addition, this method of inheritance is so
harshly economical that it
does not execute constructors , so we can see the properties of the constructor only where the
new is executed — in the
c01 object. There is no constructor execution between classes. Such severity is not always necessary (oh, not always). Properties of constructors that are generated by
this.property could be present and also participate in prototype inheritance (be a property of the prototype heir).
About the method of remaking this approach more convenient - in the next series. Although this one has every reason to be used - it all depends on the number of requirements for the inheritance mechanism
and the desire to confuse other developers with their code, for this the first option is preferable .
What similar methods for classes and collections (hashes) are developers prefer?
A similar task, of course, was encountered among class and inheritance implementations, which can be found by saying “calling methods of the parent class”.
habrahabr.ru/blogs/javascript/40909It has a basic
__parent method
created that performs the same tasks as the
superclass method. In it, too, it is necessary to use a multiple call, if you turn to ancestors.
The library provides several more methods:
Mixin (A, B) - adding from prototype B to prototype A,
Clone () - object cloning,
Extend () - expansion of hashes; only for a pair of arguments.
superclass does not connect to such methods: [“hasOwnProperty”, “isPrototypeOf”, “propertyIsEnumerable”, “toLocaleString”, “toString”, “valueOf”] is a good idea.
Another example of access to ancestors (from Rezig)
In
ejohn.org/blog/simple-javascript-inheritance (Simple JavaScript Inheritance By John Resig) we see the presence of the
_super and
extend method together with the special constructor
Class , which emphasizes its class-like nature. Here,
extend is not an extension of the hash, but a special procedure for scrolling through the list of properties, in order to “wipe out” put the
_super property — a method or property that is created if the parent has a rewritable method. Obviously, this is a more costly mechanism. Instead of a one-time access to the ancestral methods and accessing it only when we call it, we create a “workaround” method every time we construct a heir, running through the whole list of ancestor methods. It can be justified if there are a lot of resources, we turn to ancestors more often than we create new classes. In general, reference to ancestors is assumed in the description of functions — a quite time-consuming method that should be effective, so the approach of writing direct links is sometimes justified. As an alternative, we also have an appeal through links (object.) Without any costs for running through the list of methods with
.extend .
The hero, who decided alone to fill up the four-headed hydra of the reaction, is almost Hercules, but we know that the frames are drawn by a snag-programmer. He successfully examines 4 detected problems (1 - the code is transformed into a method, 2 - an extension of the heir prototype is added, 3 - the independent use of mixed methods is possible, 4 - the format of writing codes for inheritance is improved) and solves them in one piece of code. It turned out that the 5th problem was not solved at the same time, but let's call it non-overloading of the written code - the future short method will be able to extend any other hashes, not just the prototypes of the heirs. The code easily guesses the source one, which we cited in this article, which will help the developers follow the development of ideas and choose their appropriate implementation. The viewer will want to know how it happened - a single-frame review with examples will help him in this. The code will really be more convenient. Follow the adventures of Hercules on the pages of our newspapers.