Adding methods to a class
In JavaScript is quite straightforward. Methods are essentially functions that are defined within a class. Here's a basic example to demonstrate this:
Example of a Simple Class with Methods
In this example, the `Person
` class has two methods: `greet()
` and `celebrateBirthday()`
. These methods can be called on instances of the `Person
` class, such as `alice
`.
Adding Static Methods
In addition to instance methods, you can also add static methods to a class. Static methods are called on the class itself, not on instances of the class.
```javascript
class Person {
// ... constructor and other methods ...
static species() {
return 'Homo sapiens';
}
}
console.log(Person.species());
// Outputs: Homo sapiens
```
In this case, `species` is a static method of the `Person` class.
Adding Methods Outside the Class Definition
You can also add methods to a class outside of its initial definition using the prototype:
```javascript
Person.prototype.sayGoodbye = function() {
return `Goodbye from ${this.name}!`;
};
const bob = new Person('Bob', 28);
console.log(bob.sayGoodbye());
// Outputs: Goodbye from Bob!
```
In this example, `sayGoodbye
` is added to the `Person` class after its initial definition. This method is now available to all instances of the `Person` class.
These examples cover the basic ways you can add methods to a class in JavaScript.
Metaprogramming
Refers to the practice of writing programs that have the ability to treat other programs as their data. It means that such programs can analyze, modify, and even create other programs. JavaScript's support for metaprogramming is quite extensive, especially with the introduction of features like Symbols in ES6.
Symbols in JavaScript
Symbols are a new primitive type introduced in ES6. They are unique and immutable and can be used as identifiers for object properties. The primary purpose of symbols is to serve as keys for object properties that are unique and cannot collide with any other properties in the object. This uniqueness makes Symbols particularly useful in metaprogramming scenarios.
Simple Example of Symbols
Let's consider a real-world scenario where you're working with a third-party object, and you need to add new properties to it without the risk of property name collisions. Symbols are perfect for this.
```
javascript
let user = {
name: 'John Doe',
age: 30
};
// Suppose you want to add a unique ID to this user object without altering existing properties
const uniqueID = Symbol('userID');
user[uniqueID] = '12345';
console.log(user[uniqueID]);
// Outputs: 12345
```
In this example,
userID
is a unique property that won't collide with any other property names in theuser
object, even if they are named 'userID'.
Symbols and Metaprogramming
Symbols enable a form of metaprogramming by allowing you to add properties or methods to objects in a way that doesn't interfere with their existing structure. This is especially useful in situations where you need to extend or modify objects that you don't control, like those from a library or framework.
Well-Known Symbols
JavaScript provides "well-known" symbols which offer various metaprogramming features. For example, `
Symbol.iterator
` is used to customize the iteration behavior of objects.
In this case, Symbol.iterator
is used to make the collection object iterable. The for...of
loop can now directly iterate over collection, yielding each item in the items array.
Without
Symbol.iterator :
If we don't useSymbol.iterator
and want to iterate over theitems
array directly, it would look like this:
Notes for practice :
1. Difference between a parameter in the constructor and a property of a class instance.
Key Difference
_title
and_author
are constructor parameters, used for initializing the object. They serve as a way to pass information into the object at the time of its creation.title
andauthor
are the properties of theBook
instances. Once the object is created, these properties hold the state of the object and can be interacted with directly.
This separation is fundamental in object-oriented programming, allowing constructors to set up the initial state of an object, while the object's properties define its current state.
Verify the type of attributes during object creation:
To verify the type of attributes during object creation in JavaScript, you can implement checks in the constructor of your class. These checks typically involve using the typeof
operator or other type-checking methods to ensure the provided arguments match the expected data types. If they don't, you can throw an error or handle it appropriately.
Here's an example of how you might do this in a class:
Example: Verifying Attribute Types in a Class
Note: verify the type of attributes during object creation to be array of strings :
To verify that an attribute is an array of strings during object creation in JavaScript, you can use a combination of Array.isArray()
to check if the attribute is an array, and Array.prototype.every()
along with typeof
to confirm that every element in the array is a string. Here's an example to illustrate this:
Example: Verifying an Array of Strings
class Classroom {
constructor(students) {
if (!Array.isArray(students) || !students.every(student => typeof student === 'string')) {
throw new TypeError('Students must be an array of strings');
}
this.students = students;
}
}
Abstract Class:
An abstract class in programming is a class that cannot be instantiated on its own and is typically used as a base class. It defines a template for other classes to inherit from and often includes abstract methods, which are methods declared in the abstract class but must be implemented by its subclasses.
Characteristics of Abstract Classes:
Cannot Be Instantiated: You cannot create an instance of an abstract class directly. It's meant to be a blueprint for other classes.
Inheritance: They are used for inheritance. Subclasses that inherit from an abstract class must implement the abstract methods and can use the common functionality defined in the abstract class.
Abstract Methods: These are methods declared in the abstract class but do not have their implementation. Instead, they must be implemented by any subclass that inherits the abstract class.
Common Functionality: Abstract classes can include implemented methods (with actual functionality) which can be shared by all subclasses.
Abstract Classes in JavaScript:
JavaScript does not have native support for declaring a class as abstract directly. However, you can simulate abstract classes by throwing an error in the constructor of what you intend to be an abstract class:

