Objects in JavaScript are collections of key-value pairs, where values can be of any type, including other objects or functions.
# Object creation
JavaScript uses prototype-based inheritance, unlike class-based inheritance. Objects can be created directly without defining a class, and inheritance is achieved through prototype chains. This is more dynamic and flexible but can be less structured than traditional OOP languages.
JavaScript offers several ways to define and create objects. Prefer them in the following order:
1. **Object literal notation**
- Pros: Simple, good for one-off objects.
- Cons: Not reusable for multiple instances.
2. **Factory functions**
- Pros: Reusable, allows closures for private state.
- Cons: Each object has its own copy of methods.
3. **ES6 Classes**
- Pros: Familiar syntax, clear structure.
- Cons: May obscure JavaScript's prototypal behavior.
4. **Constructor functions**
- Pros: Efficient for methods, clear prototype chain.
- Cons: Can be confusing with 'this', easy to forget 'new'
5. **Object.create()**
- Pros: Direct control over prototype, no constructor needed.
- Cons: Less intuitive, not as widely used.
#### Object literals
- Use for simple, one-off objects.
- Avoid when need multiple instances or complex behavior.
```javascript
let person = {
name: "Alice",
greet: function() { console.log("Hello, I'm " + this.name); }
};
```
#### Factory functions
- Use for flexible object creation with private state and easy composition over inheritance.
- Avoid when you need to use `instaneof` or working with libraries that expect classes.
```javascript
function createPerson(name) {
return {
name,
greet() { console.log(`Hello, I'm ${name}`); }
};
}
```
#### ES6 Classes
- Use for familiar OOP structure or working with class-based frameworks.
- Avoid when requiring true privacy or flexible object composition.
>[!WARNING] JavaScript "classes"
JS does not have classes in the same sense as other OOP languages. The `class` keyword is simply syntactic sugar over constructor functions and JS's prototypal inheritance.
```javascript
class Person {
constructor(name) { this.name = name; }
greet() { console.log(`Hello, I'm ${this.name}`); }
}
```
#### Object.create()
- Use when need fine-grained control over prototype chain or advanced inheritance patterns.
- Avoid for everyday object creation due to its verbosity and potential for confusion.
```javascript
const personProto = {
greet() { console.log(`Hello, I'm ${this.name}`); }
};
const person = Object.create(personProto);
person.name = 'Alice';
```
#### Constructor functions (10% adoption, outdated / avoid)
- Use when working with older codebases.
- Avoid in modern code due to potential `new` keyword errors and better alternatives: classes or factory functions.
```javascript
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
}
const person = new Person('Amy');
```
## The object shorthand notation
The below is an example of object shorthand notation. Note that both `bobObject` and `alsoBobObject` will both have a name and age.
```javascript
const name = 'Bob';
const age = 28;
const bobObject = { name: name, age: age };
const alsoBobObject = { name, age };
```
## Destructuring
```javascript
const obj = { a: 1, b: 2};
const { a, b } = obj; // Creates two variables: a and b
const array = [1, 2, 3, 4, 5];
const [zerothElt, firstElt] = array; // Creates an array: [1, 2]
```
# Prototypal inheritance
JavaScript doesn't have a built-in class system. Instead, it uses a prototype-based inheritance model.
1. All objects in JavaScript have a prototype.
2. The prototype is another object.
3. ...that the original object inherits from, and has access to all of its prototype’s methods and properties.
When you try to access a property on an object, JavaScript first looks for that property on the object itself. If it doesn't find it, it looks on the object's prototype, then the prototype's prototype, and so on, forming what's called the prototype chain.
The prototype chain ultimately terminates at `Object.prototype`. This is the root prototype from which all objects in JavaScript inherit.
>[!NOTE] JavaScript prototypal inheritance vs Swift class inheritance
>Rather than an explicitly defined class hierarchy, JavaScript achieves inheritance through prototypal inheritance. Prototypal inheritance is more flexible, allowing for dynamic runtime changes to the prototype chain. However, this comes at the cost of predictability.
```javascript
let dog = new Dog();
dog.makeSound(); // "Woof!"
Dog.prototype.makeSound = function() { console.log("Bark!"); };
dog.makeSound(); // "Bark!"
```
##### Prefer Object.getPrototypeOf() over .\_\_proto__
`[[Prototype]]` is an internal property of objects in JavaScript, not directly accessible in code.
`.__proto__` is a deprecated accessor.
```javascript
console.log(Object.getPrototypeOf(person));
/*{
constructor: class Person
greet: ƒ greet()
[[Prototype]]: Object
} */
```
# Utilizing prototypal inheritance
For modern, advanced JavaScript developers, the preferred ways to utilize prototypal inheritance have evolved to favor cleaner, more intuitive syntax while still leveraging the power of JavaScript's prototype system.
While not strictly about inheritance, composition is often preferred over deep inheritance:
```javascript
const canSpeak = (state) => ({
speak: () => console.log(`${state.name} speaks.`)
});
const canRun = (state) => ({
run: () => console.log(`${state.name} runs.`)
});
function createAnimal(name) {
const state = { name };
return Object.assign({}, canSpeak(state), canRun(state));
}
const animal = createAnimal('Charlie');
animal.speak(); // "Charlie speaks."
animal.run(); // "Charlie runs."
```
Direct manipulation of prototypes (e.g., `Object.setPrototypeOf()`) is generally avoided in favor of these higher-level abstractions.
# Scope
Functions in JavaScript form closures. A closure refers to the combination of a function and the surrounding state in which the function was declared. This surrounding state, also called lexical environment, consists of any local variables that were in scope at the time the closure was made.
```javascript
let globalVar = "I'm a global variable, available everywhere"
function outerFunction() {
let functionVar = "Accessible only within outerFunction & its nested functions";
if (true) {
let blockVar = "I'm accessible only within this if block";
var fuctionScopedVar = "var declares function-scoped variables"
}
// Lexical scope: innerFunction() has access to variables in outerFunction()
function innerFunction() {
let innerVar = "I'm accessible only within innerFunction()";
}
innerFunction();
}
```