
Classical inheritance in JavaScript. Parsing implementations in Babel, BackboneJS, and Ember
In this article we will talk about classical inheritance in JavaScript, common patterns of its use, features and frequent application errors. Let's look at inheritance examples in Babel, Backbone JS, and Ember JS and try to derive from them the key principles of object-oriented inheritance for creating our own implementation in EcmaScript 5.
An article for those who are familiar with inheritance in other languages and have encountered attempts to emulate this behavior in JavaScript , as well as for those who are interested in looking “under the hood” of various libraries and frameworks, comparing their implementation. It turns out that the simple extend function can be implemented in very different ways. Often, mistakes are made in this case (see paragraph "The most common mistake" below).
By classic is meant OOP inheritance. As you know, in pure JavaScript there is no classical inheritance. Moreover, it lacks the concept of classes. Although the current EcmaScript specification adds syntax constructs for working with classes, this does not change the fact that it actually uses constructor functions and prototyping. Therefore, this technique is often called "pseudo-classical" inheritance. It probably pursues the only goal - to present the code in the usual OOP style.
There are various inheritance techniques, in addition to the classical: functional, prototype (in its pure form), factory, using mixins. The very concept of inheritance, which has gained high popularity among developers, is criticized and, in many cases, contrasted with a reasonable alternative - composition .
Inheritance, moreover, in the classical style, is not a panacea. Its feasibility depends on the specific situation in a particular project. However, in this article we will not delve into the issue of the advantages and disadvantages of this approach, but focus on how to use it correctly.
So, we decided to use OOP and classical inheritance in a language that initially does not support it. This decision is often made in large projects by developers who are used to OOP in other languages. It is also used by many large frameworks: Backbone, Ember JS, etc., as well as the modern EcmaScript specification.
We will analyze them in five aspects:
Of course, first of all, you should make sure that the template used is effective in terms of memory and performance. There are no particular complaints regarding the examples from the popular frameworks in this regard, however, in practice, erroneous examples often lead to memory leaks and stack growth, which we will discuss below.
The remaining criteria listed are for usability and readability of the code.
We will consider more “convenient” those implementations that are closer in syntax and functionality to classical inheritance in other languages. So, a link to a superclass (the keyword super) is optional, but its presence is desirable for a full emulation of inheritance. By cosmetic details we mean the general design of the code, the convenience of debugging, use with the operator
Let's consider inheritance in EcmaScript 6 and what we get when we compile code in ES5 using Babel.
The following is an example of a class extension in ES6.
As you can see, the syntax is close to other OOP languages, with the possible exception of the absence of types and access modifiers. And this is the uniqueness of using ES6 with a compiler: we can afford convenient syntax, and at the same time get working code on ES5 at the output. None of the following examples can boast of such syntactic simplicity, because in them, the inheritance function is implemented immediately in the finished form, without syntax conversions.
The Babel compiler implements inheritance with a simple function
The main point here can be reduced to this line:
This call creates an object with the specified prototype. The
The following line of code implements the inheritance of the class’s static fields:
The constructor of the parent class (i.e., function) becomes the prototype of the constructor of the new class (i.e., another function). All static properties and methods of the parent class are thus made available from the derived class. In the absence of a function,
The very recording of methods, both static and dynamic, occurs separately from the call by
The super keyword is simply replaced when compiling a direct prototype call. For example, calling the parent constructor from the example above is replaced by the following line:
Babel uses many helper functions that we will not cover here. The bottom line is that in this call the interpreter receives the prototype of the constructor of the current class, which is just the constructor of the base class (see above), and calls it in the current context
In our own implementation on pure ES5, the compilation stage is not available to us, so you can add fields
Backbone JS provides a function
An example of use is as follows:
This function implements an extension of the base class with support for its own constructor and static fields. It returns a constructor function of the class. Inheritance itself is implemented by the following line, similar to the example from Babel:
The function
The inheritance of the static fields of a class is implemented by simply copying the references (or values) from the parent class and the object with static fields, passed as the second argument to the extend function, to the created constructor:
The constructor reference is optional and is done inside the class declaration in the form of the constructor method. When using it, you must necessarily call the constructor of the parent class (as in other languages), so instead, developers often use a method
The keyword "__super__" is just a convenient addition, because a call to the parent method still occurs with the name of a specific method and with the transfer of context
From a code point of view, extending classes in Backbone is pretty terse. You do not have to manually create a class constructor and separately bind it to the parent class. This convenience comes at a price - debugging difficulties. In the browser debugger, all instances of classes inherited in this way have the same constructor name declared inside the function

