When I first started learning about JavaScript, I couldn’t help but notice how different it interacts with objects and entities compared to other languages that support object-oriented programming like C++ and Java, and how JavaScript is always categorized as an OOP Language by many. However, it has a different nature, as it is a special type of OOP that is prototype-based not the classical class-based OOP that we are mostly familiar with. That means that JS supports both functional and object-oriented programming; and that's why it handles objects and entities in a slightly different way.
However, despite the fact that TypeScript is a superset of JavaScript, it offers several advantages over its predecessor, particularly when it comes to object-oriented programming. In this article, we will explore how TypeScript has enhanced OOP capabilities in comparison to JavaScript, and how it conforms to the SOLID principles with code examples.
Table Of Contents
TypeScript Key Features
First : Strongly Typed Language
One of the most significant advantages of TypeScript over JavaScript is that it is a strongly typed language. This means that TS enforces types on variables, functions, and classes, ensuring that the code is more robust and less error-prone. In contrast, JavaScript is a dynamically typed language, which means that variables can change their types at runtime, leading to more bugs and less maintainable code.
// JavaScript
function add(a, b) {
return a + b;
}
// TypeScript
function add(a: number, b: number): number {
return a + b;
}
In the above example, the TypeScript function add has types for its parameters (a and b) and return value. This makes it easier to understand the function's behavior and catch errors at compile-time.
Second : Classes and Interfaces
Classes allow you to define blueprints for objects with properties and methods, while interfaces define the structure and types of objects. These features are mostly not available in JavaScript, which uses prototype-based inheritance instead of class-based inheritance.
Classes
According to MDN, the JS class implementation is introduced in 2015, in ES6. It is based on the notation that classes are just special functions
Classes are in fact "special functions", and just as you can define function expressions and function declarations, a class can be defined in two ways: a class expression or a class declaration.
Constructor Function in JavaScript
// JavaScript
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
}
Since TypeScript is a subset of JavaScript, under the hood, it implements classes the same way JS does. However, due to TS strongly typed nature, there are several properties added on top of JS that enhances readability, maintainability and reduces errors. In other words, TypeScript has full support for that syntax and also adds features on top of it, like member visibility, abstract classes, generic classes, arrow function methods, and a few others.
Classes in JavaScript
// JavaScript
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
Classes in TypeScript
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): void {
console.log(`${this.name} makes a noise.`);
}
}
As you can see, there isn't a big difference in the general syntax between JS and TS when it comes to classes. However TypeScript Classes can be considered more readable, maintainable and less error prune. As we go through the key features of TypeScript, we will explore how TypeScript has full support for the class-based OOP.
Interfaces
JavaScript does not have support for interfaces, as it's inheritance is based on objects and not classes and due to it's dynamic nature. Moreover, JavaScript uses duck typing.
If it walks like a duck, and quacks like a duck, as far as JS cares, it’s a duck.
On the other hand, TypeScript has support for interfaces. interface
and type
are how TS can enforce structure on objects, making sure that objects have the expected properties.
TypeScript interfaces supports interface extending and adding fields to an existing interface
// JavaScript
function printLabel(labelObj) {
console.log(labelObj.label);
}
// TypeScript
interface LabelObj {
label: string;
}
function printLabel(labelObj: LabelObj): void {
console.log(labelObj.label);
}
printLabel({ label:"name" })
In the above example, the JavaScript
printLabel
function takes an object with alabel
property. In TypeScript, aninterface
is defined for this object to ensure that it has alabel
property of type string. This makes it easier to catch errors at compile-time and write more maintainable code.
Type Aliases
Types are another example of how Typescript enforces object structure.
According to TypeScript Documentation :
Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable
Here's an example
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
interface Animal {
favFood: string
}
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
interface Animal {
favFood: string
}
// Error: Duplicate identifier 'Animal'.
Third : Access Modifiers
TypeScript also offers access modifiers, which allow you to restrict access to certain properties and methods of a class. Access modifiers like private and protected help to enforce encapsulation and prevent unauthorized access to the internal state of an object. JavaScript does not have access modifiers, making it more difficult to achieve encapsulation and maintain a clean code base.
Here is an example of how TypeScript access modifiers can help with encapsulation:
// JavaScript
function Person(age) {
This.age =age;
}
Person.prototype.getAge = function() {
Return this.age
}
Let's say that you want Person
to be a class with a a private property called age
. You want to create a method called getAge()
that returns the value of the age
property, but you don't want to allow direct access to the age
property from outside the class.
// TypeScript
class Person {
private age: number;
constructor(age: number) {
this.age = age;
}
public get getAge(): number {
return this.age;
}
}
By using access modifiers like private
, public
, and protected
, you can control access to the internal state of an object, ensuring that it is only modified in a controlled manner. This helps to maintain encapsulation, which is an essential principle of OOP that promotes modularity, maintainability, and robustness.
Moreover, TypeScript has two additional access modifiers than the popular three mentioned above: static
and readonly
-
static
properties: they can only be accessed from the class, not from an object instantiated from it and they can not be inherited. -
readonly
properties: these properties can not be modified after initialization, they can only be read. They must be set at the class declaration or inside the constructor
The example shows how the TS uses access modifiers inside the class. Note that this
keyword can not be used to reference static properties and methods.
//TypeScript
class Employee {
private name: string;
readonly ssn : string;
private static count: number = 0;
constructor(name:string, ssn:string){
this.name = name
this.ssn = ssn
Employee.count =+1;
}
public getName():string {
return this.name;
}
public static getCount():number {
return Employee.count;
}
public getSSN():string{
return this.ssn;
}
};
let emp:Employee = new Employee("Mike", "836527496");
//emp.count; // 'count' does not exist on type 'Employee'
//emp.ssn=5; // Cannot assign to 'ssn' because it is a read-only property
Now as mentioned before, JS class
support was introduced in ES6, and with it some features of classes:
static
keywordprivate access modifier using
#
notationget
andset
keywords
Here's an example demonstrating the added features:
//JavaScript
class Time {
#hour = 0
#minute = 0
#second = 0
static #GMT = 0
constructor(hour, minute, second) {
this.#hour = hour
this.#minute = minute
this.#second = second
}
static get displayGMT() {
return Time.#GMT
}
static set setGMT(gmt){
Time.#GMT=gmt
}
get getHour(){
return this.#hour
}
set setHour(hour){
this.#hour=hour
}
}
//console.log(Time.getHour()) // Error: Time.getHour is not a function
console.log(Time.getHour) //undefined
console.log(Time.displayGMT) //0
Time.setGMT = 2
myTime = new Time()
console.log(Time.#hour) //Error: reference to undeclared private field or method #hour
console.log(myTime.#hour) //Error: reference to undeclared private field or method #hour
console.log(Time.displayGMT) //2
Despite having these features, their behavior is quite confusing to grasp at the first glance, moreover it's readability is less than it's TypeScript equivalent.
//TypeScript
class Time {
private hour: number = 0
private minute: number = 0
private second: number = 0
private static GMT: number = 0
constructor(hour: number, minute: number, second: number) {
this.hour = hour
this.minute = minute
this.second = second
}
static get displayGMT(): number {
return Time.GMT
}
static set setGMT(gmt: number) {
Time.GMT = gmt
}
get getHour(): number {
return this.hour
}
set setHour(hour: number) {
this.hour = hour
}
}
//console.log(Time.getHour()) //Property 'getHour' does not exist on type 'typeof Time'.
console.log(Time.displayGMT) //0
Time.setGMT = 2
//myTime = new Time() //Expected 3 arguments, but got 0.
const myTime: Time = new Time(1, 2, 3)
//console.log(Time.hour) //Property 'hour' does not exist on type 'typeof Time'
//console.log(myTime.hour) //Property 'hour' is private and only accessible within class 'Time'.
console.log(Time.displayGMT) //2
Fourth : Inheritance and Polymorphism
TypeScript supports inheritance and polymorphism, two essential concepts in OOP. Inheritance allows you to create a hierarchy of classes, with each subclass inheriting properties and methods from its parent class.
Polymorphism allows you to use a subclass object in place of a parent class object, making the code more flexible and reusable. JavaScript does support inheritance and polymorphism, but it uses a less intuitive and more verbose syntax.
// JavaScript
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
}
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
console.log(`${this.name} barks.`);
}
In the JavaScript example, inheritance is implemented using prototype-based inheritance, which can be difficult to read and maintain.
// TypeScript
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): void {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
speak(): void {
console.log(`${this.name} barks.`);
}
}
In TypeScript, inheritance is implemented using the extends keyword, which makes the code more readable and easier to maintain.
With the introduction of classes in ES6, extends
keyword became supported as well with class
implementations.
Here's an example:
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(`${this.name} roars.`);
}
}
const myLion = new Lion("Fuzzy");
// Fuzzy makes a noise.
myLion.speak(); // Fuzzy roars.
Fifth : Abstract Classes
TypeScript supports abstract classes, which are classes that cannot be instantiated directly. Abstract classes can only be used as base classes for other classes. This allows developers to define common functionality for a group of related classes without having to implement the functionality in each class.
Here's an example:
// JavaScript
function Vehicle()
{
this.vehicleName="vehicleName";
throw new Error("You cannot create an instance of Abstract Class");
}
Vehicle.prototype.display=function()
{
return "Vehicle is: "+this.vehicleName;
}
//Creating a constructor function
function Bike(vehicleName)
{
this.vehicleName=vehicleName;
}
//Creating object without using the function constructor
Bike.prototype=Object.create(Vehicle.prototype);
There is no direct JS Abstract class implementation, however there are ways/ workarounds that can be implemented to apply abstraction. Here in the JavaScript example above, we created a constructor function that throws an error if the constructor is called. And when it comes to the subclass
Bike
, using it’s prototype and accessing the Object create function to set it to the base class prototype.
// TypeScript
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract speak(): void;
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
speak(): void {
console.log(`${this.name} barks.`);
}
}
class Cat extends Animal {
constructor(name: string) {
super(name);
}
speak(): void {
console.log(`${this.name} meows.`);
}
}
Unlike JavaScript, Typescript offers easy, direct and readable class abstraction implementation.
Sixth : Generics
TypeScript also supports generics, which allow you to create reusable code that can work with different types of data. Generics are especially useful when working with collections like arrays, where you may want to define a function or class that can work with any type of data. JavaScript does not have generics, making it more difficult to write generic code.
Here's a simple example of generics in TS:
// TypeScript
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("Hello");
let output2 = identity<number>(42);
However, this example can easily be created in JS with similar results, thus doesn’t really show why Generics are important, why their absence from JavaScript makes it difficult to write reusable strongly typed code.
Here's how :
// JavaScript
function identity(arg) {
return arg;
}
let output1 = identity("Hello");
let output2 = identity(42);
Now let's see their power in the example below:
//JavaScript
function getArray(arr) {
return new Array().concat(arr);
}
let myNumArr = getArray([10, 20, 30]);
let myStrArr = getArray(["Hello", "JavaScript"]);
myNumArr.push(40); // Correct
myNumArr.push("Hello OOP"); // Correct
myStrArr.push("Hello TypeScript"); // Correct
myStrArr.push(40); // Correct
console.log(myNumArr); // [10, 20, 30, 40, "Hello OOP"]
console.log(myStrArr); // ["Hello", "JavaScript", "Hello TypeScript", 40]
In the above example, the getArray()
function accepts an array. The getArray()
function creates a new array, concatenates items to it and returns this new array. Since JavaScript doesn’t define data type enforcement, we can pass any type of items to the function. But, this may not be the correct way to add items. We have to add numbers to myNumArr
and the strings to myStrArr
, but we do not want to add numbers to the myStrArr
or vice-versa.
To solve this, TypeScript introduced generics. With generics, the type variable only accepts the particular type that the user provides at declaration.
Here's the TypeScript to the previous example:
//TypeScript
function getArray<T>(arr: T[]): T[] {
return new Array<T>().concat(arr);
}
let myNumArr = getArray<number>([10, 20, 30]);
let myStrArr = getArray<string>(["Hello", "JavaScript"]);
myNumArr.push(40); // Correct
myNumArr.push("Hi! OOP"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
myStrArr.push("Hello TypeScript"); // Correct
myStrArr.push(50); // Error: Argument of type 'number' is not assignable to parameter of type 'string'
console.log(myNumArr);
console.log(myStrArr);
Seventh : Intersection and Union Types
TypeScript supports intersection and union types, which are a way to combine types to create new types. Intersection types allow developers to create types that have all the properties and methods of two or more types. Union types allow developers to create types that can be one of two or more types.
Here's an example:
// TypeScript
interface Dog {
name: string;
breed: string;
}
interface Cat {
name: string;
color: string;
}
type Pet = Dog & Cat; // Intersection
type DogOrCat = Dog | Cat; // Union
let myPet: Pet = { name: "Fluffy", breed: "Poodle", color: "White" };
// Error: Property 'color' is missing in type 'Pet' but required in type 'Cat'.
let myDogOrCat: DogOrCat = { name: "Mittens", breed: "Tabby" };
In the above example, the
Dog
andCat
interfaces are defined with different properties. ThePet
type is defined as an intersection of theDog
andCat
interfaces, which means it has all the properties of both interfaces. TheDogOrCat
type is defined as a union of theDog
andCat
interfaces, which means it can be either a dog or a cat. ThemyPet
variable is assigned an object that has all the properties of both theDog
andCat
interfaces, but TypeScript throws a compiler error because the color property is missing. ThemyDogOrCat
variable is assigned an object that has the properties of theDog
interface.
Note : It's not possible to define intersection and union types in JavaScript
Eighth : Decorators
TypeScript supports decorators, which are a way to add metadata to classes, methods, and properties. Decorators allow developers to write code that is more declarative and flexible.
Note: Several libraries and frameworks are built based on this powerful feature, for example: Angular and Nestjs.
Here is a simple method decorator example to demonstrate the syntax:
function myDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`Decorating ${target.constructor.name}.${propertyKey}`);
}
class MyClass {
@myDecorator
myMethod() {
console.log("Hello, world!");
}
}
In the above example, the
myDecorator
function is defined with three parameters:
target
(the class constructor)propertyKey
(the name of the decorated method)descriptor
(an object that describes the decorated method).The
myMethod
method of theMyClass
class is decorated with themyDecorator
function. When themyMethod
method is called, the decorator logs a message to the console.
Now let’s review a more advanced example that shows an actual decorator use case, this example uses parameter decorator and method decorator
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata( requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
let method = descriptor.value!;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
console.log("paramas", requiredParameters)
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}
class BugReport {
type = "report";
title: "string;"
constructor(t: string) {
this.title = t;
}
@validate
print(@required verbose: boolean) {
if (verbose) {
return `type: ${this.type}\ntitle: "${this.title}`;"
} else {
return this.title;
}
}
}
const br = new BugReport("Bug Report Message")
console.log(br.print()) // [ERR]: Missing required argument.
console.log(br.print(true)) // [LOG]: "type: report title: "Bug Report Message\""
The
@required
decorator adds a metadata entry that marks the parameter as required. The@validate
decorator then wraps the existinggreet
method in a function that validates the arguments before invoking the original method.
It’s important to mention that decorators, while useful and revolutionary, are an experimental feature in TypeScript. To use them you need to set the compiler target in tsconfig.ts
to ES5 or higher and enable the experimentalDecorators
, moreover, in the previous example you will also need to install the reflect-metadata
library and enable the emitDecoratorMetadata
in the tsconfig.ts
file as well
Note
I highly suggest that you check out the full documentation and article on decorators for more detailed explanations and examples. To do so, follow these links:
SOLID Principles
SOLID is an acronym for five Object-Oriented design principles that aim to make software systems more maintainable, extensible, and robust. These principles are:
- S - Single Responsibility Principle (SRP)
- O - Open-Closed Principle (OCP)
- L - Liskov Substitution Principle (LSP)
- I - Interface Segregation Principle (ISP)
- D - Dependency Inversion Principle (DIP)
Let's see how TypeScript conforms to these principles with code examples.
First : Single Responsibility Principle (SRP)
The SRP states that a class should have only one reason to change. In other words, a class should be responsible for only one thing. This principle helps to promote modularity, maintainability, and testability.
In TypeScript, you can use classes and modules to achieve the SRP.
Here's an example:
// Account.ts
export class Account {
private balance: number;
constructor(balance: number) {
this.balance = balance;
}
public getBalance(): number {
return this.balance;
}
public deposit(amount: number): void {
this.balance += amount;
}
public withdraw(amount: number): void {
if (amount > this.balance) {
throw new Error('Insufficient funds');
}
this.balance -= amount;
}
}
// Bank.ts
import { Account } from './Account';
export class Bank {
private accounts: Account[];
constructor() {
this.accounts = [];
}
public openAccount(balance: number): Account {
const account = new Account(balance);
this.accounts.push(account);
return account;
}
public closeAccount(account: Account): void {
const index = this.accounts.indexOf(account);
if (index === -1) {
throw new Error('Account not found');
}
this.accounts.splice(index, 1);
}
}
In this example, the
Account
class is responsible for managing an account's balance, while theBank
class is responsible for managing a collection of accounts. By separating these responsibilities into different classes, we ensure that each class has only one reason to change, making the code more modular, maintainable, and testable.
Second : Open-Closed Principle (OCP)
The OCP states that a class should be open for extension but closed for modification. In other words, you should be able to add new functionality to a class without modifying its existing code. This principle helps to promote extensibility and maintainability.
In TypeScript, you can use inheritance and interfaces to achieve the OCP.
Here's an example:
// Shape.ts
export interface Shape {
getArea(): number;
}
// Rectangle.ts
import { Shape } from './Shape';
export class Rectangle implements Shape {
private width: number;
private height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
public getArea(): number {
return this.width * this.height;
}
}
// Circle.ts
import { Shape } from './Shape';
export class Circle implements Shape {
private radius: number;
constructor(radius: number) {
this.radius = radius;
}
public getArea(): number {
return Math.PI * this.radius ** 2;
}
}
In this example, the
Shape
interface defines a contract for shapes that have an area. TheRectangle
andCircle
classes implement theShape
interface, providing their own implementations of thegetArea()
method. By using theShape
interface, we can add new shapes to the system without modifying the existing code, making the code more extensible and maintainable.
Third : Liskov Substitution Principle (LSP)
The LSP states that a subclass should be substitutable for its parent class without affecting the correctness of the program. In other words, you should be able to use a subclass object in place of a parent class object without causing any problems. This principle helps to promote polymorphism and extensibility.
In TypeScript, you can use inheritance and interfaces to achieve the LSP
Here's an example:
// Vehicle.ts
export class Vehicle {
public startEngine(): void {
console.log('Engine started');
}
public stopEngine(): void {
console.log('Engine stopped');
}
}
// Car.ts
import { Vehicle } from './Vehicle';
export class Car extends Vehicle {
public drive(): void {
console.log('Driving');
}
}
// RaceCar.ts
import { Car } from './Car';
export class RaceCar extends Car {
public drive(): void {
console.log('Driving fast
In this example, the
Vehicle
class defines a basic set of methods for a vehicle, including starting and stopping the engine. TheCar
class extends theVehicle
class, adding adrive()
method that is specific to cars. TheRaceCar
class extends the Car class, adding adrive()
method that is specific to race cars.
By using inheritance and the LSP, we can use a RaceCar
object in place of a Vehicle
or Car
object without affecting the correctness of the program. This promotes polymorphism and extensibility, making the code more flexible and reusable.
Fourth : Interface Segregation Principle (ISP)
The ISP states that a client should not be forced to depend on methods it does not use. In other words, you should design interfaces that are specific to each client's needs, rather than creating a monolithic interface that includes methods for all clients. This principle helps to promote modularity and maintainability.
In TypeScript, you can use interfaces to achieve the ISP.
Here's an example:
// Database.ts
export interface Database {
connect(): void;
disconnect(): void;
query(sql: string): any[];
}
// SqliteDatabase.ts
import { Database } from './Database';
export class SqliteDatabase implements Database {
public connect(): void {
console.log('Connecting to SQLite database');
}
public disconnect(): void {
console.log('Disconnecting from SQLite database');
}
public query(sql: string): any[] {
console.log(`Executing SQL query: ${sql}`);
return [];
}
}
// RedisDatabase.ts
import { Database } from './Database';
export class RedisDatabase implements Database {
public connect(): void {
console.log('Connecting to Redis database');
}
public disconnect(): void {
console.log('Disconnecting from Redis database');
}
public query(sql: string): any[] {
throw new Error('Redis does not support SQL queries');
}
}
In this example, the
Database
interface defines a contract for databases that can connect, disconnect, and execute SQL queries. TheSqliteDatabase
andRedisDatabase
classes implement theDatabase
interface, providing their own implementations of the methods.
By using interfaces and the ISP, we can create specific interfaces for each client's needs, rather than creating a monolithic interface that includes methods for all clients. This promotes modularity and maintainability, making the code more flexible and reusable.
Fifth :Dependency Inversion Principle (DIP)
The DIP states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. In other words, you should design your system so that high-level modules can be easily swapped out with other modules that implement the same abstractions. This principle helps to promote extensibility and maintainability.
In TypeScript, you can use inversion of control (IoC) and dependency injection (DI) to achieve the DIP.
Here's an example:
// Logger.ts
export interface Logger {
log(message: string): void;
}
// ConsoleLogger.ts
import { Logger } from './Logger';
export class ConsoleLogger implements Logger {
public log(message: string): void {
console.log(message);
}
}
// Service.ts
import { Logger } from './Logger';
export class Service {
private logger: Logger;
constructor(logger: Logger) {
this.logger = logger;
}
public doSomething(): void {
this.logger.log('Doing something');
}
}
In this example, the
Logger
interface defines a contract for loggers that can log messages. TheConsoleLogger
class implements theLogger
interface, providing its own implementation of thelog()
method. TheService
class depends on theLogger
interface, rather than depending on a specific logger implementation. This allows us to easily swap out theConsoleLogger
with another logger implementation that implements the sameLogger
interface.
By using IoC and DI, we can design our system so that high-level modules depend on abstractions rather than low-level modules, promoting extensibility and maintainability.
So .. Who Wins?
TypeScript has enhanced OOP capabilities than JavaScript, particularly when it comes to adhering to the SOLID principles.
By adhering to these principles, you can create code that is more modular, maintainable, and robust.
In addition to adhering to the SOLID principles, TypeScript also provides several key features that make it a powerful tool for building complex software systems. These features make TypeScript a powerful tool for building complex software systems, particularly those that rely heavily on OOP principles.
By using TypeScript, you can create code that is more reliable, maintainable, and extensible, which can save you time and money in the long run. Moreover, TypeScript provides a powerful set of tools for creating and managing complex software systems, making it an excellent choice for building large-scale applications.
References
Feel free to check out the references for more details and points of view
- https://www.dotnettricks.com/learn/typescript/modules-namespaces
- https://www.tutorialsteacher.com/typescript/typescript-namespace
- https://www.typescriptlang.org/docs/handbook/
- https://mirone.me/a-complete-guide-to-typescript-decorator/
- https://blog.logrocket.com/typescript-mixins-examples-and-use-cases/
- https://birdeatsbug.com/blog/object-oriented-programming-in-typescript
- https://github.com/jafari-dev/oop-expert-with-typescript
- https://levelup.gitconnected.com/typescript-object-oriented-concepts-in-a-nutshell-cb2fdeeffe6e
- https://medium.com/@aliafsah1988/how-to-properly-use-typescript-oop-and-solid-in-modern-web-applications-e4ef88761154
- https://medium.com/@baruahd5/object-oriented-programming-oops-using-typescript-cdbffd7d0cf5
- https://dev.to/kevin_odongo35/object-oriented-programming-with-typescript-574o
- https://betterprogramming.pub/understand-object-oriented-programming-with-typescript-c4ff8afa40d
- https://css-tricks.com/the-flavors-of-object-oriented-programming-in-javascript/
- https://www.freecodecamp.org/news/object-oriented-programming-javascript/
- https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_programming
- https://javascript.plainenglish.io/why-javascript-is-called-prototype-based-e9326562bf43
- https://www.javatpoint.com/typescript-generics
- https://www.freecodecamp.org/news/typescript-generics-use-case-example/
- https://www.javatpoint.com/javascript-oops-abstraction
- https://medium.com/swlh/typescript-doesnt-turn-javascript-into-a-class-based-oop-language-b31aae6114e3
- https://www.digitalocean.com/community/tutorials/how-to-use-classes-in-typescript
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#defining_getters_and_setters
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
- https://blog.bitsrc.io/understanding-public-and-private-fields-in-javascript-class-a281bf930566
- https://connorsmyth.com/implementing-interface-in-javascript/
- https://en.wikipedia.org/wiki/Duck_typing
- https://www.typescripttutorial.net/typescript-tutorial/typescript-getters-setters/
- https://www.javascripttutorial.net/es6/javascript-getters-and-setters/
Top comments (0)