Polymorphism is a very important concept in object-oriented programming paradigm. In a nutshell, it means one object having many forms. For example, if the class Student
inherits Person
class, then Student
has all the properties and methods Person
. Hence, an instance of Student
also has the type of Person
since it has all the fields an instance Person
would have.
Polymorphism is very important in a statically typed language. It lets you substitute values basic on this polymorphic principle. For example, a function that takes an argument of type Person
would also accept a value of type Student
. The same principle applies to the function return value.
This might sound familiar to you if you read the lesson on the Type System in TypeScript. In this article, we discussed structural typing and how TypeScript analyzes the shape of an object to infer types. If object A
has all the properties of B
, then A
has the type of B
(implicitly).
JavaScript is not typed languaged. Hence having polymorphism in the language doesn’t solve any major type-level problems. However, if you are familiar with instanceof
operator in JavaScript, it checks if an object is an instance of a class. Let’s see how we can use it in JavaScript.
The above program is a simple JavaScript program. We have a Student
class that extends Person
class. The printName
function takes an argument which could be anything since it’s JavaScript, we can statically analyze the code. But we can check if the person
argument is an instance of the class Person
.
As you can see from the result, ross
and monica
is an instead of Person
. It’s quite obvious for ross
since it’s an instance of Person
but for monica
, since it is an instance of Student
which extends Person
class, hence technically monica
is an instance of Person
. However, jack
is not an instance of Person
since it wasn’t constructed from the Person
or Student
class despite having the same object fields (shape).
What we learned here is that though JavaScript lacks types, polymorphism principle still exists in JavaScript and it can be useful in scenarios like these.
Interface-based Polymorphism
TypeScript is a completely different world and comparing it with JavaScript runtime would not be fair to JavaScript. In TypeScript, you have types to play with. Not only you have primitive and abstract data types that JavaScript also supports, but you can have custom types of your own such as interfaces, unions, enums, and whatnot.
As we have learned from the Interfaces lesson, an interface
describes the shapes of an object. And from the Type System lesson, we have learned that TypeScript compares types of the values based on their shape. Let’s combine these two principles together.
In the above example, we have a Person
interface with name
and getName
fields. The Student
interface has the exact same field Person
interface has and an extra marks
field. We could have used extends
keyword to inherit Person
interface inside Student
interface.
The printName
function accepts an argument of type Person
interface which means this argument expects an object with name
and getName
fields with types described by the Person
interface.
The ross
object has a type of Person
interface, hence it must contain exact same fields described by the Person
interface. The same goes for the monica
object of the type Student
interface since it extends the Person
. However, jack
is a plain object, hence it defines its own implicit interface.
As we can see from the result, the above program compiles and runs just fine. This is due to the fact that TypeScript’s type system is based on the shape of the values and not on what concrete type values haves. This is basically what structural typing means. Technically, concrete types do not exist in TypeScript, it is just a type system and not a language.
Hence, the printName
function accepted both monica
and jack
as a valid argument since they have all the properties as described by the Person
interface. This is very hard to implement in JavaScript since shape-based polymorphism doesn’t exist in JavaScript. TypeScript FTW!
Class-based Polymorphism
We have already seen an example of class-based polymorphism in JavaScript using the instanceof
keyword. This check is not necessary in TypeScript since TypeScript won’t allow a value to be treated as an instance of a class if it doesn’t have the exact same properties described by the class.
In the above example, the printName
function accepts an argument of type Person
. As we learned from the classes lesson, a class in TypeScript defines an implicit interface, hence the value of the argument person
is a type Person
as long as the shape of that value has all the instance fields of Person
. Here, the instanceof
keyword is not used as one would have hoped.
Therefore, monica
is a Person
since Student
class extends the Person
class and jack
is also a Person
since it has all the instance fields described by the Person
class. The judy
object, however, is not a Person
since it lacks the getFullName
method.
The takeaway from this lesson is that everything in TypeScript is based on the shape of a value. Be it a unit type (_like _*'hello'*
), collective type (_like _*'string'*
), union type (_like _*string | number*
), or interface types, everything boils down to the shape of the value.