OOP
Everything you need to know about Object Oriented Programming (OOP) in JavaScript!
Classes
JavaScript uses classes (kinda)
JavaScript supports the creation and use of classes using the class
keyword. However, this is just syntactic sugar for prototypal inheritance and objects.
Here’s an example of a class in JavaScript:
class Human {
constructor(name) { // called on creation of the object
this.dna = 'AACTG';
this.name = name;
}
get gender() { // get grabs data
return this.gender;
}
set gender(val) { // set sets data
this.gender = val;
}
walk() { // method
console.log('walk');
}
static isHuman(human) { // static makes it global to the class name
if (human.dna == 'AACTG') {
return true;
}
}
}
Objects
An object is a collection of properties, and a property is an association between a name (or key) and a value. A property’s value can be a function, in which case the property is known as a method.
Anything that isn’t a primitive type is an object!
Objects can be created or defined in a few ways. Here are two examples:
const human = {} // '{}' -> object literal syntax
const human = new Object(); // 'new' -> constructor syntax
Here’s an example of an object holding a collection of key/value pairs or properties and values:
const human = {
dna: 'AACTG',
name: 'Ethan',
born: Date.now(),
walk() {
console.log('walking');
}
}
A common issue when creating apps is calling a property from within an undefined object. This can lead to app-breaking errors! To fix this, you can use something called optional chaining.
- If you’re ever not 100% certain that the object will be defined, use optional chaining.
Here’s an example of the syntax:
const human = undefined;
human?.name;
Putting a ?
before the period to call a property on an object will allow the object to return undefined without throwing an error.
This can be used while accessing items in an array or calling a function with arguments:
const arr = [1,2,3];
arr?.[0];
function foo(a,b) { }
foo?.(1,2);
Prototypes
A prototype refers to an object from which other objects inherit properties and methods. It’s like a blueprint or a template for creating similar objects.
- In very basic terms, a prototype in JavaScript refers to an object from which other objects inherit properties and methods. It’s like a blueprint or a template for creating similar objects.
- Every object in JavaScript is linked to a prototype object, which provides it with default properties and methods. If you try to access a property or method on an object and it doesn’t exist on the object itself, JavaScript will look for it in the object’s prototype.
- This prototype-based inheritance is a key feature of JavaScript, and it allows you to create hierarchies of objects that share common functionality.
For referencing a prototype stick to this:
human.__proto__.__proto__; // never use this
Object.getPrototypeOf(human); // Recommended way to get prototype
Essentially, one object can inherit properties or methods from another object through something called the prototype chain.
- Every object can have exactly one prototype
- At the end of the chain, the value is
null
Here’s an example:
const animal = {
dna: 'ATCG',
};
const cat = {
meow() {
console.log('meow')
}
}
Object.setPrototypeOf(cat, animal); // cat now has the DNA property
console.log(cat.dna); // ATCG
console.log( Object.getPrototypeOf(cat) ); // { dna: 'ATCG' }
Object.getPrototypeOf(cat) === animal; // true
Object.getPrototypeOf(animal) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype) === null; // true (end of chain)
Let’s talk about this:
A keyword that references an object based on how a function is called
- In global scope,
this
references the browser - In an object,
this
references that specific object
For example:
function wtfIsThis() {
console.log(this); // ref: browser
}
const person = {
wtfIsThis: function() {
console.log(this); // ref: person
}
}
You can bind this
to another object using .bind()
:
const person = {};
const personFun = wtfIsThis.bind(person);
this
can get confusing… luckily, JavaScript has the “arrow” syntax
Arrow functions do not have their own this
value and are always anonymous making them ideal for function expressions
function wtfIsThis() {
console.log(this); // ref: browser
}
const person = {
wtfIsThis: () => {
console.log(this); // ref: person
}
}
Keep in mind when creating functions, when used as an argument, primitive types are passed by value (creating a copy of the original variable) and functions are passed by reference (and stored by heap).****
Destructuring Objects
Objects like this have several properties that we may want to set as individual variables to use throughout the app.
It’s good to use destructuring whenever possible.
Here’s a basic cat object:
const cat = {
name: 'milo',
age: 1,
meow() {
console.log('meow');
}
}
One way to destructure an object is with dot notation where we reference each property with a period followed by the property name:
const name = cat.name;
const age = cat.age;
However, JavaScript provides a better syntax that gives the same outcome:
// can use const or let
const { name, age } = cat;
You can even change the name of the variables with this syntax like so:
// can use const or let
const { name: nameOfCat, age } = cat;
Destructuring can also be used on arrays:
const arr = [ 'foo', 'bar', 'baz' ];
// Deconstruct a full array:
const [ a, b, c ] = arr; // position matters! ( a === 'foo' )
// Deconstruct one item:
const [ ,, c ] = arr;
// Equivalent to
const c = arr[2];
Combining Objects/Arrays With the Spread Syntax
Merge objects or arrays with the spread operator ...
Let’s say you have 2 objects that are retrieved from different API calls:
const obj1 = {
first: 'a',
second: 'b',
third: 'c',
}
const obj2 = {
third: 'd',
fourth: 'e',
}
One way you can combine these is via Object.assign()
where the last object takes the highest priority:
const full = Object.assign({}, obj1, obj2);
Where full
would contain the following:
first: 'a',
second: 'b',
third: 'd',
fourth: 'e',
This can look much nicer with the spread operator and is more common in practice:
const full = { ...obj1, ...obj2 };
Or even better merge them as you define obj2
:
const obj2 = {
...obj1,
third: 'd',
fourth: 'e',
}
Position matters, any properties that come after will be overwritten:
const obj2 = { // third === 'd' (obj2 overrides)
...obj1,
third: 'd',
fourth: 'e',
}
// Versus
const obj2 = { // third === 'c' (obj1 overrides)
third: 'd',
fourth: 'e',
...obj1,
}