When writing serious projects before JavaScript programmers have a choice: to sacrifice the quality of the code and write classes with their hands, or sacrifice speed and use the class system. And if you use the system, which one to choose?
The article describes the author’s system, which is not inferior in speed to classes written “by hand” (in other words, one of the fastest in the world). But at the same time classes have a pleasant structure in the style of C.
Class systems
There is a joke that every programmer should write his own class system. Who is not familiar with the problem - see
this comment , there are at least 50 of them collected.
')
Each of these bikes differs in its set of features, its programming style and its drop in speed. For example, creating a class MooTools is about 90 times slower than creating a class written by hand. Why then do we need all these systems?
In practice, it turns out that handwritten classes are very hard to maintain. When your JS application grows to a decent size, the prototypes will no longer be as "cool" as before, and you will probably think: it may be worth sacrificing performance a little, but it will be easier for people to work with it. Imagine, for example, what Ext.JS would look like, written on prototypes.
Note: some serious projects still do not use the class system, and it does not seem to suffer much from this. As an example - see the source code Derby.js. But I perceive Derby as a black box that does something for you, so the developers do not strongly encourage digging into its guts (correct, if not right); and in Ext, inheritance is quite the opposite.
System Benefits
What do we want from the system? First of all, it is a call to parent methods. Here is an example from MooTools:
var Cat = new Class({ Extends: Animal, initialize: function(name, age){ this.parent(age);
At first it looks very nice: inside any function you have a parent method. And refactoring is convenient - if you rename the method, then the call of the parent will not break. But for the beauty and convenience you have to pay a big price - every method in the class will be wrapped in such a terrible package:
var wrapper = function(){ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'); var caller = this.caller, current = this.$caller; this.caller = current; this.$caller = wrapper; var result = method.apply(this, arguments); this.$caller = current; this.caller = caller; return result; }.extend({$owner: self, $origin: method, $name: key});
It strongly interferes with debugging, not to mention the fact that it is very slow - this code will be executed when any method of the class is called.
What else is critically important? Each class instance must have its own properties:
var Cat = new Class({ food: [], initialize: function(name){ this.name = name; } }); var cat1 = new Cat(''); var cat2 = new Cat('');
As you can see, MooTools created its own food array for each class. How would all this be done with the traditional approach? We would assign the properties in the constructor:
function Cat() { this.food = []; Cat.superclass.constructor.call(this) } Cat.prototype.meow = function() {}
As for the methods, there are several options; in the example above, a variant with the Douglas Crockford extend function is shown. With a traditional system in the code, a lot of garbage like “Cat.prototype ...” and “superclass.constructor.call (this ...)”, such code is hard to perceive and refactor.
A few words about private class members
What is absolutely normal in C ++ is very harmful in JavaScript. I say this from my experience: if classes have private methods and variables, then such classes often become unsupported. If you want to change something in such a piece of code, then sometimes you have nothing left but to throw away the old code and rewrite everything from scratch.
Private members are bad practice. It is correct to have protected members (the name begins with "_"), and if you are afraid that some monkey will start to get them from the outside, then this is his business. Then it turns out that you hide them from the programmer who will inherit your class. Perhaps this is your goal, but most often private members do not solve anything, they only complicate the class and create problems for adequate programmers.
Now let's create a class system that is as convenient as C ++, but as fast as handwritten classes. And to work without preprocessors.
We write fast classes
So, the fastest way to create a class in JS is to write it with your own hands, using prototypes:
function Animal() {} Animal.prototype.init = function() {}
All browsers engines are optimized for this method. Step aside - and get a performance drop, for example:
Animal.prototype = { init: function() {} }
In this example, the prototype was assigned as an object. Chrome is eating normally, but in Firefox the speed of creating classes drops significantly.
Fast inheritance
Now we need to call parent methods. Is there anything faster than a prototype chain? And let's just rename the parent method in the heir class!
function Cat() {}
We copied the method from the prototype of the parent, and at the same time renamed it. Faster is simply impossible. Of course, we will not do it with our hands - the class system will do everything for us.
In this example, the instanceof operator will not work, but in practice you can do fine without it. I'm talking about real applications and tasks: if you need to distinguish the Animal type from the Cat, then this is a real task, and it is perfectly solved. But if you want to do this with the instanceof operator, then excuse me, you go to another doctor.
Even with this inheritance, there is no chain of prototypes (since the prototypes are copied) - this gives a slight acceleration compared to traditional solutions.
Convenient properties
Assigning the default properties in the constructor with your hands is also not very pleasant. So, let the script do it for us, as in MooTools. How it will work: the class system itself will generate a constructor function that will assign default properties. It will look like this:
ClassManager.define( 'Cat', { Extends: 'Animal', food: [], init: function() { this.Animal$init(); } });
As a result, we get:
Overridden parent methods are renamed according to this rule:
<__> + "$" + <_>
This syntax is the least that we paid for speed, and in practice it does not cause any inconvenience. And the classes themselves are nice debazhit, and nice to look at.
Classmanager
Now a little PR of my decision. Speed ​​test, ClassManager vs Native (
link to jsperf ):

The difference in the speed of creating classes can be attributed to the error jsperf (on the old charts, it is the same for all test variants). For your information: in practice, I happened to have the same code running as 2 different tests — it was executed with a 20% speed difference.
Why the call to the Native method is so slow - it says:
NativeChildClass.prototype.method = function() { NativeParentClass.prototype.method.apply(this); }
Immediately noticeable difference in speed between the call from its own prototype and through apply. If it seems to you that I counted here - then write your own tests, it will not be faster anyway.
Separately, it’s worth mentioning Firefox: creating a class that is generated in the browser is now much slower (on my old laptop, only 400,000 operations per second). But my ClassManager allows me to collect classes on the server - and in FF they work even faster than Native. In addition, it will speed up page loading.
ClassManager vs other systems
I took the test of the author of DotNetWise as a basis, but ... his test is despicable: he is testing class generation plus 500 iterations of methods. As you understand, the quality and speed of the generated code do not depend on the time of its generation, and for each tested framework this time introduces its own error. Moreover, my classes can be collected on the server.
So it will be much more fair to create classes first, and then test them. And if you need to compare the time of class generation, then it will be right to create a separate test for this, and not to mix it with the speed of calling methods.
In the original test - the author's system DNW, of course, leads. But if you fix the test, then in Chrome in the first place will be my ClassManager, followed by Fiber, and then DNW. In FF, TypeScript comes first, then Native, then ClassManager. Even so, this is a very specific test - here the creation of a class is measured along with the call of methods (in the wrong proportions), so I believe that it does not reflect the real picture. However,
here is the link and the results:

ClassManager features
I'll start with a very important detail: IDE hints work for my classes! At least in most cases (I use PhpStorm). Here is an example of what classes might look like:
Lava.ClassManager.define(
Standard Directives:
- Extends - direct inheritance. A descendant can be inherited from only one parent.
- Implements - for mixins and multiple inheritance. The properties and methods from the mixin retain their descendant, but everything that is redefined in the class takes precedence.
- Shared - takes the object to the prototype. By default, all objects in the body of the class are copied for each instance, but they can be shared.
Bonuses:
- There is the possibility of patching class methods on the fly and static constructors. For example, you want to apply the bugfix inside IE, and disable it in other browsers. In the class constructor, you can select the method you need and replace it in the prototype - even if your class is in the middle of the inheritance chain.
- Export generated classes. You can generate constructors on the server - this will save page load time and speed up the creation of objects in Firefox.
- Namespaces and packages. Read the documentation for details.
There are plans to add modifiers such as abstract and final.
Disadvantages:
- Now the Shared directive can only transfer objects to the prototype (not arrays). As a temporary solution, you can create an object with an array property, so this is just a slight inconvenience. There is a task for revision, but it is not yet a priority.
- And a more noticeable flaw: there is currently no tool that could compress the names of class members (if you simply rename them, the call to the parent methods will break). There are plans to create it, it will definitely appear, but not tomorrow. Interestingly, if I didn’t tell about it myself, would you pay attention to it?
Where to get?
The standalone version lies in
this repository . It also contains a link to the site of the main framework - there you will find excellent documentation (in English), there you can also see examples in the code, and take several universal classes like Observable (events), Properties (properties with events) and Enumerable (“live »Array).
PS
By the way: the main framework is called LiquidLava, and it was created as the best alternative to Angular and Ember. Interesting?
UPDIn the comments I was fixed: the speed of calling the Native method can be increased by replacing apply with call. The first test of ClassManager vs Native was updated: in FF, the speed of calling the Native method equaled the speed of ClassManager, and in Chrome it is still slightly inferior.