The Two Pillars of JS

OOP and FP

Object Oriented Programming


Organizing JS

Object Oriented Programming allows us to organize our JS. Javascript is based on objects similar to Java. The difference being that Java uses classical inheritance while JS uses prototypal inheritance. All objects in JS inherit behavior from the root Object. So any built in methods that the root Object has, our objects also have. We can also add to the prototype of a given object and extend that behavior to other objects.

Keeping JS code organized is one of the most difficult parts of JS. This is because JS doesn't have a built in class system like what we're used to in Java. But that doesn't mean you can't still write your code that way. Even though JS isn't a classical inheritance language, we can still write our code that way. Let's take a look at some paradigms we can use to help us write better JS.

Follow along here


The Revealing Module Pattern

const MyModule = (function() {
  function MyModule(name) {
    this._name = name || "Default Name"
  }

  let _age = 30

  function _printName() {
    console.log("Name: " + _name)
  }

  MyModule.prototype = {
    changeInnerText: function(element, replacementText) {
      element.innerText = replacementText
      return console.log(
        `Text of ${element.tagName} changed to "${replacementText}".`
      )
    },
    getName: function() {
      _printName()
    },
  }

  return MyModule
})()

So this should look a little more Object Oriented. Notice we have a constructor now As well as public and private (prefaced with _) methods and properties. The things we're adding to the prototype are our public methods. These are the things that we expose to our user so they can interact with our objects.


Modules

Building modular JS is really helpful for code organization since it lets us build objects that have related methods in them. This also makes testing JS a much easier process to understand and execute. In Node environments there are actual module systems that you can use and then bundle so you don't have to add multiple JS files to your HTML. Sadly, these modules systems aren't supported by browsers yet so they aren't very useful to us now without something to bundle them. Let's take a look at some examples of the two popular module systems.

CommonJS

// app.js
const rectangle = require("./rectangle")
console.log(rectangle.area(4, 4))

// rectangle.js
module.exports = {
  area: function(length, width) {
    return length * width
  },
}

So with the CommonJS pattern we can actually break all of our code out into separate files and reference them in other files when we need them. This keeps everything clean and allows us to only view the code we need for specific jobs. It also helps us evade having one JS file with thousands of lines of code.

ESModules

The ES2015 spec actually includes a module implementation that will eventually be supported in browsers. For now tools like Webpack or Browserify actually compile all of your modules and bundle your code into one file. Let's take a look at this syntax:

// app.js
import greeting from "./greeting"
greeting.sayHiTo("Alan")

// greeting.js
export default {
  sayHiTo: function(name) {
    return console.log(`Hey ${name}`)
  },
}

Classes

So we've already seen how we can make things that look more like the classes that we're used to in Java using Immediately Invoked Function Expressions (IIFEs). ES2015 actually has a class system (sort of). This is actually just syntactic sugar that makes the prototype system a little easier to work with. Let's take a look at that syntax.

class Square extends Shape {
  constructor(length, width) {
    super()

    this.length = length
    this.width = width
  }

  getArea() {
    return this.length * this.width
  }
}

So as you can see, this looks a lot more organized. But don't be fooled. This isn't bringing classical inheritance to JS. All this is doing is interacting with the prototype in a very similar fashion to what we did with the Revealing Module Pattern but ES2015 is adding some new keywords that makes this a little more organized in our code.


Functions

Finally we want to keep our code organized inside whatever module method we're using by keeping all of our functionality in functions and methods. You should avoid having lines of JS that perform some functionality in your code outside of a function declaration. This keeps things organized, informs you what the code is supposed to be doing, and maintains testability. Your future self and other developers will thank you for using functions to execute behavior in JS.


Unorganized code

So we can take something that looks like this...

const myHeading = document.querySelector("h1")
myHeading.style.backgroundColor = "blue"
myHeading.style.color = "white"
myHeading.style.padding = "1em"

And organize it more using the tactics we've been exploring

const Dom = (function() {
  function Dom() {}

  Dom.prototype = {
    updateBackgroundColor: function(element, color) {
      element.style.backgroundColor = color
      return element
    },
    updateColor: function(element, color) {
      element.style.color = color
      return element
    },
    updatePadding: function(element, space) {
      element.style.padding = space
      return element
    },
  }

  return Dom
})()

Testing

What's nice about having your code organized like this is that it makes TDD in JS a lot easier since it helps you to think about your functionality in in terms of methods instead of just random lines of code.


Functional Programming


Simplifying JS

We now come to the second pillar of JS, Functional Programming (FP). FP in JS allows us to write fewer lines of more expressive code. So OOP and FP don't compete with each other, they in fact compliment each other. We want to create objects to organize our code in ways that make our libraries extensible. We want to keep the JS code we write as concise as possible both to save on memory and to keep it readlable for other devs (including our future selves).

Let's look into elements of FP


High Level Functions

Higher level functions (HLFs) are a great example of functional programming inside of JS. Since functions are first class citizens, we can pass them to other functions

function reverse(array, fn) {
  const newArray = array
  return fn(newArray.reverse())
}

function log(message) {
  console.log(message)
}

reverse([1, 2, 3, 4, 5], log) // prints [5, 4, 3, 2, 1]

Currying

Currying allows us to break up functionality into multiple functions that have less behavior

// Without Currying
function makeElement(elementType, elementContent) {
  const element = document.createElement(elementType)
  element.innerHTML = elementContent
}

// With Currying
const makeCurriedElement = elementType => elementContent => {
  const element = document.createElement(elementType)
  element.innerHTML = elementContent
}

const makeArticle = makeCurriedElement("article")

console.log(makeElement("article", "<h2>Here's my article</h2>"))
console.log(makeArticle("<h2>Here's my more easily read article</h2>"))

forEach

We've seen forEach in action. This is a really great way to iterate over a collection of objects.

const ninjaTurtles = [
  { name: "Leonardo", color: "blue" },
  { name: "Michelangelo", color: "Orange" },
  { name: "Raphael", color: "Red" },
  { name: "Donatello", color: "Purple" },
]

ninjaTurtles.forEach(turtle =>
  console.log(`${turtle.name}'s color is ${turtle.color}`)
)

There are plenty of other Array methods to use.


Conclusion

OOP and FP are HUGE programming practices that are essential to writing good JS. Using these practices while writing JS is truly what separates good devs from the truly great. Make sure to adopt these two pillars into your JS.

Resources