When should you use inheritance?
Should you use class inheritance or prototype inheritance?
As soon as you need it! 浪 Jokes aside, it can be difficult to know when to take advantage of inheritance. Unfortunately, the vast majority of examples that explain inheritance are contrived. Yes, we understand that Dog is an Animal that’s different from Cat but this doesn’t help us with real problems. So, let’s consider a more realistic problem: sharing utility functions.
Now we can take advantage of our complete() method from Task. We’re using inheritance! 拾 This is a very simple example.
Inheritance is most useful for sharing logic across similar or related data types. Usually you will have data to represent in your app and common methods for manipulating it. Often, these methods will be needed for different types of data and you can use inheritance to share them. This is done with the extends keyword.
When you extend a class, you are creating a super-sub relationship between classes. Our “super” class is Task and our “sub” class is Project. There’s a clear, one-way hierarchy. Sometimes this isn’t ideal and you need a more loosely defined relationship between objects. So what other option do you have? You can use prototypal inheritance!
Should you use class inheritance or prototypal inheritance?
You’ll notice that we aren’t using class at all. Yet, we get a similar result as before. This is done by using Object.create. This function will use the provided object as the prototype for a new object. What is a prototype? It’s the object that is delegated to when you try to use a property that your object doesn’t have. In our example, task doesn’t have a property complete() so it delegates to completion for that function call. In effect, this works the same way as class inheritance.
We’re inheriting from the task object which sets title and isComplete for us. The task object inherits from completion which provides the complete() behavior. The key difference here is that completion is not bound to either Task or Project directly. It can be used in other objects you create that need the same behavior.
You should use prototypal inheritance directly when you need to compose your functionality and data. You should use classes when you need to strictly colocate your data and functionality. Inheritance is possible in both situations, but the source of inheritance is different. In this case, it’s neither the journey nor the destination, but the origin that you should be thinking about.
With the addition of Object.assign we can compose our prototype with multiple objects. This enables reusablility, better separation of concerns, and doesn’t impose hierarchy on our data model. But there are drawbacks!
The object returned from Task() will be merged into priority which affects all references to priority. The order of arguments makes all the difference. Inheriting behavior from multiple objects is powerful but can be easily misused.
Sometimes class inheritance is the better way to go. Classes can provide more structure and predictability. Prototypes provide more flexibility. This is because the prototype interface is the building block upon which classes are created.