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
瀏覽:104

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]刪除
最新教學 更多>
  • 圖片在Chrome中為何仍有邊框? `border: none;`無效解決方案
    圖片在Chrome中為何仍有邊框? `border: none;`無效解決方案
    在chrome 中刪除一個頻繁的問題時,在與Chrome and IE9中的圖像一起工作時,遇到了一個頻繁的問題。和“邊境:無;”在CSS中。要解決此問題,請考慮以下方法: Chrome具有忽略“ border:none; none;”的已知錯誤,風格。要解決此問題,請使用以下CSS ID塊創建帶...
    程式設計 發佈於2025-04-29
  • Java 8中如何有效處理Lambda和Stream中的Checked Exception?
    Java 8中如何有效處理Lambda和Stream中的Checked Exception?
    [java 8 lambdas and streams and streams 在利用Java 8 lambdas and stream時處理檢查的異常。但是,與期望相反:的限制異常:A Potential Solution (Missed Opportunity):A more appropr...
    程式設計 發佈於2025-04-29
  • 如何將MySQL數據庫添加到Visual Studio 2012中的數據源對話框中?
    如何將MySQL數據庫添加到Visual Studio 2012中的數據源對話框中?
    在Visual Studio 2012 儘管已安裝了MySQL Connector v.6.5.4,但無法將MySQL數據庫添加到實體框架的“ DataSource對話框”中。為了解決這一問題,至關重要的是要了解MySQL連接器v.6.5.5及以後的6.6.x版本將提供MySQL的官方Visual...
    程式設計 發佈於2025-04-29
  • Python中何時用"try"而非"if"檢測變量值?
    Python中何時用"try"而非"if"檢測變量值?
    使用“ try“ vs.” if”來測試python 在python中的變量值,在某些情況下,您可能需要在處理之前檢查變量是否具有值。在使用“如果”或“ try”構建體之間決定。 “ if” constructs result = function() 如果結果: 對於結果: ...
    程式設計 發佈於2025-04-29
  • 使用JavaScript訪問MVC模型屬性方法
    使用JavaScript訪問MVC模型屬性方法
    在JavaScript中訪問MVC模型屬性 問題: 如何在JavaScript代碼中訪問綁定到視圖模型的數據?例如,如何在JavaScript中訪問FloorPlanSettingsModel的屬性? 最初嘗試: var floorplanSettings = "@Model.FloorPlan...
    程式設計 發佈於2025-04-29
  • 為什麼我的CSS背景圖像出現?
    為什麼我的CSS背景圖像出現?
    故障排除:CSS背景圖像未出現 ,您的背景圖像儘管遵循教程說明,但您的背景圖像仍未加載。圖像和样式表位於相同的目錄中,但背景仍然是空白的白色帆布。 而不是不棄用的,您已經使用了CSS樣式: bockent {背景:封閉圖像文件名:背景圖:url(nickcage.jpg); 如果您的html,cs...
    程式設計 發佈於2025-04-29
  • 大批
    大批
    [2 數組是對象,因此它們在JS中也具有方法。 切片(開始):在新數組中提取部分數組,而無需突變原始數組。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    程式設計 發佈於2025-04-29
  • Java數組中元素位置查找技巧
    Java數組中元素位置查找技巧
    在Java數組中檢索元素的位置 利用Java的反射API將數組轉換為列表中,允許您使用indexof方法。 (primitives)(鏈接到Mishax的解決方案) 用於排序陣列的數組此方法此方法返回元素的索引,如果發現了元素的索引,或一個負值,指示應放置元素的插入點。
    程式設計 發佈於2025-04-29
  • 為什麼不````''{margin:0; }`始終刪除CSS中的最高邊距?
    為什麼不````''{margin:0; }`始終刪除CSS中的最高邊距?
    在CSS 問題:不正確的代碼: 全球範圍將所有餘量重置為零,如提供的代碼所建議的,可能會導致意外的副作用。解決特定的保證金問題是更建議的。 例如,在提供的示例中,將以下代碼添加到CSS中,將解決餘量問題: body H1 { 保證金頂:-40px; } 此方法更精確,避免了由全局保證金重置...
    程式設計 發佈於2025-04-29
  • 表單刷新後如何防止重複提交?
    表單刷新後如何防止重複提交?
    在Web開發中預防重複提交 在表格提交後刷新頁面時,遇到重複提交的問題是常見的。要解決這個問題,請考慮以下方法: 想像一下具有這樣的代碼段,看起來像這樣的代碼段:)){ //數據庫操作... 迴聲“操作完成”; 死(); } ? > ...
    程式設計 發佈於2025-04-29
  • 解決MySQL錯誤1153:數據包超出'max_allowed_packet'限制
    解決MySQL錯誤1153:數據包超出'max_allowed_packet'限制
    mysql錯誤1153:故障排除比“ max_allowed_pa​​cket” bytes 更大的數據包,用於面對陰謀mysql錯誤1153,同時導入數據capase doft a Database dust?讓我們深入研究罪魁禍首並探索解決方案以糾正此問題。 理解錯誤此錯誤表明在導入過程中...
    程式設計 發佈於2025-04-29
  • 如何實時捕獲和流媒體以進行聊天機器人命令執行?
    如何實時捕獲和流媒體以進行聊天機器人命令執行?
    在開發能夠執行命令的chatbots的領域中,實時從命令執行實時捕獲Stdout,一個常見的需求是能夠檢索和顯示標準輸出(stdout)在cath cath cant cant cant cant cant cant cant cant interfaces in Chate cant inter...
    程式設計 發佈於2025-04-29
  • 如何使用PHP將斑點(圖像)正確插入MySQL?
    如何使用PHP將斑點(圖像)正確插入MySQL?
    essue VALUES('$this->image_id','file_get_contents($tmp_image)')";This code builds a string in PHP, but the function call fil...
    程式設計 發佈於2025-04-29
  • 如何從PHP中的數組中提取隨機元素?
    如何從PHP中的數組中提取隨機元素?
    從陣列中的隨機選擇,可以輕鬆從數組中獲取隨機項目。考慮以下數組:; 從此數組中檢索一個隨機項目,利用array_rand( array_rand()函數從數組返回一個隨機鍵。通過將$項目數組索引使用此鍵,我們可以從數組中訪問一個隨機元素。這種方法為選擇隨機項目提供了一種直接且可靠的方法。
    程式設計 發佈於2025-04-29
  • 在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在JTable中維護jtable單元格渲染後,在JTable中,在JTable中實現自定義單元格渲染和編輯功能可以增強用戶體驗。但是,至關重要的是要確保即使在編輯操作後也保留所需的格式。 在設置用於格式化“價格”列的“價格”列,用戶遇到的數字格式丟失的“價格”列的“價格”之後,問題在設置自定義單元...
    程式設計 發佈於2025-04-29

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

Copyright© 2022 湘ICP备2022001581号-3