📜 ⬆️ ⬇️

JS classes with protected, multiple inheritance, getters / setters, and impurities

Hi, Habr! To begin with, I'm tired of the wretchedness of classes and inheritance in JavaScript! Having spent thousands of hours on a large JS project, it became obvious to me. Especially when switching from the back end using Yii2, to the front end. Indeed, in Yii2 and php there are real classes, real protected / private fields, there is a trait, all sorts of dependency injection and behavior. And right after all these things, you create such a file NewClass.js in order to write some class, and you realize that in JavaScript there is nothing like that. And even more, classes can be written in hundreds of different ways - prototype / functional inheritance, ES6 classes, and different sugars using external libraries. Then I said to myself - "enough to endure it!".


What do we offer in modern standards?


In ES6, it became possible to describe classes in a more familiar way for all languages ​​using the class {} syntax. However, this is rather a more familiar record of classes using the old prototype inheritance, and neither protected nor privatized access modifiers to class properties have appeared in it. In the newest ES2017 standard, this is still not the case.


Cycling


Of course, I didn’t want to be a bike collector, and the first thing I did, before I got down to my library version, was to look for existing solutions. And, everything that will be described below, is not my discovery - the frame for the bike has already been found in the ideas of other sources , and the mozart library. I would like to especially note the latter, since it served as a good basis for the further development of the idea of ​​implementing almost real classes.


Overview of Features


In order not to turn the article into a README retelling of the project, I will describe only briefly the list of possibilities, and give an example of use, and below I will tell you how all this magic works.


1) Public, protected and private access to class members
 var Figure = Class.create(function ($public, $protected, _) { $public.x = 0; $public.y = 0; $protected.name = 'figure'; $protected.init = function (x, y) { _(this).id = 123; // private this.x = x; this.y = y; }; $protected.protectedMethod = function () { console.log('protectedMethod: ', this.id, this.name, this.self.x, this.self.y); }; this.square = function (circle) { return 2 * Math.PI * circle.radius; } }); var Circle = Class.create(Figure, function ($public, $protected, _) { $public.radius = 10; $public.publicMethod = function () { console.log('publicMethod: ', _(this).id, _(this).name, this.radius); _(this).protectedMethod(); }; }); var circle = new Circle(2, 7); circle.radius = 5; circle.publicMethod(); // publicMethod: undefined figure 5 / protectedMethod: 123 figure 2 7 console.log(Circle.square(circle)); // 31.415926536 

2) Simple and multiple class inheritance, calling parent methods via $ super
 var Layer = Class.create(function ($public, $protected, _) { $protected.uid = null; $protected.init = function () { _(this).uid = Date.now(); } }); var Movable = Class.create(function ($public, $protected, _) { $public.x = 0; $public.y = 0; $protected.init = function (x, y) { this.x = x; this.y = y; } $public.move = function () { this.x++; this.y++; } }); var MovableLayer = Class.create([Layer, Movable], function ($public, $protected, _, $super) { $protected.init = function (x, y) { $super.get(Layer).init.apply(this, arguments); $super.get(Movable).init.apply(this, arguments); } }); var layer = new MovableLayer(); //    console.log(layer instanceof Layer, layer instanceof Movable); // true false console.log(Class.is(layer, Layer), Class.is(layer, Movable)); // true true 

3) Automatic creation of getters / setters
 var Human = Class.create(function ($public, $protected, _) { $protected.birthday = null; $public.getBirthday = function () { return _(this).birthday; }; $public.setBirthday = function (day) { _(this).birthday = day; }; $public.getAge = function () { var date = new Date(_(this).birthday); return Math.floor((Date.now() - date.getTime()) / (1000 * 3600 * 24 * 365)); }; }); var human = new Human(); human.birthday = '1975-05-01'; console.log(human.age); 

4) Impurities
 var SortableMixin = function ($public, $protected, _) { $public.sort = function () { _(this).data.sort(); }; }; var Archive = Class.create(null, SortableMixin, function ($public, $protected, _) { $protected.init = function () { _(this).data = [3, 9, 7, 2]; }; $public.outData = function () { console.log(_(this).data); }; }); var archive = new Archive(); archive.sort(); archive.outData(); // [2, 3, 7, 9] 

Expose the focus


Since objects in JavaScript do not have any access settings for its properties, we can simulate behavior similar to protected / private by hiding protected data. In normal functional inheritance, this is done by closing on the constructor itself, and all methods are created for each instance of the class:


 var SomeClass = function () { var privateProperty = 'data'; this.someMethod = function () { return privateProperty; }; }; var data = []; for (var i = 0; i < 10000; i++) { data.push(new SomeClass()); } 

When executing this code in memory, in addition to the objects themselves, another 10,000 functions, someMethod, will be created, which will badly eat out the memory. In this case, it is impossible to simply remove the function declaration outside the constructor, since in this case the function will lose access to privateProperty.


To solve this problem, we need to declare the function of the method only once, and receive protected data only at the expense of a pointer to this object:


 var SomeClass; (function () { var privateData = []; var counter = -1; SomeClass = function () { this.uid = ++counter; }; SomeClass.prototype.someMethod = function () { var private = privateData[this.uid]; }; })(); 

This is better, but still bad. First, a certain uid is accessible from outside. And secondly, the garbage collector will never clear what gets into the privateData array and will slowly but surely erase the memory. To solve two problems at once in ES6, wonderful classes Map and WeakMap appeared.


Map is almost the same arrays, but unlike them, any JavaScript object can be passed as a key. We will be more interested in WeakMap - this is also the same as Map, but unlike it, WeakMap does not prevent the garbage collector from clearing objects that fall into it.


Rewrite:


 var SomeClass; (function () { var privateData = new WeakMap(); SomeClass = function () {}; SomeClass.prototype.someMethod = function () { var private = privateData.get(this); }; })(); 

So we got private. With the implementation of protected, everything is much more complicated - to store protected data, they need to be placed in some common storage for all derived classes, but at the same time giving access to a specific class is not for all properties, but only those that are declared in it. We use WeakMap as such storage again, and the prototype of the object as the key:


 SomeClass.prototype.someMethod = function () { var protected = protectedData.get(Object.getPrototypeOf(this)); }; 

To restrict access only to those protected-properties that are in the class itself, we will issue to the class not the object itself with the protected data, but the associated object, the necessary properties of which will be obtained from the main object, by declaring a getter and a setter:


 var allProtectedData = { notAllowed: 'secret', allowed: 'not_secret' }; var currentProtectedData = {}; Object.defineProperties(currentProtectedData, { allowed: { get: function () { return allProtectedData.allowed; }, set: function (v) { allProtectedData.allowed = v; }, } }); currentProtectedData.allowed = 'readed'; console.log(allProtectedData.allowed, currentProtectedData.allowed, currentProtectedData.notAllowed); // readed readed undefined 

That's about the way it works.


img


Well, then it remains only to weigh all this beauty and possibilities, and voila!


Conclusion


You will find a detailed description of the possibilities in the project's README . Thank you all for your attention!


Project on GitHub


')

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


All Articles