It is much more convenient to debug this chain using inheritance from Babel:

Another drawback is that property
Ember JS uses both the function
What is of particular interest is the implementation of the “super” keyword in Ember. It allows you to call the parent method without specifying a specific method name, for example:
Note: when calling the super-class (
How it works? How does Ember know which method to call when accessing a universal property
When inheriting a class, Ember passes through all its methods and calls for each given wrapper function, replacing each original function with
Pay attention to the line marked with a comment. A
The idea is undoubtedly interesting, and it can be applied in its implementation of inheritance. But it’s important to note that all this negatively affects performance. Each class method (at least from those that override the parent method), regardless of the fact of using the property
One of the most common and dangerous errors in practice is to create an instance of the parent class when it is expanded. Here is an example of such code, the use of which should always be avoided:
Have you noticed a mistake?
This code will work, it will allow the SubClass to inherit the properties and methods of the parent class. However, when the classes are linked through
This can lead to severely detectable errors when the properties initialized in the parent constructor (
Instead, create an empty object whose prototype is the prototype property of the parent class:
We gave examples of the implementation of pseudo-classical inheritance in the Babel compiler (ES6-to-ES5) and in the frameworks of Backbone JS, Ember JS. Below is a comparative table of all three implementations according to the previously described criteria.
* - Babel application is ideal when using ES6; if you write your implementation on its basis for ES5, static fields and a link to the superclass will have to be added independently.
The performance criterion was evaluated not in absolute values, but relative to other implementations, based on the number of operations and cycles in each variant. In general, differences in performance are not significant, as extension of classes usually occurs once at the initial stage of the application and is not called again.
All the above examples have their advantages and disadvantages, but the implementation of Babel can be considered the most practical. As mentioned above, if possible, use the inheritance specified in EcmaScript 6 with compilation in ES5. If this is not possible, it is recommended to write your own implementation of the function
An article for those who are familiar with inheritance in other languages and have encountered attempts to emulate this behavior in JavaScript , as well as for those who are interested in looking “under the hood” of various libraries and frameworks, comparing their implementation. It turns out that the simple extend function can be implemented in very different ways. Often, mistakes are made in this case (see paragraph "The most common mistake" below).
The article is available in English with a short video presentation on Today Software Magazine.
About classic inheritance
By classic is meant OOP inheritance. As you know, in pure JavaScript there is no classical inheritance. Moreover, it lacks the concept of classes. Although the current EcmaScript specification adds syntax constructs for working with classes, this does not change the fact that it actually uses constructor functions and prototyping. Therefore, this technique is often called "pseudo-classical" inheritance. It probably pursues the only goal - to present the code in the usual OOP style.
There are various inheritance techniques, in addition to the classical: functional, prototype (in its pure form), factory, using mixins. The very concept of inheritance, which has gained high popularity among developers, is criticized and, in many cases, contrasted with a reasonable alternative - composition .
Inheritance, moreover, in the classical style, is not a panacea. Its feasibility depends on the specific situation in a particular project. However, in this article we will not delve into the issue of the advantages and disadvantages of this approach, but focus on how to use it correctly.
Comparison criteria
So, we decided to use OOP and classical inheritance in a language that initially does not support it. This decision is often made in large projects by developers who are used to OOP in other languages. It is also used by many large frameworks: Backbone, Ember JS, etc., as well as the modern EcmaScript specification.
The best advice on applying inheritance would be to use it as described in EcmaScript 6, with the keywords class, extends, constructor, etc. If you have such an opportunity,Let's look at some popular examples of implementing classical inheritance.then you can not read further,this is the best option in terms of code readability and performance. All the following description will be useful for the case of using the old specification, when the project has already been started using ES5 and the transition to the new version does not seem available.
We will analyze them in five aspects:
- Memory efficiency.
- Performance.
- Static properties and methods.
- Link to the superclass.
- Cosmetic details.
Of course, first of all, you should make sure that the template used is effective in terms of memory and performance. There are no particular complaints regarding the examples from the popular frameworks in this regard, however, in practice, erroneous examples often lead to memory leaks and stack growth, which we will discuss below.
The remaining criteria listed are for usability and readability of the code.
We will consider more “convenient” those implementations that are closer in syntax and functionality to classical inheritance in other languages. So, a link to a superclass (the keyword super) is optional, but its presence is desirable for a full emulation of inheritance. By cosmetic details we mean the general design of the code, the convenience of debugging, use with the operator
instanceof
etc.The _inherits function in Babel
Let's consider inheritance in EcmaScript 6 and what we get when we compile code in ES5 using Babel.
The following is an example of a class extension in ES6.
class BasicClass {
static staticMethod() {}
constructor(x) {
this.x = x;
}
someMethod() {}
}
class DerivedClass extends BasicClass {
static staticMethod() {}
constructor(x) {
super(x);
}
someMethod() {
super.someMethod();
}
}
As you can see, the syntax is close to other OOP languages, with the possible exception of the absence of types and access modifiers. And this is the uniqueness of using ES6 with a compiler: we can afford convenient syntax, and at the same time get working code on ES5 at the output. None of the following examples can boast of such syntactic simplicity, because in them, the inheritance function is implemented immediately in the finished form, without syntax conversions.
The Babel compiler implements inheritance with a simple function
_inherits
:function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
The main point here can be reduced to this line:
subClass.prototype = Object.create(superClass.prototype);
This call creates an object with the specified prototype. The
prototype
constructor property subClass
points to a new object whose prototype is the prototype
parent class superclass
. Thus, this is a simple prototype inheritance disguised as classic in the source code. The following line of code implements the inheritance of the class’s static fields:
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
The constructor of the parent class (i.e., function) becomes the prototype of the constructor of the new class (i.e., another function). All static properties and methods of the parent class are thus made available from the derived class. In the absence of a function,
setPrototypeOf
Babel provides for direct writing of the prototype to a hidden property __proto__
- the technique is not recommended, but suitable in extreme cases when using older browsers. The very recording of methods, both static and dynamic, occurs separately from the call by
_inherits
simply copying the links to the constructor or it prototype
. When writing your own inheritance implementation, you can use this example as a basis and add objects with dynamic and static fields to it as additional arguments to the function _inherits
.The super keyword is simply replaced when compiling a direct prototype call. For example, calling the parent constructor from the example above is replaced by the following line:
return _possibleConstructorReturn(this, (DerivedClass.__proto__ || Object.getPrototypeOf(DerivedClass)).call(this, x));
Babel uses many helper functions that we will not cover here. The bottom line is that in this call the interpreter receives the prototype of the constructor of the current class, which is just the constructor of the base class (see above), and calls it in the current context
this
. In our own implementation on pure ES5, the compilation stage is not available to us, so you can add fields
_super
to the constructor prototype
to have a convenient link to the parent class, for example:function extend(subClass, superClass) {
// ...
subClass._super = superClass;
subClass.prototype._super = superClass.prototype;
}
The extend function in Backbone JS
Backbone JS provides a function
extend
for extending library classes: Model, View, Collection, etc. If you wish, you can borrow it for your own purposes. Below is the function code extend
from Backbone version 1.3.3.var extend = function(protoProps, staticProps) {
var parent = this;
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent constructor.
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function and add the prototype properties.
child.prototype = _.create(parent.prototype, protoProps);
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
return child;
};
An example of use is as follows:
var MyModel = Backbone.Model.extend({
constructor: function() {
// ваш конструктор класса; его использование опционально,
// но если используете, нужно обязательно вызвать родительский конструктор
Backbone.Model.apply(this, arguments);
},
toJSON: function() {
// метод переопределён, но можно вызвать родительский через «__super__»
MyModel.__super__.toJSON.apply(this, arguments);
}
}, {
staticMethod: function() {}
});
This function implements an extension of the base class with support for its own constructor and static fields. It returns a constructor function of the class. Inheritance itself is implemented by the following line, similar to the example from Babel:
child.prototype = _.create(parent.prototype, protoProps);
The function
_.create()
is an analogue Object.create()
from ES6 implemented by the Underscore JS library. Its second argument allows you to immediately write to the prototype the properties and methods protoProps
passed when the function was called extend
. The inheritance of the static fields of a class is implemented by simply copying the references (or values) from the parent class and the object with static fields, passed as the second argument to the extend function, to the created constructor:
_.extend(child, parent, staticProps);
The constructor reference is optional and is done inside the class declaration in the form of the constructor method. When using it, you must necessarily call the constructor of the parent class (as in other languages), so instead, developers often use a method
initialize
that is called automatically from within the parent constructor. The keyword "__super__" is just a convenient addition, because a call to the parent method still occurs with the name of a specific method and with the transfer of context
this
. Without this, such a challenge would lead to a loop in the case of a multi-level chain of inheritance. The superclass method, whose name is usually known in the current context, can be called directly, so this keyword is only an abbreviation for:Backbone.Model.prototype.toJSON.apply(this, arguments);
From a code point of view, extending classes in Backbone is pretty terse. You do not have to manually create a class constructor and separately bind it to the parent class. This convenience comes at a price - debugging difficulties. In the browser debugger, all instances of classes inherited in this way have the same constructor name declared inside the function
extend
- “child”. This flaw may seem insignificant until you come across it in practice when debugging a class chain, when it becomes difficult to understand which class an object is from and which class it inherits from: 
It is much more convenient to debug this chain using inheritance from Babel:

Another drawback is that property
constructor
is enumerable, i.e. enumerated when traversing an instance of a class in a for-in loop. It is immaterial, however, Babel took care of this, declaring a constructor listing the necessary modifiers.Superclass reference in Ember JS
Ember JS uses both the function
inherits
implemented by Babel, and its own implementation extend
- very complex and sophisticated, with support for mixins and other things. There is simply not enough space to bring the code for this function in this article, which already casts doubt on its performance when used for its own needs outside the framework. What is of particular interest is the implementation of the “super” keyword in Ember. It allows you to call the parent method without specifying a specific method name, for example:
var MyClass = MySuperClass.extend({
myMethod: function (x) {
this._super(x);
}
});
Note: when calling the super-class (
this._super(x)
) method, we do not specify the name of the method. And no code transformations occur during compilation. How it works? How does Ember know which method to call when accessing a universal property
_super
without code conversion? It's all about the complex work with classes and the tricky function _wrap
, the code of which is given below:function _wrap(func, superFunc) {
function superWrapper() {
var orig = this._super;
this._super = superFunc; // <--- магия здесь
var ret = func.apply(this, arguments);
this._super = orig;
return ret;
}
// здесь опущена нерелевантная часть кода
return superWrapper;
}
When inheriting a class, Ember passes through all its methods and calls for each given wrapper function, replacing each original function with
superWrapper
. Pay attention to the line marked with a comment. A
_super
pointer to the parent method corresponding to the name of the method being called is written to the property (the work to determine correspondence occurred even at the stage of creating the class when called extend
). Next, the original function is called, from within which you can refer to _super
as the parent method. Then the property is _super
assigned the original value, which allows it to be used in deep chains of calls.The idea is undoubtedly interesting, and it can be applied in its implementation of inheritance. But it’s important to note that all this negatively affects performance. Each class method (at least from those that override the parent method), regardless of the fact of using the property
_super
in it, turns into a separate function. Therefore, with a deep chain of method calls of one class, the stack will grow. This is especially critical for methods that are called regularly in a loop or when rendering the user interface. Therefore, we can say that this implementation is too cumbersome and does not justify the advantage obtained in the form of an abbreviated form of recording.The most common mistake
One of the most common and dangerous errors in practice is to create an instance of the parent class when it is expanded. Here is an example of such code, the use of which should always be avoided:
function BaseClass() {
this.x = this.initializeX();
this.runSomeBulkyCode();
}
// ...объявление методов BasicClass в прототипе...
function SubClass() {
BaseClass.apply(this, arguments);
this.y = this.initializeY();
}
// собственно наследование
SubClass.prototype = new BaseClass();
SubClass.prototype.constructor = SubClass;
// ...объявление методов SubClass в прототипе...
new SubClass(); // создание экземпдяра
Have you noticed a mistake?
This code will work, it will allow the SubClass to inherit the properties and methods of the parent class. However, when the classes are linked through
prototype
, an instance of the parent class is created, its constructor is called, which leads to unnecessary actions, especially if the constructor does a lot of work when creating the object ( runSomeBulkyCode ). You can’t do this :SubClass.prototype = new BaseClass();
This can lead to severely detectable errors when the properties initialized in the parent constructor (
this.x
) are not written to the new instance, but to the prototype of all instances of the SubClass class . In addition, the same BaseClass constructor is then called repeatedly from the subclass constructor. If the parent constructor requires some parameters when calling, it is difficult to make such an error, but if they are absent, it is quite possible. Instead, create an empty object whose prototype is the prototype property of the parent class:
SubClass.prototype = Object.create(BasicClass.prototype);
Summary
We gave examples of the implementation of pseudo-classical inheritance in the Babel compiler (ES6-to-ES5) and in the frameworks of Backbone JS, Ember JS. Below is a comparative table of all three implementations according to the previously described criteria.
Babel | Backbone js | Ember js | |
---|---|---|---|
Memory | Equally | ||
Performance | Higher | Average | Lower |
Static fields | + (only in ES6) * | + | - (excluding the internal use of inheritance from Babel) |
Superclass Link | super.methodName() (only in ES6) | Constructor.__super__.prototype | this._super() |
Cosmetic details | Ideal with ES6; needs refinement in its own implementation under ES5 | Convenience of the announcement; debugging problems | Depends on the method of inheritance; The same debugging problems as in Backbone |
The performance criterion was evaluated not in absolute values, but relative to other implementations, based on the number of operations and cycles in each variant. In general, differences in performance are not significant, as extension of classes usually occurs once at the initial stage of the application and is not called again.
All the above examples have their advantages and disadvantages, but the implementation of Babel can be considered the most practical. As mentioned above, if possible, use the inheritance specified in EcmaScript 6 with compilation in ES5. If this is not possible, it is recommended to write your own implementation of the function
extend
based on an example from the Babel compiler, taking into account the above comments and additions from other examples. So inheritance can be implemented in the most flexible and appropriate way for a given project.Sources
Only registered users can participate in the survey. Please come in.
Which of the inheritance implementations do you prefer (other than the standard in EcmaScript 6)?
- 45.6% Babel 26
- 10.5% Backbone 6
- 0% Ember 0
- 22.8% Other 13
- 21% None: I am against OOP in JavaScript 12