If new.target is equal to AbstractAnimal, it means that AbstractAnimal's constructor was called directly to create an instance (e.g., new AbstractAnimal()).
Super:
In object-oriented programming, particularly in languages like JavaScript, the super
keyword is used in the context of class inheritance. It serves two main purposes:
To Call the Constructor of the Parent Class: In a subclass, you use
super
to call the constructor of the parent class. This is necessary when you want to ensure that the parent class is properly initialized before doing any subclass-specific initialization. This is often the first line in the constructor of a subclass.To Access Parent Class Methods:
super
can also be used to access methods on a parent class that might have been overridden in the subclass. This allows you to extend or reuse the functionality of parent class methods within subclass methods.
Using super
in Constructors
When you extend a class in JavaScript, the subclass constructor must call super()
before it can use this
. Here's an example:
How JavaScript handles type conversion for objects?
In JavaScript, the Number
and String
functions are global objects used for type conversion. When you pass an object to these functions, JavaScript internally calls certain methods on the object to perform the conversion:
Number(obj)
: When you callNumber(obj)
, JavaScript internally uses thevalueOf
method of the object to convert it to a numeric value. IfvalueOf
does not return a primitive,toString
is then tried.String(obj)
: Similarly, when you callString(obj)
, JavaScript first tries thetoString
method of the object to convert it to a string. IftoString
does not return a primitive,valueOf
is then tried.
Hint :
Remember, export default
is used when a module only exports a single thing (like a function or a class), while export
can be used to export multiple things.
Further Reading:
More on Symbol:
Using `Symbol` as a key for class properties in JavaScript provides several benefits related to encapsulation and property name collision avoidance. Here's why you might choose to do so:
1. Encapsulation and Privacy
JavaScript classes do not have true private fields or methods (though private class features are being introduced in newer ECMAScript versions). By using `Symbol`, you can create properties that are not easily accessible from outside the class, which mimics private property behavior. This helps in encapsulating the data, allowing you to control how it is accessed and modified.
2. Avoid Property Name Collisions
Symbols are unique and immutable. Using them as property keys ensures that these properties do not clash with other properties of the object, whether they are defined in the class itself, its prototypes, or even in extensions added by other scripts. This is particularly useful when working with large codebases or in library/framework development, where you might not have control over how objects are extended or used.
3. Intentional Property Hiding
Symbol-keyed properties do not show up in common object property enumerations. For example, when using `for...in` loops or `Object.keys()
`, symbol-keyed properties are not listed. This characteristic can be leveraged to keep certain properties hidden from standard property enumeration mechanisms, which can be useful in certain scenarios, such as when you want to avoid exposing internal properties or methods.
4. Semantics and Debugging
Symbols can have descriptions, which can be helpful during debugging. When you create a symbol, you can give it a descriptive name, like:
`const brandSymbol = Symbol('brand')
`.
This can make your code more readable and the debugging process more straightforward, as the purpose of the symbol is clearer.
In this example, `internalKey`
is a `Symbol` used to store a value in `MyClass
` instances. This value is not easily accessible from outside the class, enhancing data encapsulation and protection.
Thank you for sharing this, Senior ✨. Super simple, clear and to the point.🚀💫