7. Arrow Functions and Lexical this

In arrow functions, this is lexically scoped, meaning it inherits this from the surrounding context.

const person = {  name: \\\"Bob\\\",  greet: function () {    const arrowFunc = () => {      console.log(this.name); // \\'this\\' refers to the \\'person\\' object    };    arrowFunc();  },};person.greet(); // Output: Bob
  1. call, apply, and bind MethodsYou can explicitly set the value of this using call, apply, or bind.
// callfunction greet() {  console.log(`Hello, ${this.name}`);}const user = { name: \\\"John\\\" };greet.call(user); // Output: Hello, John// apply (similar to call but with arguments as an array):function introduce(greeting, age) {  console.log(`${greeting}, I\\'m ${this.name} and I\\'m ${age} years old.`);}const user = { name: \\\"Emily\\\" };introduce.apply(user, [\\\"Hi\\\", 25]); // Output: Hi, I\\'m Emily and I\\'m 25 years old.// bind (returns a new function with this bound):function sayName() {  console.log(this.name);}const user = { name: \\\"Lucy\\\" };const boundFunc = sayName.bind(user);boundFunc(); // Output: Lucy

Static Methods and Properties

Static methods and properties belong to the class itself rather than to instances of the class. Remember, we said earlier that whenever you instantiate a class, it is an instance of the class that is created, not the class itself. Based on this, we can say that static methods and properties are used to create methods and properties that are related to the class but not to any particular instance of the class.

class MathOperations {  static PI = 3.14159;  static square(x) {    return x * x;  }  static cube(x) {    return x * x * x;  }}// Accessing static properties and methods directly from the classconsole.log(MathOperations.PI); // 3.14159console.log(MathOperations.square(4)); // 16console.log(MathOperations.cube(3)); // 27// Accessing static properties and methods through an instance (This will not work)const mathOperations = new MathOperations();console.log(mathOperations.PI); // undefinedconsole.log(mathOperations.square(4)); // TypeError: mathOperations.square is not a functionconsole.log(mathOperations.cube(3)); // TypeError: mathOperations.cube is not a function

Private and Public Properties/Methods

JavaScript has several ways to implement private properties and methods:

Using Symbols

const _radius = Symbol(\\\"radius\\\");class Circle {  constructor(radius) {    this[_radius] = radius;  }  get area() {    return Math.PI * this[_radius] ** 2;  }}const circle = new Circle(5);console.log(circle.area); // 78.53981633974483console.log(circle[_radius]); // undefined (the property is private - can not be accessed outside the class)

Using WeakMaps

const _radius = new WeakMap();class Circle {  constructor(radius) {    _radius.set(this, radius);  }  get area() {    return Math.PI * _radius.get(this) ** 2;  }}const circle = new Circle(5);console.log(circle.area); // 78.53981633974483console.log(_radius.get(circle)); // 5

Using Private Fields (ES2022)

class Circle {  #radius;  constructor(radius) {    this.#radius = radius;  }  get area() {    return Math.PI * this.#radius ** 2;  }}const circle = new Circle(5);console.log(circle.area); // 78.53981633974483// console.log(circle.#radius); // SyntaxError

Getters and Setters

Getters and setters allow you to define object accessors (computed properties):

class Temperature {  constructor(celsius) {    this._celsius = celsius;  }  get fahrenheit() {    return (this._celsius * 9) / 5   32;  }  set fahrenheit(value) {    this._celsius = ((value - 32) * 5) / 9;  }  get celsius() {    return this._celsius;  }  set celsius(value) {    if (value < -273.15) {      throw new Error(\\\"Temperature below absolute zero is not possible\\\");    }    this._celsius = value;  }}const temp = new Temperature(25);console.log(temp.fahrenheit); // 77temp.fahrenheit = 86;console.log(temp.celsius); // 30

Polymorphism and Method Overriding

Polymorphism allows objects of different types to be treated as objects of a common parent class. Method overriding is a form of polymorphism where a subclass provides a specific implementation of a method that is already defined in its parent class.

class Shape {  area() {    return 0;  }  toString() {    return `Area: ${this.area()}`;  }}class Circle extends Shape {  constructor(radius) {    super();    this.radius = radius;  }  area() {    return Math.PI * this.radius ** 2;  }}class Rectangle extends Shape {  constructor(width, height) {    super();    this.width = width;    this.height = height;  }  area() {    return this.width * this.height;  }}const shapes = [new Circle(5), new Rectangle(4, 5)];shapes.forEach((shape) => {  console.log(shape.toString());});// Output:// Area: 78.53981633974483// Area: 20

Notice how both the Circle and Rectangle classes have a toString method (which we inherited from the Shape class - the parent class). However, the toString method in the Circle class overrides the toString method in the Shape class. This is an example of polymorphism and method overriding.

Object Freezing, Sealing, and Preventing Extensions

// Object.freeze() - Prevents adding, removing, or modifying propertiesconst frozenObj = Object.freeze({  prop: 42,});frozenObj.prop = 33; // Fails silently in non-strict modeconsole.log(frozenObj.prop); // 42// Object.seal() - Prevents adding new properties and marking existing properties as non-configurableconst sealedObj = Object.seal({  prop: 42,});sealedObj.prop = 33; // This workssealedObj.newProp = \\\"new\\\"; // This fails silently in non-strict modeconsole.log(sealedObj.prop); // 33console.log(sealedObj.newProp); // undefined// Object.preventExtensions() - Prevents adding new propertiesconst nonExtensibleObj = Object.preventExtensions({  prop: 42,});nonExtensibleObj.prop = 33; // This worksnonExtensibleObj.newProp = \\\"new\\\"; // This fails silently in non-strict modeconsole.log(nonExtensibleObj.prop); // 33console.log(nonExtensibleObj.newProp); // undefined

These methods are useful for creating immutable objects or preventing accidental modifications to objects.

Best Practices for Writing Clean OOP Code in JavaScript

  1. Use ES6 Classes: They provide a cleaner, more intuitive syntax for creating objects and implementing inheritance.

  2. Follow the Single Responsibility Principle: Each class should have a single, well-defined purpose.

// Good ✅class User {  constructor(name, email) {    this.name = name;    this.email = email;  }}class UserValidator {  static validateEmail(email) {    // Email validation logic  }}// Not so good ❌class User {  constructor(name, email) {    this.name = name;    this.email = email;  }  validateEmail() {    // Email validation logic  }}
  1. Use Composition Over Inheritance: Favor object composition over class inheritance when designing larger systems.
// Compositionclass Engine {  start() {    /* ... */  }}class Car {  constructor() {    this.engine = new Engine();  }  start() {    this.engine.start();  }}// Inheritanceclass Vehicle {  start() {    /* ... */  }}class Car extends Vehicle {  // ...}
  1. Implement Private Fields: Use the latest JavaScript features or closures to create truly private fields.

  2. Use Getters and Setters: They provide more control over how properties are accessed and modified.

  3. Avoid Overusing this: Use object destructuring in methods to make the code cleaner and less prone to errors.

class Rectangle {  constructor(width, height) {    this.width = width;    this.height = height;  }  area() {    const { width, height } = this;    return width * height;  }}
  1. Use Method Chaining: It can make your code more readable and concise.
class Calculator {  constructor() {    this.value = 0;  }  add(n) {    this.value  = n;    return this;  }  subtract(n) {    this.value -= n;    return this;  }  result() {    return this.value;  }}const calc = new Calculator();console.log(calc.add(5).subtract(2).result()); // 3
  1. Favor Declarative Over Imperative Programming: Use higher-order functions like map, filter, and reduce when working with collections.

  2. Use Static Methods Appropriately: Use static methods for utility functions that don\\'t require access to instance-specific data.

  3. Write Self-Documenting Code: Use clear, descriptive names for classes, methods, and properties. Add comments only when necessary to explain complex logic.

Small Project: Building a Library Management System

Let\\'s put our OOP knowledge into practice by building a simple Library Management System.

class Book {  constructor(title, author, isbn) {    this.title = title;    this.author = author;    this.isbn = isbn;    this.isAvailable = true;  }  checkout() {    if (this.isAvailable) {      this.isAvailable = false;      return true;    }    return false;  }  return() {    this.isAvailable = true;  }}class Library {  constructor() {    this.books = [];  }  addBook(book) {    this.books.push(book);  }  findBookByISBN(isbn) {    return this.books.find((book) => book.isbn === isbn);  }  checkoutBook(isbn) {    const book = this.findBookByISBN(isbn);    if (book) {      return book.checkout();    }    return false;  }  returnBook(isbn) {    const book = this.findBookByISBN(isbn);    if (book) {      book.return();      return true;    }    return false;  }  get availableBooks() {    return this.books.filter((book) => book.isAvailable);  }}// Usageconst library = new Library();library.addBook(  new Book(\\\"The Great Gatsby\\\", \\\"F. Scott Fitzgerald\\\", \\\"9780743273565\\\"));library.addBook(  new Book(\\\"To Kill a Mockingbird\\\", \\\"Harper Lee\\\", \\\"9780446310789\\\"));console.log(library.availableBooks.length); // 2library.checkoutBook(\\\"9780743273565\\\");console.log(library.availableBooks.length); // 1library.returnBook(\\\"9780743273565\\\");console.log(library.availableBooks.length); // 2

This project demonstrates the use of classes, encapsulation, methods, and properties in a real-world scenario.

Some Leetcode Problems on OOP

To further practice your OOP skills in JavaScript, try solving these problems:

  1. LeetCode: Design Parking System
  2. LeetCode: Design HashMap
  3. Codewars: Object Oriented Piracy

Conclusion

Object-Oriented Programming is a powerful paradigm that helps organize and structure code in a way that mirrors real-world objects and relationships. In this article, we\\'ve covered the fundamental concepts of OOP in JavaScript, from basic object creation to advanced topics like polymorphism and best practices.

Key takeaways:

As you continue your journey with OOP in JavaScript, remember that practice is key. Try to apply these concepts in your projects, refactor existing code to follow OOP principles, and don\\'t be afraid to explore advanced patterns and techniques.

References

For further reading and practice, check out these resources:

  1. MDN Web Docs: Object-oriented JavaScript
  2. JavaScript.info: Classes
  3. You Don\\'t Know JS: this & Object Prototypes
  4. Eloquent JavaScript: Chapter 6: The Secret Life of Objects

Remember, mastering OOP is a journey. Keep coding, keep learning, and most importantly, enjoy the process of creating robust and elegant object-oriented JavaScript applications!



Stay Updated and Connected

To ensure you don\\'t miss any part of this series and to connect with me for more in-depth discussions on Software Development (Web, Server, Mobile or Scraping / Automation), OOP, data structures and algorithms, and other exciting tech topics, follow me on:

Stay tuned and happy coding ?‍??

","image":"http://www.luping.net/uploads/20241017/1729167847671101e7b9d8e.jpg","datePublished":"2024-11-08T16:37:10+08:00","dateModified":"2024-11-08T16:37:10+08:00","author":{"@type":"Person","name":"luping.net","url":"https://www.luping.net/articlelist/0_1.html"}}
」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 物件導向程式設計:掌握 DSA 的第一步

物件導向程式設計:掌握 DSA 的第一步

發佈於2024-11-08
瀏覽:204

Imagine you're walking through a bustling factory. You see different machines, each designed for a specific purpose, working together to create a final product. Some machines are similar but with slight modifications to perform specialized tasks. There's a clear organization: each machine encapsulates its own functionality, yet they inherit common traits from their predecessors, and they can be easily replaced or upgraded without disrupting the entire production line.

This factory is a perfect analogy for Object-Oriented Programming (OOP). In the world of code, our objects are like these machines – self-contained units with specific purposes, inheriting traits, and working together to build complex applications. Just as a factory manager organizes machines for efficient production, OOP helps developers organize code for efficient, maintainable, and scalable software development.

Course Outline

In this article, we'll explore the intricate world of OOP in JavaScript in our pursuit of mastering data structures and algorithms, covering:

  1. What is OOP and why it matters
  2. Key concepts of OOP
    1. Encapsulation
    2. Inheritance
    3. Polymorphism
    4. Abstraction
  3. Objects and Classes in JavaScript
  4. Methods and Properties
  5. Constructor Functions and the new keyword
  6. this keyword and context in OOP
  7. Static methods and properties
  8. Private and public properties/methods (including symbols and weak maps)
  9. Getters and Setters
  10. Polymorphism and method overriding
  11. Object freezing, sealing, and preventing extensions
  12. Best practices for writing clean OOP code in JavaScript
  13. Small Project: Building a Library Management System
  14. Some Leetcode Problems on OOP
  15. Conclusion
  16. References


Let's dive in and start building our own code factory!

Object-Oriented Programming: Your First Step Toward Mastering DSA

What is OOP and Why It Matters

Object-Oriented Programming is a programming paradigm that organizes code into objects, which are instances of classes. These objects contain data in the form of properties and code in the form of methods. OOP provides a structure for programs, making them more organized, flexible, and easier to maintain.

To illustrate OOP, let's consider a real-world example: A Car. In OOP terms, we can think of a car as an object with properties (like color, model, year) and methods (like start, accelerate, brake). Here's how we might represent this in JavaScript:

class Car {
  constructor(color, model, year) {
    this.color = color;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`The ${this.color} ${this.model} is starting.`);
  }

  accelerate() {
    console.log(`The ${this.color} ${this.model} is accelerating.`);
  }

  brake() {
    console.log(`The ${this.color} ${this.model} is braking.`);
  }
}

const myCar = new Car("red", "Toyota", 2020);
myCar.start(); // The red Toyota is starting.
myCar.accelerate(); // The red Toyota is accelerating.
myCar.brake(); // The red Toyota is braking.

Why does OOP matter?

  1. Organization: OOP helps in organizing complex code into manageable, reusable structures.
  2. Modularity: Objects can be separated and maintained independently, making debugging and updating easier.
  3. Reusability: Once an object is created, it can be reused in different parts of the program or even in different programs.
  4. Scalability: OOP makes it easier to build and maintain larger applications.
  5. Real-world modeling: OOP concepts often align well with real-world objects and scenarios (just like our car example), making it intuitive to model complex systems.

Key Concepts of OOP

In OOP, there are four key concepts that we cannot ignore, they are:

1. Encapsulation

Encapsulation is the bundling of data and the methods that operate on that data within a single unit (object). It restricts direct access to some of an object's components, which is a means of preventing accidental interference and misuse of the methods and data.

Object-Oriented Programming: Your First Step Toward Mastering DSA

class BankAccount {
  #balance = 0; // Private field; it can only be accessed within the class
  // private balance = 0; // this is the same as #balance

  deposit(amount) {
    if (amount > 0) {
      this.#balance  = amount;
      console.log(`Deposited ${amount}. New balance: ${this.#balance}`);
    }
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
// console.log(account.#balance); // This would throw an error

In this example, #balance is a private field, encapsulated within the BankAccount class. It can only be accessed and modified through the class methods, ensuring data integrity.

2. Inheritance

Inheritance allows a class to inherit properties and methods from another class. This promotes code reuse and establishes a relationship between parent and child classes.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog("Buddy");
dog.speak(); // Outputs: Buddy barks.

Here, Dog inherits from Animal (since Animal is the parent class, meaning all Dog objects are also Animal objects with their own name property), reusing the name property and overriding the speak method.

3. Polymorphism

Polymorphism allows objects of different classes to be treated as objects of a common parent class. It enables the same interface to be used for different underlying forms (data types).

class Shape {
  area() {
    return 0;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

function printArea(shape) {
  console.log(`Area: ${shape.area()}`);
}

const circle = new Circle(5);
const rectangle = new Rectangle(4, 5);

printArea(circle); // Area: 78.53981633974483
printArea(rectangle); // Area: 20

In this example, printArea function can work with any shape that has an area method, demonstrating polymorphism. Since Circle and Rectangle are both shapes, they are expected to have an area method, though they may have different implementations.

4. Abstraction

Abstraction is the process of hiding complex implementation details and showing only the necessary features of an object. It helps in reducing programming complexity and effort.

class Vehicle {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }

  start() {
    return "Vehicle started";
  }

  stop() {
    return "Vehicle stopped";
  }
}

class Car extends Vehicle {
  start() {
    return `${this.make} ${this.model} engine started`;
  }
}

const myCar = new Car("Toyota", "Corolla");
console.log(myCar.start()); // Toyota Corolla engine started
console.log(myCar.stop()); // Vehicle stopped

Here, Vehicle provides an abstraction for different types of vehicles. The Car class uses this abstraction and provides its own implementation where needed.

Objects and Classes in JavaScript

In JavaScript, objects are standalone entities with properties and methods. Classes, introduced in ES6, provide a cleaner, more compact alternative to constructor functions and prototypes. Let's explore both approaches:

Objects

Objects can be created using object literals:

const person = {
  name: "John",
  age: 30,
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  },
};

console.log(person.name); // John
person.greet(); // Hello, my name is John

Classes

Classes are templates for creating objects. This means that they define the structure and behavior that all instances of the class will have. In other words, classes serve as blueprints for creating multiple objects with similar properties and methods. When you create an object from a class (using the new keyword), you're creating an instance of that class, which inherits all the properties and methods defined in the class.

Note: It is important to note that when you instantiate a class, the constructor method is called automatically. This method is used to initialize the object's properties. Also, it is just an instance that is been created when you use the new keyword.

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const john = new Person("John", 30);
john.greet(); // Hello, my name is John

Methods and Properties

Methods are functions that belong to an object, while properties are the object's data.

class Car {
  constructor(make, model) {
    this.make = make; // Property
    this.model = model; // Property
    this.speed = 0; // Property
  }

  // Method
  accelerate(amount) {
    this.speed  = amount;
    console.log(`${this.make} ${this.model} is now going ${this.speed} mph`);
  }

  // Method
  brake(amount) {
    this.speed = Math.max(0, this.speed - amount);
    console.log(`${this.make} ${this.model} slowed down to ${this.speed} mph`);
  }
}

const myCar = new Car("Tesla", "Model 3");
myCar.accelerate(50); // Tesla Model 3 is now going 50 mph
myCar.brake(20); // Tesla Model 3 slowed down to 30 mph

Constructor Functions and the new Keyword

Before ES6 classes, constructor functions were used to create objects:

function Person(name, age) {
  this.name = name;
  this.age = age;

  this.greet = function () {
    console.log(`Hello, my name is ${this.name}`);
  };
}

const john = new Person("John", 30);
john.greet(); // Hello, my name is John

The new keyword:

  1. Creates a new empty object
  2. Sets this to point to that object (the newly created object can now be accessed using the this keyword)
  3. Calls the constructor function to initialize the object
  4. Returns the object (implicitly)

this Keyword and Context in OOP

In JavaScript, this refers to the object that is executing the current function. Its value can change depending on how a function is called. Let's take a look at some examples:

1. Global Context

When used in the global context (outside any function or object), this refers to the global object (window in browsers or global in Node.js).

console.log(this); // In browsers, this will log the 'window' object

2. Inside an Object Method

When this is used inside a method of an object, it refers to the object that owns the method.

const person = {
  name: "Alice",
  sayHello: function () {
    console.log(this.name); // 'this' refers to the 'person' object
  },
};

person.sayHello(); // Output: Alice

3. Inside a Regular Function

In a regular function, this refers to the global object (window in browsers or global in Node.js), unless in strict mode (use strict), where this is undefined.

function showThis() {
  console.log(this); // 'this' refers to the global object in non-strict mode
}

showThis(); // In browsers, it logs the 'window' object

4. Inside a Constructor Function

When using a constructor function, this refers to the newly created object.

function Car(brand) {
  this.brand = brand;
}

const myCar = new Car("Toyota");
console.log(myCar.brand); // Output: Toyota

5. Inside a Class

When used in a class method, this refers to the instance of the class.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

const dog = new Animal("Dog");
dog.speak(); // Output: Dog makes a sound.

6. Using this in an Event Handler

In event handlers, this refers to the HTML element that received the event



7. Arrow Functions and Lexical this

In arrow functions, this is lexically scoped, meaning it inherits this from the surrounding context.

const person = {
  name: "Bob",
  greet: function () {
    const arrowFunc = () => {
      console.log(this.name); // 'this' refers to the 'person' object
    };
    arrowFunc();
  },
};

person.greet(); // Output: Bob
  1. call, apply, and bind Methods You can explicitly set the value of this using call, apply, or bind.
// call
function greet() {
  console.log(`Hello, ${this.name}`);
}

const user = { name: "John" };

greet.call(user); // Output: Hello, John

// apply (similar to call but with arguments as an array):
function introduce(greeting, age) {
  console.log(`${greeting}, I'm ${this.name} and I'm ${age} years old.`);
}

const user = { name: "Emily" };

introduce.apply(user, ["Hi", 25]); // Output: Hi, I'm Emily and I'm 25 years old.

// bind (returns a new function with this bound):
function sayName() {
  console.log(this.name);
}

const user = { name: "Lucy" };
const boundFunc = sayName.bind(user);

boundFunc(); // Output: Lucy

Static Methods and Properties

Static methods and properties belong to the class itself rather than to instances of the class. Remember, we said earlier that whenever you instantiate a class, it is an instance of the class that is created, not the class itself. Based on this, we can say that static methods and properties are used to create methods and properties that are related to the class but not to any particular instance of the class.

class MathOperations {
  static PI = 3.14159;

  static square(x) {
    return x * x;
  }

  static cube(x) {
    return x * x * x;
  }
}

// Accessing static properties and methods directly from the class
console.log(MathOperations.PI); // 3.14159
console.log(MathOperations.square(4)); // 16
console.log(MathOperations.cube(3)); // 27

// Accessing static properties and methods through an instance (This will not work)
const mathOperations = new MathOperations();
console.log(mathOperations.PI); // undefined
console.log(mathOperations.square(4)); // TypeError: mathOperations.square is not a function
console.log(mathOperations.cube(3)); // TypeError: mathOperations.cube is not a function

Private and Public Properties/Methods

JavaScript has several ways to implement private properties and methods:

Using Symbols

const _radius = Symbol("radius");

class Circle {
  constructor(radius) {
    this[_radius] = radius;
  }

  get area() {
    return Math.PI * this[_radius] ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.area); // 78.53981633974483
console.log(circle[_radius]); // undefined (the property is private - can not be accessed outside the class)

Using WeakMaps

const _radius = new WeakMap();

class Circle {
  constructor(radius) {
    _radius.set(this, radius);
  }

  get area() {
    return Math.PI * _radius.get(this) ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.area); // 78.53981633974483
console.log(_radius.get(circle)); // 5

Using Private Fields (ES2022)

class Circle {
  #radius;

  constructor(radius) {
    this.#radius = radius;
  }

  get area() {
    return Math.PI * this.#radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.area); // 78.53981633974483
// console.log(circle.#radius); // SyntaxError

Getters and Setters

Getters and setters allow you to define object accessors (computed properties):

class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }

  get fahrenheit() {
    return (this._celsius * 9) / 5   32;
  }

  set fahrenheit(value) {
    this._celsius = ((value - 32) * 5) / 9;
  }

  get celsius() {
    return this._celsius;
  }

  set celsius(value) {
    if (value 



Polymorphism and Method Overriding

Polymorphism allows objects of different types to be treated as objects of a common parent class. Method overriding is a form of polymorphism where a subclass provides a specific implementation of a method that is already defined in its parent class.

class Shape {
  area() {
    return 0;
  }

  toString() {
    return `Area: ${this.area()}`;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

const shapes = [new Circle(5), new Rectangle(4, 5)];

shapes.forEach((shape) => {
  console.log(shape.toString());
});

// Output:
// Area: 78.53981633974483
// Area: 20

Notice how both the Circle and Rectangle classes have a toString method (which we inherited from the Shape class - the parent class). However, the toString method in the Circle class overrides the toString method in the Shape class. This is an example of polymorphism and method overriding.

Object Freezing, Sealing, and Preventing Extensions

// Object.freeze() - Prevents adding, removing, or modifying properties
const frozenObj = Object.freeze({
  prop: 42,
});
frozenObj.prop = 33; // Fails silently in non-strict mode
console.log(frozenObj.prop); // 42

// Object.seal() - Prevents adding new properties and marking existing properties as non-configurable
const sealedObj = Object.seal({
  prop: 42,
});
sealedObj.prop = 33; // This works
sealedObj.newProp = "new"; // This fails silently in non-strict mode
console.log(sealedObj.prop); // 33
console.log(sealedObj.newProp); // undefined

// Object.preventExtensions() - Prevents adding new properties
const nonExtensibleObj = Object.preventExtensions({
  prop: 42,
});
nonExtensibleObj.prop = 33; // This works
nonExtensibleObj.newProp = "new"; // This fails silently in non-strict mode
console.log(nonExtensibleObj.prop); // 33
console.log(nonExtensibleObj.newProp); // undefined

These methods are useful for creating immutable objects or preventing accidental modifications to objects.

Best Practices for Writing Clean OOP Code in JavaScript

  1. Use ES6 Classes: They provide a cleaner, more intuitive syntax for creating objects and implementing inheritance.

  2. Follow the Single Responsibility Principle: Each class should have a single, well-defined purpose.

// Good ✅
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

class UserValidator {
  static validateEmail(email) {
    // Email validation logic
  }
}

// Not so good ❌
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  validateEmail() {
    // Email validation logic
  }
}
  1. Use Composition Over Inheritance: Favor object composition over class inheritance when designing larger systems.
// Composition
class Engine {
  start() {
    /* ... */
  }
}

class Car {
  constructor() {
    this.engine = new Engine();
  }

  start() {
    this.engine.start();
  }
}

// Inheritance
class Vehicle {
  start() {
    /* ... */
  }
}

class Car extends Vehicle {
  // ...
}
  1. Implement Private Fields: Use the latest JavaScript features or closures to create truly private fields.

  2. Use Getters and Setters: They provide more control over how properties are accessed and modified.

  3. Avoid Overusing this: Use object destructuring in methods to make the code cleaner and less prone to errors.

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  area() {
    const { width, height } = this;
    return width * height;
  }
}
  1. Use Method Chaining: It can make your code more readable and concise.
class Calculator {
  constructor() {
    this.value = 0;
  }

  add(n) {
    this.value  = n;
    return this;
  }

  subtract(n) {
    this.value -= n;
    return this;
  }

  result() {
    return this.value;
  }
}

const calc = new Calculator();
console.log(calc.add(5).subtract(2).result()); // 3
  1. Favor Declarative Over Imperative Programming: Use higher-order functions like map, filter, and reduce when working with collections.

  2. Use Static Methods Appropriately: Use static methods for utility functions that don't require access to instance-specific data.

  3. Write Self-Documenting Code: Use clear, descriptive names for classes, methods, and properties. Add comments only when necessary to explain complex logic.

Small Project: Building a Library Management System

Let's put our OOP knowledge into practice by building a simple Library Management System.

class Book {
  constructor(title, author, isbn) {
    this.title = title;
    this.author = author;
    this.isbn = isbn;
    this.isAvailable = true;
  }

  checkout() {
    if (this.isAvailable) {
      this.isAvailable = false;
      return true;
    }
    return false;
  }

  return() {
    this.isAvailable = true;
  }
}

class Library {
  constructor() {
    this.books = [];
  }

  addBook(book) {
    this.books.push(book);
  }

  findBookByISBN(isbn) {
    return this.books.find((book) => book.isbn === isbn);
  }

  checkoutBook(isbn) {
    const book = this.findBookByISBN(isbn);
    if (book) {
      return book.checkout();
    }
    return false;
  }

  returnBook(isbn) {
    const book = this.findBookByISBN(isbn);
    if (book) {
      book.return();
      return true;
    }
    return false;
  }

  get availableBooks() {
    return this.books.filter((book) => book.isAvailable);
  }
}

// Usage
const library = new Library();

library.addBook(
  new Book("The Great Gatsby", "F. Scott Fitzgerald", "9780743273565")
);
library.addBook(
  new Book("To Kill a Mockingbird", "Harper Lee", "9780446310789")
);

console.log(library.availableBooks.length); // 2

library.checkoutBook("9780743273565");

console.log(library.availableBooks.length); // 1

library.returnBook("9780743273565");

console.log(library.availableBooks.length); // 2

This project demonstrates the use of classes, encapsulation, methods, and properties in a real-world scenario.

Some Leetcode Problems on OOP

To further practice your OOP skills in JavaScript, try solving these problems:

  1. LeetCode: Design Parking System
  2. LeetCode: Design HashMap
  3. Codewars: Object Oriented Piracy

Conclusion

Object-Oriented Programming is a powerful paradigm that helps organize and structure code in a way that mirrors real-world objects and relationships. In this article, we've covered the fundamental concepts of OOP in JavaScript, from basic object creation to advanced topics like polymorphism and best practices.

Key takeaways:

  • OOP helps in creating modular, reusable, and maintainable code.
  • JavaScript provides multiple ways to implement OOP concepts, with ES6 classes offering a clean and intuitive syntax.
  • Principles like encapsulation, inheritance, polymorphism, and abstraction form the backbone of OOP.
  • Best practices, such as using composition over inheritance and following the single responsibility principle, can greatly improve code quality.

As you continue your journey with OOP in JavaScript, remember that practice is key. Try to apply these concepts in your projects, refactor existing code to follow OOP principles, and don't be afraid to explore advanced patterns and techniques.

References

For further reading and practice, check out these resources:

  1. MDN Web Docs: Object-oriented JavaScript
  2. JavaScript.info: Classes
  3. You Don't Know JS: this & Object Prototypes
  4. Eloquent JavaScript: Chapter 6: The Secret Life of Objects

Remember, mastering OOP is a journey. Keep coding, keep learning, and most importantly, enjoy the process of creating robust and elegant object-oriented JavaScript applications!



Stay Updated and Connected

To ensure you don't miss any part of this series and to connect with me for more in-depth discussions on Software Development (Web, Server, Mobile or Scraping / Automation), OOP, data structures and algorithms, and other exciting tech topics, follow me on:

  • GitHub
  • Linkedin
  • X (Twitter)

Stay tuned and happy coding ?‍??

版本聲明 本文轉載於:https://dev.to/emmanuelayinde/object-oriented-programming-your-first-step-toward-mastering-dsa-16a3?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何在 PHP 中根據與另一個 2D 數組的交集從 2D 數組過濾行?
    如何在 PHP 中根據與另一個 2D 數組的交集從 2D 數組過濾行?
    基於行交集過濾二維數組的行在PHP 中,array_diff_assoc() 函數旨在找出兩個數組之間的差異,同時對鍵值進行優先排序對。然而,當使用此函數根據與另一個 2D 數組的交集來過濾 2D 數組中的行時,它可能不會總是產生預期的結果。 理解問題問題的出現​​是由於由 array_diff_a...
    程式設計 發佈於2024-11-08
  • SQLRAG:利用自然語言和法學碩士轉變資料庫交互
    SQLRAG:利用自然語言和法學碩士轉變資料庫交互
    在資料驅動的世界中,速度和洞察力的可訪問性至關重要,SQLRAG 帶來了一種與資料庫互動的全新方法。透過利用大型語言模型 (LLM) 的強大功能,SQLRAG 使用戶能夠使用自然語言查詢資料庫,從而無需深厚的 SQL 知識。在這篇文章中,我們將深入探討 SQLRAG 的工作原理、其主要功能,以及它如...
    程式設計 發佈於2024-11-08
  • 哪些建置系統可以擴展 Go 的開發工作流程?
    哪些建置系統可以擴展 Go 的開發工作流程?
    Go 建置系統:擴展您的開發工作流程Go 是一種以其簡單性和併發性而聞名的程式語言,已獲得廣泛的認可。隨著開發專案的發展,對強大的建置系統來自動化建置、測試和部署流程的需求變得至關重要。但是哪些建置系統支援 Go 並增強其功能? Makefile:初始 Go 建置系統傳統上,Go 依賴與其原始碼發行...
    程式設計 發佈於2024-11-08
  • 如何在 JavaScript 中安全處理空值
    如何在 JavaScript 中安全處理空值
    JavaScript 中的空值檢查使用 JavaScript 時,正確處理「空」值至關重要。但是,標準空檢查可能並不總是按預期工作。讓我們探討原因並提供替代解決方案。 了解JavaScript 的Null Check在JavaScript 中,相等運算子(==) 和嚴格相等運算子(===)分別檢查值...
    程式設計 發佈於2024-11-08
  • 使用 AWS Lambda 為 Next.js 建置無伺服器後端
    使用 AWS Lambda 為 Next.js 建置無伺服器後端
    在不斷發展的 Web 開發世界中,利用無伺服器架構已經成為遊戲規則的改變者,尤其是對於 Next.js 應用程式而言。透過整合 AWS Lambda,開發人員可以建立可擴展且高效的後端,而無需管理伺服器的開銷。在這篇文章中,我們將探討如何使用 AWS Lambda 為您的 Next.js 應用程式...
    程式設計 發佈於2024-11-08
  • 當你開始學習程式語言時會發生什麼
    當你開始學習程式語言時會發生什麼
    在數位時代,學習程式語言不僅是一種優勢,而且是一種必要。無論您的目標是提升職業生涯、建立創新應用程序,還是只是更好地了解數位世界,程式設計技能都是不可或缺的。讓我們深入探討您應該踏上這趟變革之旅的原因和方法。 學習程式語言的重要性 職涯發展 根據美國勞工統計局的數據...
    程式設計 發佈於2024-11-08
  • 如何使用匿名結構或聯合編譯 C 程式碼?
    如何使用匿名結構或聯合編譯 C 程式碼?
    使用匿名結構/聯合編譯C 代碼出現了關於如何使用匿名結構或聯合編譯C 代碼的問題,如C 具有使用聯合的匿名欄位。在 C 中,嘗試使用包含匿名聯合的命名結構建立類似的結構會導致編譯錯誤。 錯誤訊息表示匿名聯合和結構欄位未在結構聲明中聲明。若要在 C 中啟用此功能,必須使用 -fms-extension...
    程式設計 發佈於2024-11-08
  • 如何使用 OpenSSL 和 C++ 產生 SHA256 雜湊值?
    如何使用 OpenSSL 和 C++ 產生 SHA256 雜湊值?
    使用 OpenSSL 和 C 產生 SHA256 雜湊 雜湊是一種加密技術,用於產生資料的唯一指紋或摘要。對於 SHA256(安全雜湊演算法 2,256 位元),此摘要是 256 位元十六進位字串。 SHA256 通常用於檢查資料完整性、驗證數位簽章和安全儲存密碼。 在本文中,我們將介紹如何使用 O...
    程式設計 發佈於2024-11-08
  • 探索軟體工程師的就業市場
    探索軟體工程師的就業市場
    Introduction In this article, we dive into the process of extracting and analyzing job data from LinkedIn, leveraging a combination of Python...
    程式設計 發佈於2024-11-08
  • 如何優化FastAPI中大數據的JSON回應效能?
    如何優化FastAPI中大數據的JSON回應效能?
    利用大數據提高 FastAPI 中 JSON 回應的效能FastAPI 用戶在透過端點傳回大量 JSON 資料時遇到嚴重延遲。全面的解決方案涉及解決多個因素,包括資料檢索、序列化和客戶端顯示。 資料提取和讀取如範例程式碼中突出顯示的,資料最初使用Pandas 的read_parquet() 函數從P...
    程式設計 發佈於2024-11-08
  • 在 React 中的選項卡之間發送資料。
    在 React 中的選項卡之間發送資料。
    本文將介紹如何在 React 全域元件之間發送數據,甚至在不同的瀏覽器標籤中也是如此。 故事 想像您有一個項目列表,例如用戶。 每個使用者都可以在模態視窗中開啟進行修改。 您沒有任何後端訂閱,這表示如果使用者發生變化,使用者清單不會自動與後端同步。 因此,一旦使用者的個人資料...
    程式設計 發佈於2024-11-08
  • 如何從 WPF 中的非調度程序執行緒修改 ObservableCollection?
    如何從 WPF 中的非調度程序執行緒修改 ObservableCollection?
    「這種類型的CollectionView 不支援從與調度程式執行緒不同的執行緒更改其SourceCollection」問題描述A DataGrid 綁定非同步填充的ObservableCollection 會拋出錯誤,指出不允許從非Dispatcher 執行緒對SourceCollection 進行...
    程式設計 發佈於2024-11-08
  • SQL Server 中的日期時間和時間戳記有什麼不同?
    SQL Server 中的日期時間和時間戳記有什麼不同?
    了解SQL Server 中日期時間和時間戳記之間的差異雖然SQL Server 中的日期時間和時間戳記資料型別都處理日期和時間,但它們表現出根本的區別。 Datetime 是專為儲存日期和時間資訊而設計的資料類型。它支援多種格式和日期/時間計算。另一方面,Timestamp 並不是用來儲存日期和時...
    程式設計 發佈於2024-11-08
  • 如何使用現代前端開發技術建立令人驚嘆的網站
    如何使用現代前端開發技術建立令人驚嘆的網站
    在当今的数字时代,网页设计在给访问者留下持久印象方面发挥着至关重要的作用。随着数以百万计的网站争夺注意力,创建一个令人惊叹的、脱颖而出的网站比以往任何时候都更加重要。现代前端开发技术彻底改变了网站的构建方式,使设计美观、实用且响应灵敏的网站变得更加容易,从而提供最佳的用户体验。本文将深入探讨可帮助您...
    程式設計 發佈於2024-11-08
  • 讓我們建立一個簡單的 React hook 來偵測瀏覽器及其功能
    讓我們建立一個簡單的 React hook 來偵測瀏覽器及其功能
    使用者代理嗅探是最受歡迎的瀏覽器偵測方法。不幸的是,由於多種原因,前端開發不太容易使用它。瀏覽器供應商不斷嘗試讓嗅探變得不可能。因此,每個瀏覽器都有自己的使用者代理字串格式,解析起來非常複雜。 有一個更簡單的方法可以使用瀏覽器 CSS API 實現相同的目的,我將向您展示。那麼讓我們建立瀏覽器功能...
    程式設計 發佈於2024-11-08

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3