Static Members and Modules

Using Static Members

In object-oriented programming, static members are properties or methods that belong to the class itself rather than individual instances of the class. They are accessed using the class name rather than an instance of the class. Static members are useful for defining utility methods or properties that are shared across all instances of the class.

We can use a static member in our Todo class to give each todo item a unique ID. We define a static property that keeps track of the last used ID. Let’s update the Todo class to include these static members:

class Todo {
  static #nextId = 1;
  #id;
  #text;
  #completed;

  constructor(text, completed = false) {
    this.#id = Todo.#nextId++;
    this.#text = text;
    this.#completed = completed;
  }

  toggle() {
    this.#completed = !this.#completed;
  }

  get id() {
    return this.#id;
  }

  get text() {
    return this.#text;
  }

  get completed() {
    return this.#completed;
  }

  set text(newText) {
    this.#text = newText;
  }
}

const todo1 = new Todo("Buy milk", true);
const todo2 = new Todo("Buy coffee");

console.log(todo1.id);  // 1
console.log(todo1.text);  // Buy milk
console.log(todo1.completed);  // true

console.log(todo2.id);  // 2
console.log(todo2.text);  // Buy coffee
console.log(todo2.completed);  // false

In this updated Todo class, we define a static private property #nextId to keep track of the next available ID for todo items. We initialize this property to 1. In the constructor method, we assign the value of #nextId to the #id property of the todo item and then increment #nextId by 1. Each todo item therefore gets a unique, auto-incrementing ID.

Exporting the Todo Class

To use the Todo class in other parts of our application, we need to export it from the todo.js file. Update the todo.js file to restore the set completed setter, remove the test code, and export the Todo class:

   set text(newText) {
     this.#text = newText;
   }
+
+  set completed(value) {
+    this.#completed = value;
+  }
 }

-const todo1 = new Todo("Buy milk", true);
-const todo2 = new Todo("Buy coffee");
-
-console.log(todo1.id); // 1
-console.log(todo1.text); // Buy milk
-console.log(todo1.completed); // true
-
-console.log(todo2.id); // 2
-console.log(todo2.text); // Buy coffee
-console.log(todo2.completed); // false
+export default Todo;

We use the export default syntax to export the Todo class from the todo.js file. This allows us to import the Todo class in other files using the import statement. For example, suppose we had a demo.js file in the same directory as the todo.js file. We could import the Todo class in the demo.js file as follows:

import Todo from "./todo.js";

const todo1 = new Todo("Buy milk", true);
const todo2 = new Todo("Buy coffee");

console.log(todo1.id);  // 1
console.log(todo1.text);  // Buy milk
console.log(todo1.completed);  // true

console.log(todo2.id);  // 2
console.log(todo2.text);  // Buy coffee
console.log(todo2.completed);  // false

Understanding ES6 Modules

The import and export statements used in the previous step are part of ES6 modules. ES6 modules were introduced in ECMAScript 2015 (ES6), the sixth major version of the ECMAScript standard. They are now fully supported in all modern browsers and Node.js.

With ES6 modules, each file is treated as a separate module. This means that the variables, functions, classes, and other objects defined in a module are not available in the global scope. Instead, we can export and import functions, objects, or primitives from one module (file) to another. This allows for better encapsulation and separation of concerns.

  • export: The export statement is used to export values from a module so they can be used in other modules with the import statement.

  • import: The import statement is used to bring in exports from another module. You specify the name of the exported item in curly braces { } and the path to the module file.

  • You can mark one of the exported values as the default export from a module. Another file then imports it with syntax such as import Todo from './Todo'.

Benefits:

  • Encapsulation: By using modules, we can hide the internal details of a module and expose only what is necessary. This is a core concept in software design.

  • Namespace Management: Modules help in avoiding naming collisions in the global namespace. Since each module has its own scope, the same name can exist in different modules without any conflict.

  • Reusability: Modules can be reused in different parts of the application, and in other applications.

Note on Paths: When importing modules, the path can be relative or absolute. In our case, we are using relative paths. The ./ at the beginning of the path indicates that the module is in the same directory as the current module.

Checkpoint: Commit your progress.

git add .
git commit -m "todos-17: Add static members and export Todo class"
git push