Object Creation and Class Patterns in JavaScript
Objects are a fundamental part of JavaScript, providing a way to store and manipulate data in a structured manner. JavaScript is a prototype-based language, but with the introduction of ES6 classes, it has become easier to implement object-oriented programming patterns. In this lesson, we will explore different methods of creating objects, understand constructor functions, and learn about ES6 classes and inheritance.
Object Creation in JavaScript
Object Literals
- Definition: The simplest way to create an object using curly braces
{}
.
Example:
const person = {
name: 'Alice',
age: 30,
greet: function () {
console.log(`Hello, my name is ${this.name}.`)
},
}
person.greet() // Outputs: Hello, my name is Alice.
Explanation:
- The
person
object has propertiesname
,age
, and a methodgreet
.
Using Object.create()
- Definition: Creates a new object with the specified prototype object and properties.
Example:
const animal = {
eat: function () {
console.log(`${this.name} is eating.`)
},
}
const dog = Object.create(animal)
dog.name = 'Buddy'
dog.eat() // Outputs: Buddy is eating.
Explanation:
dog
is created withanimal
as its prototype.
Constructor Functions
- Definition: Functions used to create multiple similar objects.
Syntax:
function ConstructorFunction(parameters) {
// Initialization code
}
Example:
function Car(make, model) {
this.make = make
this.model = model
this.getInfo = function () {
return `${this.make} ${this.model}`
}
}
const myCar = new Car('Toyota', 'Corolla')
console.log(myCar.getInfo()) // Outputs: Toyota Corolla
Explanation:
Car
is a constructor function.- The
new
keyword creates a new object, setsthis
to that object, and returns it.
ES6 Classes
- Definition: Syntactic sugar over JavaScript's existing prototype-based inheritance.
Syntax:
class ClassName {
constructor(parameters) {
// Initialization code
}
// Methods
}
Example:
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
greet() {
console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`)
}
}
const bob = new Person('Bob', 25)
bob.greet() // Outputs: Hi, I'm Bob and I'm 25 years old.
Explanation:
Person
is a class with a constructor and a methodgreet
.
Constructor Functions in Detail
Understanding Constructor Functions
- Purpose: To create multiple objects with the same properties and methods.
- Convention: Constructor function names start with an uppercase letter.
How it works:
- When a function is invoked with
new
:- A new object is created.
- The
this
keyword is set to the new object. - The new object inherits from the constructor's
prototype
. - The constructor function code runs, potentially modifying
this
. - The new object is returned unless the constructor returns a non-primitive value.
Example:
function Circle(radius) {
this.radius = radius
}
Circle.prototype.area = function () {
return Math.PI * this.radius * this.radius
}
const circle = new Circle(5)
console.log(circle.area()) // Outputs: 78.53981633974483
Explanation:
- The
Circle
constructor initializes theradius
. - The
area
method is added toCircle.prototype
, so all instances share the same method.
Best Practices with Constructor Functions
- Always use the
new
keyword when calling a constructor function. - Define shared methods on the constructor's prototype, not within the constructor.
- Use
this
to assign properties within the constructor.
Avoiding Errors:
- Forgetting
new
can lead to unexpected results, asthis
would refer to the global object in non-strict mode orundefined
in strict mode.
Example without new
:
const circle = Circle(5) // Forgot 'new'
console.log(radius) // Outputs: 5 (in global scope)
Explanation:
- Without
new
,this.radius
assignsradius
to the global scope.
Solution:
- Enforce the use of
new
:
function Circle(radius) {
if (!(this instanceof Circle)) {
return new Circle(radius)
}
this.radius = radius
}
ES6 Classes and Inheritance
Defining Classes
- Classes are defined using the
class
keyword.
Syntax:
class ClassName {
constructor(parameters) {
// Initialization code
}
method1() {
// Method code
}
method2() {
// Method code
}
// Static methods
static staticMethod() {
// Static method code
}
}
Example:
class Rectangle {
constructor(width, height) {
this.width = width
this.height = height
}
getArea() {
return this.width * this.height
}
}
const rect = new Rectangle(10, 5)
console.log(rect.getArea()) // Outputs: 50
Explanation:
Rectangle
class has a constructor and a methodgetArea
.
Class Inheritance with extends
- Use
extends
to create a subclass that inherits from a parent class. - Use
super()
to call the parent class's constructor.
Example:
class Animal {
constructor(name) {
this.name = name
}
speak() {
console.log(`${this.name} makes a noise.`)
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name) // Call parent constructor
this.breed = breed
}
speak() {
console.log(`${this.name} barks.`)
}
}
const dog = new Dog('Buddy', 'Golden Retriever')
dog.speak() // Outputs: Buddy barks.
Explanation:
Dog
extendsAnimal
.- The
speak
method inDog
overrides the one inAnimal
.
Static Methods
- Defined using the
static
keyword. - Called on the class itself, not on instances.
Example:
class MathUtils {
static add(a, b) {
return a + b
}
}
console.log(MathUtils.add(2, 3)) // Outputs: 5
Explanation:
add
is a static method and is called directly onMathUtils
.
Getters and Setters
- Use
get
andset
to define getters and setters.
Example:
class Person {
constructor(name) {
this._name = name
}
get name() {
return this._name.toUpperCase()
}
set name(newName) {
this._name = newName
}
}
const person = new Person('Charlie')
console.log(person.name) // Outputs: CHARLIE
person.name = 'Dave'
console.log(person.name) // Outputs: DAVE
Explanation:
- The getter
name
returns the name in uppercase. - The setter
name
allows updating the name.
Private Fields (ES2021)
- Use
#
prefix to declare private fields.
Example:
class Counter {
#count = 0 // Private field
increment() {
this.#count++
console.log(`Count: ${this.#count}`)
}
}
const counter = new Counter()
counter.increment() // Outputs: Count: 1
// console.log(counter.#count); // SyntaxError: Private field '#count' must be declared in an enclosing class
Explanation:
#count
is not accessible outside the class.
Combining Constructor Functions and Prototypes
- Before ES6 classes, inheritance was achieved using constructor functions and manipulating prototypes.
Example:
function Vehicle(make) {
this.make = make
}
Vehicle.prototype.drive = function () {
console.log(`${this.make} is driving.`)
}
function Car(make, model) {
Vehicle.call(this, make) // Inherit properties
this.model = model
}
Car.prototype = Object.create(Vehicle.prototype) // Inherit methods
Car.prototype.constructor = Car // Set constructor reference
Car.prototype.honk = function () {
console.log(`${this.make} ${this.model} says beep beep!`)
}
const myCar = new Car('Toyota', 'Corolla')
myCar.drive() // Outputs: Toyota is driving.
myCar.honk() // Outputs: Toyota Corolla says beep beep!
Explanation:
Car
inherits fromVehicle
both properties and methods.
Advantages of ES6 Classes
- Cleaner Syntax: More readable and familiar to those from class-based OOP languages.
- Inheritance Simplification: Easier to implement inheritance with
extends
andsuper
. - Static Methods: Conveniently define methods on the class.
- Getters and Setters: Easily define property accessors.
- Private Fields: With the introduction of private fields, data encapsulation is improved.
Best Practices
Use ES6 Classes for Object-Oriented Programming
- Reason: Cleaner syntax, better readability, and modern features.
Name Classes and Constructors with UpperCamelCase
- Convention: Helps differentiate constructors and classes from regular functions.
Example:
class UserAccount {
// ...
}
Use super()
in Subclasses
- Reason: Ensure the parent class's constructor is called.
Define Methods on Prototypes
- For constructor functions, define shared methods on
ConstructorFunction.prototype
.
Avoid Extending Built-in Objects
- Reason: Can lead to unexpected behavior and conflicts.
Be Mindful of this
Binding
- In classes and constructor functions,
this
refers to the instance. - Arrow functions do not have their own
this
; be cautious when using them as methods.
Exercises
Exercise 1: Constructor Functions
Question:
Create a constructor function Book
that takes title
and author
as parameters. Add a method getDetails
to the prototype that returns a string with the book's details.
Answer:
function Book(title, author) {
this.title = title
this.author = author
}
Book.prototype.getDetails = function () {
return `${this.title} by ${this.author}`
}
const book = new Book('1984', 'George Orwell')
console.log(book.getDetails()) // Outputs: 1984 by George Orwell
Exercise 2: ES6 Class Inheritance
Question:
Create a class Shape
with a method area()
that returns 0
. Then, create a subclass Square
that extends Shape
and overrides the area()
method to calculate the area of the square.
Answer:
class Shape {
area() {
return 0
}
}
class Square extends Shape {
constructor(sideLength) {
super()
this.sideLength = sideLength
}
area() {
return this.sideLength * this.sideLength
}
}
const square = new Square(5)
console.log(square.area()) // Outputs: 25
Exercise 3: Using super()
Question:
Explain why the following code results in an error and fix it.
class Animal {
constructor(name) {
this.name = name
}
}
class Cat extends Animal {
constructor(name) {
this.type = 'Cat'
this.name = name
}
}
const kitty = new Cat('Whiskers')
console.log(kitty.name)
Answer:
Error:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Explanation:
- In a subclass constructor, you must call
super()
before accessingthis
.
Fixed Code:
class Animal {
constructor(name) {
this.name = name
}
}
class Cat extends Animal {
constructor(name) {
super(name)
this.type = 'Cat'
}
}
const kitty = new Cat('Whiskers')
console.log(kitty.name) // Outputs: Whiskers
Exercise 4: Private Fields
Question:
Implement a class BankAccount
with a private field #balance
. Provide methods to deposit
, withdraw
, and getBalance
, ensuring that the balance cannot be accessed or modified directly from outside the class.
Answer:
class BankAccount {
#balance = 0
deposit(amount) {
if (amount > 0) {
this.#balance += amount
console.log(`Deposited: $${amount}`)
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount
console.log(`Withdrew: $${amount}`)
} else {
console.log('Insufficient funds')
}
}
getBalance() {
console.log(`Balance: $${this.#balance}`)
}
}
const account = new BankAccount()
account.deposit(100) // Outputs: Deposited: $100
account.withdraw(30) // Outputs: Withdrew: $30
account.getBalance() // Outputs: Balance: $70
// console.log(account.#balance); // SyntaxError
Exercise 5: Static Methods
Question:
Create a class Calculator
with static methods add
, subtract
, multiply
, and divide
. These methods should perform the respective arithmetic operations.
Answer:
class Calculator {
static add(a, b) {
return a + b
}
static subtract(a, b) {
return a - b
}
static multiply(a, b) {
return a * b
}
static divide(a, b) {
if (b !== 0) {
return a / b
} else {
throw new Error('Division by zero')
}
}
}
console.log(Calculator.add(2, 3)) // Outputs: 5
console.log(Calculator.subtract(5, 2)) // Outputs: 3
console.log(Calculator.multiply(3, 4)) // Outputs: 12
console.log(Calculator.divide(10, 2)) // Outputs: 5
Understanding the various ways to create and work with objects in JavaScript, from constructor functions to modern ES6 classes, is essential for writing well-structured and maintainable code. By mastering these patterns and their nuances, you'll be better equipped to implement object-oriented programming concepts effectively and handle related technical interview questions with confidence.
Practice Problems
How does inheritance work in ES6 classes?
Loading...
What is the difference between a constructor function and a regular function in JavaScript?
Loading...
What are the advantages of using ES6 classes over constructor functions?
Loading...
Explain the role of `super()` in class inheritance.
Loading...
Can you explain how to implement private variables in ES6 classes?
Loading...
Let's continue exploring the next page. Take your time, and proceed when you're ready.