Additional Features of Classes in JavaScript

Additional Features of Classes in JavaScript

In this article, we will explain the additional features of classes in JavaScript.

YouTube Video

Additional Features of Classes in JavaScript

Private Properties in JavaScript

In JavaScript, private properties are properties that are only accessible within the object or class. This enables more secure and robust code design by providing encapsulation, so that direct modification or reference from external code is not possible.

In ECMAScript 2020 (ES11), introduced in 2020, the use of # was introduced to define private fields within classes. This provides a clearer method than the traditional JavaScript private convention (e.g., variable names starting with an underscore). This provides a clearer way, replacing traditional JavaScript private conventions (such as variable names starting with an underscore).

How to Use Private Properties

Defining Private Properties in Classes

Private fields are defined using a name that starts with #. This field cannot be directly accessed from instances of the class or its subclasses.

 1class Person {
 2    // Define private property
 3    #name;
 4
 5    constructor(name) {
 6        this.#name = name;
 7    }
 8
 9    // Method to access the private property
10    getName() {
11        return this.#name;
12    }
13
14    // Method to change the private property
15    setName(newName) {
16        this.#name = newName;
17    }
18}
19
20const john = new Person("John");
21console.log(john.getName()); // John

In the above code, #name is a private property that cannot be directly accessed from outside the Person class. You can only access or change the name through the getName and setName methods.

1// Cannot access private property directly
2console.log(john.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class

Defining Private Methods

Like private properties, private methods are also defined using a name that starts with #. Private methods can only be called from within the class.

 1class Counter {
 2    #count = 0;
 3
 4    increment() {
 5        this.#count++;
 6        this.#logCount(); // Calling private method
 7    }
 8
 9    // Private method
10    #logCount() {
11        console.log(`Current count: ${this.#count}`);
12    }
13}
14
15const counter = new Counter();
16counter.increment(); // Current count: 1

Here, #logCount is defined as a private method and cannot be accessed from outside the class. This method is only used within the class.

1// Cannot access private method directly
2counter.#logCount(); // SyntaxError: Private field '#logCount' must be declared in an enclosing class

Advantages and Considerations of Private Properties

Advantage

  • Encapsulation: Hides internal state from the outside and maintains data consistency.
  • Security: Prevents properties from being unintentionally modified by external code.
  • Improved Maintainability: Hides the implementation of objects or classes and clarifies the interface exposed to the outside.

Notes

  • Because private fields are completely hidden from outside the class, testing and debugging can become difficult. Therefore, it is important to provide an API that can be thoroughly tested at the design stage.
  • Private fields behave differently from other parts of JavaScript with prototype-based characteristics because they are unique for each instance.

Traditional Pseudonymous Implementation of Private Properties

Prior to the introduction of private fields using #, JavaScript did not have an official syntax for private properties. Therefore, in the past, pseudo-private properties were implemented in the following ways.

Convention of using underscores

Developers conventionally indicated 'private' by prefixing variable names with an underscore.

 1class Car {
 2    constructor(brand) {
 3        this._brand = brand; // Using an underscore to indicate private
 4    }
 5
 6    getBrand() {
 7        return this._brand;
 8    }
 9}
10
11const car = new Car("Toyota");
12console.log(car.getBrand()); // Toyota
13console.log(car._brand); // Toyota (Accessible from outside)

This method is merely a 'convention,' and in practice, the properties can still be accessed from outside.

Implementation of private properties using closures

It's also possible to achieve private properties by using closures with function scope.

 1function createPerson(name) {
 2    let _name = name; // Private variable within function scope
 3
 4    return {
 5        getName: function() {
 6            return _name;
 7        },
 8        setName: function(newName) {
 9            _name = newName;
10        }
11    };
12}
13
14const person = createPerson("Alice");
15console.log(person.getName()); // Alice
16person.setName("Bob");
17console.log(person.getName()); // Bob
18
19// Cannot access directly from outside
20console.log(person._name); // undefined

With this method, the variable _name is enclosed within the function scope and cannot be directly accessed from outside.

Summary

Private properties in JavaScript are very effective in providing encapsulation in class and object design, helping to protect data safely. The # notation for private fields introduced in ES2020 provides a clearer and more secure method of privacy management compared to traditional conventions and closures.

Optional Chaining in JavaScript

Optional chaining is a very useful syntax in JavaScript for accessing deeply nested object properties. It enhances code readability and maintainability by allowing safe access without having to individually check for the existence of specific properties.

Basic syntax of Optional Chaining

Optional chaining can be used by placing a ? before the . or bracket notation used for property access. This notation returns undefined when a traversed property is null or undefined, allowing the program to continue processing safely without throwing an error.

Example:

 1const user = {
 2    name: 'John',
 3    address: {
 4        street: '123 Main St',
 5        city: 'New York'
 6    }
 7};
 8
 9// Without using optional chaining
10const city = user && user.address && user.address.city;
11console.log(city);  // New York
12
13// Using optional chaining
14const cityWithOptionalChaining = user?.address?.city;
15console.log(cityWithOptionalChaining);  // New York

In this example, we are accessing the address and its city property of the user object. Without using optional chaining, you would need to perform multiple existence checks, but with optional chaining, you can safely access the property with a single statement.

Using Optional Chaining with arrays and functions

Optional chaining is applicable not only to object properties, but also to array elements and function calls.

Example with arrays:

1const users = [{ name: 'Alice' }, { name: 'Bob' }];
2
3// Accessing the non-existent third element
4const thirdUser = users[2]?.name;
5console.log(thirdUser);  // undefined
  • In this way, you can also access array elements using optional chaining.

Example with functions:

 1const user = {
 2    greet: function() {
 3        return 'Hello!';
 4    }
 5};
 6
 7// Call the function only if greet exists
 8const greeting = user.greet?.();
 9console.log(greeting);  // Hello!
10
11// Return undefined if greet does not exist
12const nonExistentGreeting = user.nonExistentMethod?.();
13console.log(nonExistentGreeting);  // undefined
  • As shown in this example, it can also be applied to function calls.

Combining Optional Chaining with default values

When using optional chaining, it's common to use the logical OR (||) or nullish coalescing operator (??) to specify default values if a property does not exist.

Example:

 1const user = {
 2    name: 'John',
 3    address: {
 4        city: 'New York'
 5    }
 6};
 7
 8// Set a default value for a non-existent property
 9const state = user?.address?.state || 'Unknown';
10console.log(state);  // Unknown
11
12// Example using the nullish coalescing operator
13const zipCode = user?.address?.zipCode ?? '00000';
14console.log(zipCode);  // 00000

The logical OR operator treats values like false, 0, and '' as falsy, whereas the nullish coalescing operator uses the default value only when the value is null or undefined.

Benefits of Optional Chaining

  • Safe access to nested objects: By using optional chaining, you no longer need to perform explicit existence checks, making the code more concise.
  • Avoidance of errors: It can prevent runtime errors even if a property is null or undefined.
  • Improved readability and maintainability: Especially when dealing with many nested objects, the code's readability is greatly enhanced.

Cautions with Optional Chaining

  • Frequent use of optional chaining may be a sign that the data structure's design is overly complex. You should strive for a simple data model.
  • Since some older browsers and JavaScript engines do not support it, you may need to use polyfills or transpilers.

Conclusion

Optional chaining is a powerful feature that simplifies safe access to properties and functions of nested objects in JavaScript. It is particularly effective when accessing deeply nested data structures, contributing to error prevention and improved code readability.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video