Skip to content
sergio edited this page Nov 17, 2020 · 28 revisions

An alternative way to structure our code, Object Orientation. By understanding and using OO, we can design our code to be easier to read, understand and change.

Object Orientation grants us the ability to write code that structurally establishes the relationships between data and functions.

  • Easier to Change
  • Offers Better Data Control
  • Easy to Replicate
  • Improves Understanding by Adding Meaning

The class keyword is just syntactic sugar, or a nice abstraction, over JavaScript's existing prototypal object structure. no function keyword is used for instance methods.

class Square {
  constructor(sideLength) {
    this.sideLength = sideLength;
  }
  // notice how the instance method doesn't have the `function` keyword
  area() {
    return this.sideLength * this.sideLength;
  }
}

let square = new Square(5);
square; // => Square { sideLength: 5 }
square.sideLength; // => 5
square.area(); // => 25

In class methods, this acts similar to Ruby's self keyword. this can be used to refer to properties of an instance, like name and age, or methods of an instance (this.sayName()).

You can also access the properties of an instance with:

square[`sideLenght`]
// => 5
square[`sideLenght`] = 13

So to use variables to call properties.


Static Methods

Static methods are class level methods - they are not callable on instances of a class, only the class itself. These are often used in 'utility' class that encapsulate a set of methods but don't need to be made into instances. For example, we could write a CommonMath class that stores a series of math related methods. For static methods, this references the class. This means that you can call a static method from within another static method of the same class using this.

class CommonMath {
  static triple(number) {
    return number * number * number;
  }

  static findHypotenuse(a, b) {
    return Math.sqrt(a * a + b * b);
  }

  static doubleTriple(number) {
    return this.triple(number) * 2
  }
}

let num = CommonMath.triple(3);
num; // => 27
let c = CommonMath.findHypotenuse(3, 4);
c; // => 5

This sort of class might be useful in many different situations, but we don't ever need an instance of it.


Getters and Setters

get

In modern JavaScript get has been introduced for methods which serve the specific purpose of retrieving data from an instance.

class Square {
  constructor(sideLength) {
    this.sideLength = sideLength;
  }

  get area() {
    return this.sideLength * this.sideLength;
  }
}

let square = new Square(5);
square.sideLength; // => 5
square.area; // => 25

If you try to use this.area(), you'll receive a TypeError. area is no longer considered a function!

//BAD PRACTICE!
class Square {
  constructor(sideLength) {
    this.sideLength = sideLength;
    this.area = sideLength * sideLength;
  }
}

This is valid code, but what we've done is load our constructor with more calculations.

The main benefit to using get is that your area calculation isn't actually run until it is accessed.

If included in the constructor, an expensive process will be called every time a new instance of a class is created.

Since properties can change, any values dependent on them should be calculated based on the current property values, otherwise we will run in to issues like this:

//also:
class Square {
  constructor(sideLength) {
    this.sideLength = sideLength;
    this.area = sideLength * sideLength;
  }
}

let square = new Square(5);
square.area; // => 25
square.sideLength = 10;
square.area; // => 25

set

class Square {
  constructor(sideLength) {
    this.sideLength = sideLength;
  }

  get area() {
    return this.sideLength * this.sideLength;
  }
}

square.sideLength = 10;
square.area; // => 100

We can change area by changing sideLenght however we can't assign area a new value. To make area fully act like a real property, we create both get and set methods for it:

class Square {
  constructor(sideLength) {
    this.sideLength = sideLength;
  }

  get area() {
    return this.sideLength * this.sideLength;
  }

  set area(newArea) {
    this.sideLength = Math.sqrt(newArea);
  }
}

let square = new Square(5);
square.sideLength; // => 5
square.area; // => 25
square.area = 64;
square.sideLength; // => 8
square.area; // => 64

area became a pseudo-property.

From the outside, it looks like a property is being set, but behind the scenes, we can define what we want to happen, including applying conditional statements:

set area(newArea) {
  if (newArea > 0) {
    this.sideLength = Math.sqrt(newArea)
  } else {
    console.warn("Area cannot be less than 0");
  }
}

Private Properties

All properties are accessible from outside an instance, as we see with square.sideLength, as well as from within class methods (this.sideLength).

class Transaction {
  constructor(amount, date, memo) {
    this.amount = amount;
    this.date = date;
    this.memo = memo;
  }
}

The date, amount and memo properties represent fixed values for each instance when a Transaction instance is created and probably shouldn't be altered. However, it is still possible to change these properties.

there is no official way to make a property private - all class and object properties are exposed as we see above. One common convention, however, is to include an underscore at the beginning of the property name to indicate those properties are not intended to be accessed from outside the class:

class Transaction {
  constructor(amount, date, memo) {
    this._amount = amount;
    this._date = date;
    this._memo = memo;
  }
}

Implementing private properties is planned in future versions of JavaScript, and will use a # symbol to indicate a property is private.


Private Properties with get and set

You may remember, though that in JavaScript, properties are exposed. That is, any property can be reassigned from outside. Here is where get and set really shine. With get and set, we can define the 'public' facing methods for updating a 'private' property.

In this Student class, we've set up a pseudo-property, firstName, which refers to a 'private' property _firstName:

class Student {
  constructor(firstName, lastName) {
    this._firstName = this.sanitize(firstName);
    this._lastName = this.sanitize(lastName);
  }

  get firstName() {
    return this.capitalize(this._firstName);
  }

  set firstName(firstName) {
    this._firstName = this.sanitize(firstName);
  }

  capitalize(string) {
    // capitalizes first letter
    return string.charAt(0).toUpperCase() + string.slice(1);
  }

  sanitize(string) {
    // removes any non alpha-numeric characters except dash and single quotes (apostrophes)
    return string.replace(/[^A-Za-z0-9-']+/g, '');
  }
}

let student = new Student('Carr@ol-Ann', ')Freel*ing');
student; // => Student { _firstName: 'Carrol-Ann', _lastName: 'Freeling' }

student.firstName = 'Hea@)@(!$)ther';
student.firstName; // => 'Heather'

When to Use Methods Over get and set

normal instance methods can do everything that get and set can do. All class methods and properties are exposed for use 'publicly'. Using get and set is purely for design purposes to produce better organized, easier to understand classes.

We can use get and set whenever we are handling input or output of a class. We are, in essence, creating the public interface of the class. get and set methods are the ways in which other classes and code should utilize this class.

Using this design, all remaining methods can be considered private. They don't deal with input and output; they are only used internally as helper methods.

Using a setter, you can call a function each time the value of a pseudo-property is changed, making sure all data on a class instance is consistent. Using a getter, you can return a computed value as though it is a property!

Clone this wiki locally