Feliciano Guimaraes (CC BY 2.0)
“Conquering the JavaScript Interview” is a series of articles I wrote to prepare readers for common interview questions for medium and advanced JavaScript development positions. I often ask this question myself in actual interviews. See “What is a Closure” for the first article in this series.
Note: This article uses ES6 standard as code examples. For more information about ES6, see the ES6 Study Guide.
Original link: https://medium.com/javascript-scene/master-the-javascript-interview-what-s-the-difference-between-class-prototypal-inher itance-e4cd0a7562e9#.d84c324od
Objects are widely used in JavaScript language. Learning how to use objects effectively will help improve work efficiency. Poor object-oriented design can lead to code engineering failure or, worse, corporate disaster.
Unlike most other languages, JavaScript is a prototype-based object system, not a class-based one. Unfortunately, most JavaScript developers don’t understand or use their object systems well enough and tend to use them in a class-like fashion, which can lead to messy use of objects in code. So it’s a good idea for JavaScript developers to have an understanding of both prototypes and classes.
What is the difference between class inheritance and stereotype inheritance?
This is a complicated issue, and there may be some disagreement in the comments section. Therefore, the rank of the reader needs to play up the twelve points of spiritual learning differences, and will learn to apply to practice.
Class inheritance: A class can be likened to a blueprint that describes the properties and characteristics of the object being created.
It is well known that you can create an instance of a class by calling a constructor using the new keyword. In ES6, class inheritance can be implemented without the class keyword. Concepts like classes in the Java language don’t technically exist in JavaScript. But JavaScript borrows the idea of constructors. The class keyword in ES6 is a wrapper around constructors, and is still a function at heart.
class Foo {}
typeof Foo // 'function'
Copy the code
Although the implementation of class inheritance in JavaScript is based on prototype inheritance, it does not mean that the two have the same functionality:
JavaScript class inheritance uses a Prototype chain to connect the [[Prototype]] of a subclass to its parent class, forming the proxy pattern. Normally, the super()_ constructor is also called. This mechanism leads to a single inheritance structure and the tightest coupling behavior in object-oriented design.
“The inheritance relationship between classes leads to the correlation between subclasses, resulting in hierarchy based classification.”
Stereotype inheritance: A stereotype is an instance of a working object. Objects inherit properties directly from other objects.
In the prototype inheritance pattern, an object instance can be composed of multiple object sources. This makes inheritance more flexible and the [[Prototype]] agent hierarchy shallow. In other words, object-oriented design based on archetypal inheritance does not have the side effect of hierarchical categorization — a key difference from class inheritance.
Object instances are usually created by factory functions or object.create (), or can be defined directly using Object literals.
“A prototype is an instance of a working object. Objects inherit properties directly from other objects.”
Why is it important to understand class inheritance versus stereotype inheritance?
Inheritance is essentially a code reuse mechanism — a way for objects to share code. If the code is shared in the wrong way, it can cause many problems, such as:
Using class inheritance has the side effect of parent-child object classification
This hierarchy of class inheritance will inevitably lead to problems for new use cases. Furthermore, excessive derivation of a base class can also lead to weak base class problems whose errors are difficult to fix. In fact, class inheritance causes many problems in the object-oriented programming world:
-
The problem of tight coupling (class inheritance is the most coupled type of design in object-oriented design), tight coupling also causes another problem:
-
Fragile base class problems
-
Hierarchy rigor (the emergence of new use cases eventually causes problems at all involved inheritance levels)
-
Inevitable repeatability (because of rigid hierarchies, existing code is often copied rather than modified to accommodate new use cases)
-
Gorilla-banana problem (You want a banana, but you end up with a gorilla with a banana, and the jungle)
I’ve discussed these issues in depth: “Class Inheritance is a thing of the past — Exploring the idea of Object-oriented programming based on prototypes.”
“Choose object composition over class inheritance.” ~ Pioneer Four, Design Patterns: The Way to Reusable Object-oriented Software
It sums it up nicely:
Are all inheritance methods problematic?
When people say “preference object composition over inheritance” they mean “preference object composition over class inheritance” (quote from Design Patterns). This idea is a general consensus in the field of object-oriented design, because the congenital defect of class inheritance will lead to many problems. When people talk about inheritance, they routinely omit the word class, making it seem like they’re referring to all inheritance methods, which is not the case.
Because most inheritance methods are pretty good.
Three different approaches to prototype inheritance
Before diving into other types of inheritance, I need to take a closer look at what I mean by class inheritance.
You can find and test this sample program on Codepen.
BassAmp inherited from GuitarAmp, ChannelStrip inherited from BassAmp and GuitarAmp. From this example we can see how object-oriented design can go wrong. ChannelStrip is not actually a GuitarAmp, and it does not require a Cabinet property at all. A better solution is to create a new base class that amps and Strip can inherit from, but this approach is still limited.
Eventually, the strategy of adopting a new base class fails.
A better approach is to use class composition to inherit properties that are really needed:
The modified code.
If you look closely at this code, you can see that by composing objects, we can guarantee that objects can be inherited on demand. This is not possible with the class inheritance pattern. When class inheritance is used, the subclass inherits both needed and unwanted attributes.
At this point you might say, “Well, that’s true. But why is there no mention of a prototype?”
First of all, you need to know that there are three methods of object-oriented design based on prototypes.
-
Splicing inheritance: Is the pattern of copying properties directly from one object to another. Copied prototypes are often referred to as mixins. ES6 provides a handy tool for this pattern, object.assign (). Prior to ES6, Underscore/Lodash is generally used for.extend(), or $.extend() in jQuery. The above example of object composition takes the form of concatenation inheritance.
-
Stereotype proxy: In JavaScript, an object may contain a reference to a stereotype, called a proxy. If an attribute does not exist in the current object, its proxy prototype is looked up. The proxy prototype itself will have its own proxy prototype. This creates a chain of archetypes, looking up the proxy chain until the property is found, or until the root proxy Object.prototype is found. Prototypes do this by creating instances using the new keyword and linking constructive.prototype into an inheritance chain. Of course, you can use Object.create() for the same purpose, or mix it with concatenation inheritance to reduce multiple prototypes to a single agent, or extend them after the Object instance is created.
-
Function inheritance: In JavaScript, any function can be used to create objects. If a function is neither constructor nor class, it is called a factory function. Function inheritance works by creating an object from a factory function and extending the object by adding attributes directly to it (using concatenation inheritance). The concept of function inheritance was first introduced by Douglas Crockford, but it has been around for a long time in JavaScript.
As you’ll see, concatenated inheritance is the secret to JavaScript’s ability to implement object composition, and it also makes prototype proxy and function inheritance more colorful.
When most people think of JavaScript object-oriented design, the first thing they think of is a prototype proxy. But you see, it’s not just prototype agents. To replace class inheritance, the stereotype proxy has to take a back seat to object composition.
* Why does object composition avoid weak base class problems
To understand this, we need to know how fragile base classes are formed:
-
Suppose there is A base class A;
-
Class B inherits from base class A;
-
Class C inherits from B;
-
Class D also inherits from B;
Call the super method in C, which executes the code in class B. Similarly, B calls the super method, which executes the code in A.
C and D need to inherit some unrelated features from A and B. At this point, D, as A new use case, needs to inherit some features from A’s initialization code that are slightly different from C’s. In response to these requirements, novice developers will adjust A’s initialization code. As a result, although D works, the original properties of C are broken.
In the example above,A
andB
forC
andD
Provides various features. However,C
andD
Don’t need to come fromA
andB
They just need to inherit certain properties. However, through inheritance and invocationsuper
Methods, you can’t selectively inherit, only all of them:
“The problem with object-oriented languages is that subclasses carry information about the environment implied by their parents. You want a banana, but you end up with a gorilla with a banana, and the jungle. “– Joe Armstrong, Programming life
If you use object composition, consider the following features:
feat1, feat2, feat3, feat4
Copy the code
C needs features Feat1 and Feat3, while D needs features Feat1, Feat2, Feat4:
const C = compose(feat1, feat3);
const D = compose(feat1, feat2, feat4);
Copy the code
Suppose you find that D needs slightly different features than Feat1. There is no need to change Feat1. By creating a customized version of Feat1, you can maintain feat2 and feat4 features without affecting C, as follows:
const D = compose(custom1, feat2, feat4);
Copy the code
Such flexibility is an advantage that class inheritance does not offer. Because when a subclass inherits, it takes the entire class inheritance structure with it.
In this case, to adapt to the new use case, either duplicate the existing class layer partition (the inevitable repetition problem) or refactor the existing class layer structure, which again leads to the fragile base class problem.
With object composition, both problems can be solved.
Do you really understand the prototype?
Creating classes and constructors and then inheriting them is not true prototype inheritance, but a way of using prototypes to simulate class inheritance. Here are some common misconceptions about inheritance in JavaScript for your reference.
The class inheritance pattern in JavaScript has a long history and is built on the flexible and rich archetypal inheritance feature (as in ES6 and beyond). But once you use class inheritance, you lose the flexibility and power of archetypes. All the problems with class inheritance will always be there.
Using class inheritance in JavaScript is a trivial exercise.
Stamps: combinable factory functions
In most cases, object composition is achieved by using factory functions: factory functions are responsible for creating object instances. What if factory functions could be combined? Check out Stamp’s document to find out.
(Translator’s Note: I feel that the original expression is not very happy. So I took the liberty of drawing two diagrams for the reader to understand. Please understand and correct the deficiencies.) Figure: Class inheritance
Description: From the figure, we can see the problems of single inheritance relationship, tight coupling and hierarchical classification directly; Class 8, which only wants to inherit the properties of the pentagon, gets other properties on the inheritance chain that are not needed — the gorilla/banana problem. Class 9 only needs to change the pentacle property to a quadrangle, resulting in the need to modify base class 1, which affects the entire inheritance tree — weak base class/hierarchy rigidity problem; Otherwise, you need to create a new base class for 9 — the inevitable repeatability problem. Figure: Prototype inheritance/object composition
Description: The use of prototype inheritance/object combination, can avoid complex depth of hierarchy. When 1 requires the quadrangle feature, it only needs to combine the new feature and does not affect other instances.