📜 ⬆️ ⬇️

Classes, Objects, and JavaScript Inheritance

Recently in the office of Habr, I wanted to read to my colleagues a small report on object orientation and class inheritance in JavaScript.

The fact is that at one time I was delighted, learning how to create my own objects and build inheritance chains, and decided, as they say, to share my findings and observations with others. (=

I confess that in the process of preparing the texts of the seminar I discovered anew for myself again and was surprised again by the possibilities that JavaScript has at its disposal, and which become available to the developer only by scrutinizing and experimenting with the language.
')
Taking advantage of the fact that the seminar was postponed all the time “until next Friday”, I decided to publish the seminar texts online so that my enthusiasm could be useful to someone else.

The whole text is divided into 5 sections:
  1. OOP in Java Script (1/5): Objects
  2. OOP in Java Script (2/5): Classes
  3. OOP in Java Script (3/5): Properties and Methods of a Class
  4. OOP in Java Script (4/5): Class Inheritance
  5. OOP in Java Script (5/5): Useful Links


OOP in Java Script (1/5): Objects


Everything in javascript is actually an object. An array is an object. A function is an object. The object is also an object. So what is an object? An object is a collection of properties. Each property is a name-value pair. The property name is a string, and the property value is a string, a number, a boolean value, or an object (including an array and a function).

When we define a variable, for example:

var s = 'hello world'; alert(typeof s); //  string 

we are, in fact, implicitly setting the property of an object. In this case, this object will be the global window object:

 alert (s == window.s); //  true alert (typeof window); //  object 

Moreover, this window.s property is itself an object, since it has already initially defined its own collection of properties:

 alert(s.length); //  11 (   ) 

For all that, it is, at first glance, an ordinary string literal!

If the property value is a function, we can call this property an object method. To call an object's method, it is enough to add two parentheses () after its name. When an object's method is executed, the this variable inside this function refers to the object itself. Using the this keyword, the object method accesses all other properties and methods of the object.

 var s = 'futurico'; //    s  window (window.s) var f = function(){ //    f  window (window.f) alert(this == window); //  true alert(this.s); //  'futurico' } f(); //   f  window (window.f()) var o = {}; //    o  window (window.o) os = 'karaboz'; //    s  window.o (window.os) of = function(){ //    f  window.o (window.of) alert(this == o); //  true alert(this.s); //  'karaboz' } of(); //   f  window.o (window.of()) 

An object is created using a constructor function that initializes the object and the keyword new . The constructor function provides the same features as a class in other programming languages: namely, it describes the template by which objects (instances) of the class will be created. The basis of such a template is the enumeration of properties and methods that an object created on the basis of this class will possess. For all built-in data types in JavaScript, there are built-in function constructors.

For example, when we declare a string variable:

 var str='karaboz'; 

we implicitly call the built-in constructor function:

 var str = new String('karaboz'); 

and thereby create an object (instance) of class String .

The same statement is true for all other JavaScript data types:

 //  var num = 12345.6789; // var num = new Number(12345.6789); //   var bul = true; // var c = new Boolean(true); //  var fun = function(x){var p = x}; // var fun = new Function('x', 'var p = x'); //  var arr = ['a', 'b', 'c']; // var arr = new Array('a', 'b', 'c'); //  var obj = {}; // var obj = new Object(); 

All of these objects immediately after creation have all the properties and methods described in their constructor functions (classes):

 alert(num.toFixed(1)); //  12345.6 alert(arr.length); //  3 

In fact, the JavaScript interpreter is somewhat trickier than it might seem from the previous example. So, despite the fact that the following code shows the equality of two variables (objects of class String ):

 var str1 = 'karaboz'; var str2 = new String('karaboz'); alert(str1 == str2); //  true 

when trying to define a new user method for str1, we get an error:

 str1.tell = function(){ alert(this); } str1.tell(); //   'str1.tell is not a function' 

At the same time, for str2 everything will work, as we expect:

 str2.tell = function(){ alert(this); } str2.tell(); //  'karaboz' 

This restriction imposed by JavaScript on variables (objects) created through string, numeric, and Boolean literals, however, does not apply to objects created through function, array, or object literals. Those. variables (objects) containing a function, or an array, or an object, you can directly assign custom properties and methods:

 var s = 'futurico'; //    s  window (window.s) var f = function(){ //    f  window (window.f) alert(this == window); //  true alert(this.s); //  'futurico' } f(); //   f  window (window.f()) fs = 'karaboz'; //    s  window.f (window.fs) fm = function(){ //    m  window.f (window.fm) alert(this == f); //  true alert(this.s); //  'karaboz' } fm(); //   m  window.f (window.fm()) 

Here we clearly see that the function f, created as a method of the global window object, is itself an object that can have its own properties and methods!

OOP in Java Script (2/5): Classes


So, a class is a template that describes the properties and methods that any object created on the basis of this class will have. To create our own class in JavaScript, we need to write a constructor function:

 // - -    var Class = function(p){ alert('My name is constructor'); this.p = p; } 

And in order to create an object of this new class, we must call it as a normal function, using the new keyword. In this case, the this keyword within the constructor function will now point to the newly created object:

 var o = new Class('karaboz'); alert(o); //  [Object object] alert(op); //  'karaboz' -     o 

If you try to variable o simply assign a call to the function Class () - without the keyword new, then no object will be created:

 var o = Class('karaboz'); //   window.Class() alert(o); //  undefined,   ,    Class() alert(window.p); //  'karaboz' -      window 

When creating a function, JavaScript automatically creates an empty .prototype property for it. Any properties and methods recorded in the .prototype function will be available as properties and methods of objects created on the basis of this function. This is the basis for the description of the template (class), according to which the objects will be created.

 Class.prototype.method = function(){ alert('my name is .method'); } 

Now we can call this method as the method of the object itself:

 o.method(); // ! 

When calling an object property, it is searched first in the object itself, and if it is not there, then the interpreter looks at the .prototype function that created the object.

So, when creating an object, there already exists a property .constructor , which points to the constructor function that created this object:

 alert(o.constructor == Class); //  true 

Note that we did not define such a property in the object itself. The interpreter, not finding the .constructor property in the object, takes it from the .prototype of the constructor function that created the object. Check:

 alert(Class.prototype.constructor == Class); //  true 

It should be noted that .prototype exists only for the constructor function, but not for the object itself created on its basis:

 alert(o.prototype); //  undefined alert(o.constructor.prototype); //  [Object object] 

Access to the .prototype function exists for all objects, including objects embedded in JavaScript, such as strings, numbers, etc. Moreover, there are already no restrictions in creating own properties and methods (we saw these restrictions when trying to directly assign properties and methods to a string variable - to an object created via a string literal):

 var s = 'karaboz'; s.constructor.prototype.tell = function(){ alert(this); } s.tell(); //     ,   'karaboz' 

You can also set a new property or method for built-in object types directly through the built-in constructor function of these objects:

 String.prototype.tell = function(){ alert(this); } 

By the way, we once again confirmed the statement that everything in JavaScript is an object (=

OOP in Java Script (3/5): Properties and Methods of a Class


Properties and methods of a class (class members) can be public (public), private (private), privileged (privileged) and static (static).

Public (public) members


Open refers to such properties and methods that can be directly read, modified, deleted, or added by any code outside the object itself.
Open properties are set using the .this inside a constructor function:

 var Class = function(p){ this.p = p; } var o = new Class('karaboz'); alert(op); //  'karaboz' op = 'mertas'; alert(op); //  'mertas' 

Public methods are defined using the .prototype function:

 Class.prototype.method = function(){ alert('my name is .method'); } obj.method(); //  'my name is .method' obj.method = function(){ alert('my name is .method, but I am new one!'); } obj.method(); //  'my name is .method, but I am new one!' 

Assigning the .method method to the obj object, we do not change the method of the same name in the .prototype function, but just close it from the interpreter, creating a new property with the same name in our object. Those. all newly created objects will still have the standard method from .prototype .

We can allow the object to see again and use the method from .prototype . To do this, simply delete the .method property of the object itself:

 delete o.method; o.method(); //   'my name is .method' 

Properties and methods specified through the .prototype of the constructor function are not copied to newly created objects. All objects of this class use the reference to the same properties and methods. At the same time, we can define open members at any point of the program, including even after the creation of an object (instance) of a class.

Private Members


Private properties and methods are not directly accessible from outside the object. They are described directly in the class constructor function and are created when the object is initialized. Variables that are passed as parameters to a constructor function, variables declared using the var keyword, and functions declared as local within the constructor function have these properties.

 var Class = function(p){ var secret = p; var count = 3; var counter = function(){ count –; if(count > 0){ return true; } else { return false; } } } 

The secret , count and counter properties are created in the object upon its initialization. They are called closed because they do not have access to both the code from outside the object and the public methods of the object itself. To understand how you can use these private properties, you need to refer to the preferred methods.

Privileged methods


The privileged method has access to private properties and methods, and is also available to both public methods of the object and from outside it. It is possible to delete or rewrite the preferred method, but you cannot change it or force it to reveal the secrets it protects.

The privileged method is defined in the constructor using the this keyword:

 var Class = function(p){ var secret = p; var count = 3; var counter = function(){ if(count > 0){ count –; return true; } else { return false; } } this.tellSecret = function(){ if(counter()){ return secret; } else { return null; } } } var o = new Class('12345'); alert(o.tellSecret()); //  '12345' alert(o.tellSecret()); //  '12345' alert(o.tellSecret()); //  '12345' alert(o.tellSecret()); //  null //      counter, // a   ,       o.counter = function(){ return true; } alert(o.tellSecret()); //    null 

.tellSecret is the preferred method. It returns the private property secret at the first three calls, and at all subsequent starts to return null . Each time .tellSecret calls the private method counter , which itself has access to the private properties of the object. Any code has access to the .tellSecret method, but this does not give direct access to the private members of the object.

Unlike the public methods created through .prototype , a copy of the privileged method is created in each object that is created, which naturally entails a greater memory consumption. Private and privileged members are created only at the time of initialization of the object and later can not be changed.

Static members


Static properties and methods are properties and methods that are tied to the constructor function itself (to the class itself). Therefore, they are also called class properties and methods. They are available to any code both inside and outside the object:

 var Class = function(p){ this.p = p; } Class.prototype.tell = function(word){ alert(this.p + ' ' + word + ' ' + this.constructor.p); // alert(this.p + ' ' + word + ' ' + Class.p); } Class.p = 'futurico'; var o = new Class('karaboz'); o.tell('love'); //  'karaboz loves futurico'; 


Closure (closure)


Private and privileged methods are possible in JavaScript due to what is called closure. A closure is a function, plus all the lexical variables from the covering context that it uses. When we use the function operator, we always create not a function, but a closure. A closure “remembers” the values ​​of all variables that existed in the context creating this closure, even when the function is used already outside the context that created it.

 var createFunc = function(param){ var closureParam = param; //  var returnedFunc = function(){alert(closureParam);} return returnedFunc; } var f = createFunc('karaboz'); 

Now, if we look at the variable f , we will see that this is a normal function, in the body of which is the closureParam parameter, which is undefined anywhere in the surrounding context and should give an error:

 alert(f); // : function(){alert(closureParam);} 

However, there will be no error; the function(){alert(closureParam);} due to the closure effect, remembers closureParam from the context that generated it:

 f(); //  'karaboz' 

If we recall the privileged .tellSecret method described above, now we can understand how it works. The method remembers both the private function count() and the private property secret , declared in the context that creates .tellSecret . At the same time, when count() is called inside .tellSecret , this last function, in turn, remembers the count() .tellSecret used in its body.

OOP in Java Script (4/5): Class Inheritance


Basic principles of class inheritance:

  1. A subclass always inherits all the properties and methods defined in its superclass.
  2. A subclass can override inherited properties and methods, as well as create new ones - and this should not affect the properties and methods of the superclass with the same name.
  3. A subclass should be able to call the native methods of the superclass even if it overrides them.
  4. Subclass objects should be initialized only at the time of their creation.

There are no tools in JavaScript to create classic class inheritance. Instead, there is inheritance based on the object's .prototype property: when the object method is called, the interpreter searches for this method in the properties of the object itself, and if it does not find the method there, it continues searching in the property (object) of the object's .prototype function.

Knowing about this JavaScript behavior, let's try to create the inheritance of two classes:

 var Class = function(){ // -  this.className = 'Class'; } Class.prototype.method = function(){ //     alert('method of ' + this.className); } var ClassSub = function(){ // -  this.className = 'ClassSub'; } ClassSub.prototype = new Class(); //     .prototype  var objSub = new ClassSub(); //    ClassSub objSub.method(); // !  'method of ClassSub' 

We see that the subclass inherits the method .method its superclass (executes it as its own). How does this happen? First, the interpreter searches for the .method method in the objSub object objSub and naturally does not find it there. Next, the interpreter accesses ClassSub.prototype and searches for .method among the properties of this object. Again, it doesn’t find anything: we have never set anything similar to ClassSub.prototype.method = function(){} . But after all, the ClassSub.prototype object ClassSub.prototype created from the Class() constructor function. Therefore, not finding the necessary properties in ClassSub.prototype itself, the interpreter refers to the .prototype function of the constructor of this object. And here already finds the requested method: Class.prototype.method = function(){} .

We confirm this long argument with a simple comparison:

 // .method  objSub   .method  ClassSub.prototype alert(objSub.method == ClassSub.prototype.method); // true //  .method  ClassSub.prototype   .method  Class.prototype alert(ClassSub.prototype.method == Class.prototype.method); // true 

Such a chain of prototypes can be arbitrarily long, but the search for the interpreter in any case ends at the moment when it gets to the object created (explicitly or implicitly) from the built-in class Object . If in the Object.prototype it still does not find the requested method, it will return an error. The Object class lies at the very top of any possible hierarchy of classes created in JavaScript.

Now let's try to override this inherited method, and at the same time expand the subclass with our own additional method. At the same time, we check that the methods of the superclass remain the same (remember that you can add public methods and properties even after creating an instance of the class):

 ClassSub.prototype.method = function(){ //     alert('method of ' + this.className + ' but new one'); } ClassSub.prototype.methodSub = function(){ //     alert('methodSub of ' + this.className); }; //       objSub.method(); //  'method of ClassSub but new one' //       objSub.methodSub(); //  'methodSub of ClassSub' var obj = new Class(); //    Class //       obj.method(); //  'method of Class' //       obj.methodSub(); //   'obj.methodSub is not a function' 

So for now everything is going fine. We redefined the .method method in a subclass, and an instance of the subclass began to implement it. Simultaneously, a copy of the superclass retained its same method with the same name. We have created a new subclass method that works successfully on an instance of the subclass. At the same time, this new method did not become a superclass method - an instance of the superclass does not see it and gives an error.

Everything looks simple until we try to write more realistic code. As a rule, the constructor function not only defines the properties of an object, but also performs some initialization functions. For example, create an Animal class in which the name of an individual will be passed as a parameter, and each new instance of which will scream at birth (=

 var Animal = function(name){ this.name = name; this.cry(); //      } Animal.prototype.cry = function(){ alert('whoa!'); } var animal_thing = new Animal('karaboz'); //  'whoa!'; 


Now create a subclass of Cat, instances of which do not scream, but meow:

 var Cat = function(name){ this.name = name; this.cry(); } Cat.prototype = new Animal(); //    Animal Cat.prototype.cry = function(){ //   .cry alert('meow!'); } var cat_thing = new Cat('mertas'); 

By running this code, we will hear not two shouts (whoa! Meow!), But three! (whoa!, whoa !, meow!) And it is clear why. The second cry occurs at the very moment when we do the inheritance of Cat.prototype = new Animal (). We unwittingly create an instance of the class Animal (and make it scream at birth). Those. we run the designer function of the superclass idle before creating any instance of the subclass!

In addition, in the subclass we completely duplicated the constructor function of the superclass! While we do not even see how otherwise it is possible to force the subclass to assign properties to the object, passed through the parameters of the constructor function, and how to make this constructor do something differently in another way.

Solving the problem of idle call of the constructor function of a superclass


Maybe try not to create an instance of the class Animal , but just point out the equality of the prototypes of the two classes? (after all, it is through their prototypes that they are connected). Let's try to change this line:

 Cat.prototype = Animal.prototype; 

Running the code, we hear two expected shouts! But it only seems that the problem is solved. Let's try to create another instance of the Animal superclass right after creating an instance of the Cat subclass.

 var animal_thing_new = new Animal('juks'); //  'meow!',      Cat! 

This copy is screaming in Cat voice! It turned out that we overwritten the same-name method of the parent class. The thing is, when we write Cat.prototype = Animal.prototype , we pass the Animal.prototype object to the Cat.prototype object by reference (as always happens when an object is assigned to a variable).Therefore, any changes of the first reasonably lead to a change in the second. When we wrote Cat.prototype = new Animal(), we created a Cat.prototypenew object. Changing its properties, we did not affect the properties of .prototypethe object's constructor function itself.

Let's try to implement inheritance - without creating an instance of the parent class - a little differently. Let's just try to copy .prototypeall the properties and methods from the .prototypesuperclass to the subclass. Rewrite the problem line as follows:

 for (var prop in Animal.prototype){ Cat.prototype[prop] = Animal.prototype[prop]; } 

Run the code and see that the third individual is no longer meowing, i.e. The parent class method is still the same! But did we do well? In fact, we did not inherit the properties of the superclass, but simply created another copy of them. If there are many objects of a subclass, then each object will create its own complete copy of all properties of the superclass. Moreover, if you try to change the class methods after creating objects of a subclass, then these changes will not affect the objects of the subclass! This code seems very inflexible and cumbersome.

Let us resort to the following tricky trick: we will create .prototypea new object in the subclass that has a link to the .prototypesuperclass, but does not launch the constructor function of the superclass. Rewrite the complex line again:

 var Empty = function(){}; //   - Empty.prototype = Animal.prototype; Cat.prototype = new Empty(); 

We created Cat.prototypean object in an artificial class Empty. When creating this object, nothing happens because the constructor function is Empty()empty. Any assignments in Cat.prototypewill affect only changes in the properties of the object itself Cat.prototypeand will not affect the constructor function of the superclass Animal. If the interpreter does not find the required method either in the class instance Cator in the properties Cat.prototype, it will turn to the object's constructor function Cat.prototype (== new Empty())and begin to search in Empty.prototype, which refers directly to the one we need.Animal.prototype

Solving the problem of duplicating the constructor function of a superclass


We would like to do something like this:

 var Cat = function(name){ Animal.apply(this, arguments); } 

Those.during initialization of each new object of the subclass, Catcall the constructor function of the superclass Animalin the context of the object new Cat(). In principle, our code already works well, but I would like to see it more universal - not tied to specific class names.

Let's make one lyrical digression. As we remember, when creating any object, it forms a property .constructor, taken from .prototype.constructorthe constructor function that spawned it. However, when we recorded:, Cat.prototype = new Empty()we created a Cat.prototypenew object. If you now try to turn to (new Cat()).constructor, the interpreter will go to look for it in Cat.prototype.constructor, which means in (new Empty().constructor)and will find this property as a result in Empty.prototype.constructor ( == Animal.prototype.constructor). Those.our property .constructornow points to the constructor function of a superclass, not a subclass! We have corrupted this property. Knowing all this, right now could be written:

 var Cat = function(name){ this.constructor.apply(this, arguments); } 

and get the desired universality of the code, but rather with this code we will add even more confusion, because .constructoran object must point to a construct function of a subclass, not a superclass. Therefore, let's look like this: in the place of the last problem line in which inheritance took place, we write the following:

 var Empty = function(){}; //   - Empty.prototype = Animal.prototype; Cat.prototype = new Empty(); Cat.prototype.constructor = Cat; //     - Cat.superClass = Animal; //    -   ,     - : var Cat = function(name){ Cat.superClass.apply(this, arguments); } 

If we now want in the subclass to change our method not entirely, but only expand it, we can easily do it like this:

 Cat.prototype.cry = function(){ Cat.superClass.prototype.cry.apply(this, arguments); alert('one more cat was born'); } 

We will restore order in our code and write a universal inheritance function. We write it in the .prototypebuilt-in constructor function Function. Thus, we will create a new method for all possible functions, incl. and for our custom classes.

 //    Function.prototype.inheritsFrom = function(superClass) { var Inheritance = function(){}; Inheritance.prototype = superClass.prototype; this.prototype = new Inheritance(); this.prototype.constructor = this; this.superClass = superClass; } // -  var Class = function(){} //      Class.prototype.method = function(){}; // -  var ClassSub = function(){ ClassSub.superClass.apply(this, arguments); } //   ClassSub.inheritsFrom(Class); // sic! //      ClassSub.prototype.method = function(){ ClassSub.superClass.prototype.method.apply(this, arguments); } 


OOP in Java Script (5/5): Useful Links


  1. Private Members in JavaScript , Douglas Crockford
  2. Classical Inheritance in JavaScript , Douglas Crockford
  3. OOP in JS, Part 1: Public / Private Variables and Methods , Gavin Kistner
  4. OOP in JS, Part 2: Inheritance , Gavin Kistner
  5. Inheritance in JavaScript , Kevin Lindsey
  6. Little tricks of JavaScript, or writing scripts in a new way , Dmitry Koterov
  7. Big tricks of JavaScript , Dmitry Koterov
  8. JavaScript ,

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


All Articles