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
orundefined
. - 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.