Introduction to Object Oriented JavaScript
Switch branches/tags
Nothing to show
Clone or download
Pull request Compare This branch is even with wdi-nyc-thundercats:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
README.md

README.md

Object Oriented JavaScrip Fundamentals

Learning Objectives

  • Define Object Oriented Programming
  • Identify examples of JavaScript's object oriented nature
  • Define objects with properties and methods as object literals
    • Explain the meaning of the this keyword
  • Define a factory funtion for creating standard objects
  • Define a constructor for OOJS creation of standard objects
    • Explain the effect of the new keyword
    • Explain the utility of a prototype
  • Define and provide use cases for inheritance
    • Model inheritance in JS with prototype

Object Oriented Programming (10 minutes)

Object oriented programming has been the dominant paradigm of programming for the last few decades. Countless books, articles, and talks have been created around object oriented best practices. This lesson will cover the fundemental ideas and rationale for object oriented programming and will identify the object oriented constructs of JavaScript.

As we have discussed, the greatest advesary in development is complexity. When projects grow, they inherently become more complex and harder to reason about. The principle behind object oriented programming is that we want to encapsulate complexity as much as we can. Encapsulation is the idea that we don't need to worry about how something works on the inside if we know how to talk to it from the outside.

Consider the document object from the browser. How do we grab an element with an id my-element from the document? Do we need to understand how the browser keeps track of the document or goes about finding the element we are looking for?

In object oriented programming, we define objects, frequently modeling them after things in the real world that our application is describes (e.g. a school registrar would have class, student, and faculty objects).

Fundemental to object oriented programming is the idea that an object is a cobination of the data describing that object, and the functionality of the object.

Object Literals (We Do - 10 minutes)

Lets define some objects describing some basic shapes:

const rectangle = {
  height: 3,
  width: 5,
  area: function () {return this.height * this.width},
  perimeter: function () {return this.width * 2 + this.height * 2},
  expand: function (times) {
    this.width *= times
    this.height *= times
  }
}

What is the this keyword doing in the method bodies?

Right Triangle (You Do - 10 Minutes/5 Minutes Review)

With a partner, write an object literal describing a right triangle. It should have properties: width and height, and methods: area, hypotenuse (Math.sqrt(width ** 2 + height ** 2)), and perimeter.

Problems with Scaling Up (5 Minutes)

This is object oriented programming! We did it!

Once our application starts getting larger (hundreds, thousands, or millions of shapes) complexity is going to start to creep up on us again. What are some problems we might face when we need to create many shapes as object literals?

Factory Functions (We Do - 10 Minutes)

A good solution to this is to create a function that takes some inputs to initialize anything unique about our shape, but duplicates anything shared by all shapes of a particular kind.

function rectangleFactory (height, width) {
  const rectangle = {}
  rectangle.height = height
  rectangle.width = width
  rectangle.area = function () {return this.height * this.width}
  rectangle.perimeter = function () {return this.height * 2 + this.width * 2}
  rectangle.expand = function (times) {
    this.height *= times
    this.width *= times
  }
  return rectangle
}

const rect1 = rectangleFactory(3, 5)
const rect2 = rectangleFactory(100, 250)
const rect3 = rectangleFactory(7, 7)

Notice, this is just a regular function returning a regular object -- this is much better than what we had before and we aren't using any special features of JS.

Right Triangle Factory (You Do - 10 Minutes/5 Minutes Review)

Working with a different partner, create a factory function for producing right triangle objects as described above.

Constructors (I Do - 15 Minutes)

Our factory functions are great but can be a little better. Constructors are JavaScript's standard way of defining a reusable template for object creation.

function Rectangle (height, width) {
  this.height = height
  this.width = width
  this.area = function () {return this.height * this.width}
  this.perimeter = function () {return this.height * 2 + this.width * 2}
  this.expand = function (times) {
    this.height *= times
    this.width *= times
  }
}

const rect1 = new Rectangle(3, 5)
const rect2 = new Rectangle(100, 250)
const rect3 = new Rectangle(7, 7)

What all has changed here?

  • The function name is capitalized and the name of the kind of thing we're constructing
  • When we call the constructor, we precede it with it with the new keyword
    • Instead of assigning properties to a new empty object we put them on the this keyword
    • We don't explicitly return from the constructor function

If we look at rect1, rect2, and rect3, they look like our rectangle literal from the beginning (which is what we were going for) but they also specify the name of the constructor which made them:

> rect1
Rectangle {
  height: 3,
  width: 5,
  area: [Function],
  perimeter: [Function],
  expand: [Function] }
> rect2
Rectangle {
  height: 100,
  width: 250,
  area: [Function],
  perimeter: [Function],
  expand: [Function] }
> rect3
Rectangle {
  height: 7,
  width: 7,
  area: [Function],
  perimeter: [Function],
  expand: [Function] }

RightTriangle Constructor (You Do - 10 Minutes/5 Minute Review)

You guessed it!

Inheritance (10 Minutes)

We now have three instances of Rectangle -- each have two properties and three methods. There is a bit more going on with these Rectangle instances.

What do we expect to see when we call:

rect1.toString()

Ignoring the output of toString for now, where did that method come from in the first place?

An important idea of object oriented programming is the ability to define specialized types of objects that inherit most of their functionality from another, more general type of object. This idea is generally know as inheritance. There are a lot of pitfalls to designing applications heavily dependent on inheritance but there are certainly use cases. For most of this course it is more important to understand inheritance when thinking about how the language works than actually implement inheritance in your projects.

Specialized Types (You Do - 10 minutes)

With a partener, list three examples of a general thing and more specialized versions. Examples might be:

  • A doctor is a general thing; pediatricians and cardiologists are specializations
  • A dog is a general thing; german shepards and shiba inus are specializations
  • A motor vehicle is a general thing; cars and motorcycles are specializations

Protoypes (10 Minutes)

JavaScript is what is called a prototype based language which is in contrast to class based languages like Java, Ruby, and many others. Protoypes are a more general pattern than classes, that is to say you can get all the behavior of a class based language but you can't do everything you can with protoypes just with classes. In a few weeks we will discuss JS classes which really is just using protoypes under the hood.

Everything in JavaScript is an object except for our primitive values (Booleans, Strings, Numbers, undefined, and null -- of those only undefined and null cannot be converted into an object). Every object has a prototype. We can use the method Object.getPrototypeOf(someObj) or the object property someObj.__proto__.

Run each of the following in the REPL:

Object.getPrototypeOf(true)
Object.getPrototypeOf(1)
Object.getPrototypeOf('Hello world!')
Object.getPrototypeOf([])
Object.getPrototypeOf({})

These prototypes are shared by all instances of that type. So, for example, the prototype of all strings is the same:

Object.getPrototypeOf('Hello world!') === Object.getPrototypeOf('')
Object.getPrototypeOf(true) === Object.getPrototypeOf(false)
Object.getPrototypeOf(1e10) === Object.getPrototypeOf(1)
Object.getPrototypeOf({}) === Object.getPrototypeOf({className: 'Thundercats', classRooms: ['3a', '3b']})

Climbing the Prototype Chain (10 Minutes)

When you access a property or method on an object, the environment will execute the following steps:

  1. Check the object to see if the property/method exists. If it does return that property or method (this is how we have accessed methods and properties that we defined so far).
  2. If the property/method does not exist on the object the access happened on, look to that objects prototype and check there. If it is there, return that. If not, continue checking prototypes until at the top of the prototype chain.

Let's look back at rect1.toString(). When we look at rect1, we can see that it does not have a method toString:

> rect1
Rectangle {
  height: 3,
  width: 5,
  area: [Function],
  perimeter: [Function],
  expand: [Function] }

Let's check the prototype of rect1 (using .__proto__ here for brevity):

> Object.getOwnPropertyNames(rect1.__proto__)
[ 'constructor' ]

Nothing to see here (but we'll revisit this shortly). First lets continue up the chain:

> Object.getPrototypeOf(Object.getPrototypeOf(rect1))
[ '__defineGetter__',
  '__defineSetter__',
  'hasOwnProperty',
  '__lookupGetter__',
  '__lookupSetter__',
  'propertyIsEnumerable',
  'toString',
  'valueOf',
  '__proto__',
  'constructor',
  'toLocaleString',
  'isPrototypeOf' ]

Here it is! Our instance of Rectangle inherits from Object and toString is a method on the prototype of Object.

Putting Things on the Prototype (I Do - 10 Minutes)

Currently the toString method when called on our Rectangle instance returns a pretty boring '[Object object]'. How could we go about specifying a custom toString method for use by all of our Rectangle instances? (As an aside, this customization of behavior for a standard named method is called polymorphism and is another very important idea in object oriented programming -- many of the resources linked at the end go into more detail on polymorphism)

function Rectangle (height, width) {
  this.height = height
  this.width = width
  this.area = function () {return this.height * this.width}
  this.perimeter = function () {return this.height * 2 + this.width * 2}
  this.expand = function (times) {
    this.height *= times
    this.width *= times
  }
}

Rectangle.prototype.toString = function () {
  return `Rect: ${this.height}x${this.width}`
}

const rect1 = new Rectangle(3, 5)
const rect2 = new Rectangle(100, 250)
const rect3 = new Rectangle(7, 7)

And we can try out our new toString method

> rect1.toString()
'Rect: 3x5'
> rect2.toString()
'Rect: 100x250'
> rect3.toString()
'Rect: 7x7'

Currently, our area, perimeter, and expand methods are defined on each instance as they are created. How can we leverage our prototype to share methods amoung all our instances?

function Rectangle (height, width) {
  this.height = height
  this.width = width
}

Rectangle.prototype.toString = function () {
  return `Rect: ${this.height}x${this.width}`
}

Rectangle.prototype.area = function () {return this.heigh * this.width}

Rectangle.prototype.perimeter = function () {return this.height * 2 + this.width * 2}

Rectangle.prototype.expand = function (times) {
  this.height *= times
  this.width *= times
}

const rect1 = new Rectangle(3, 5)
const rect2 = new Rectangle(100, 250)
const rect3 = new Rectangle(7, 7)

RightTriangle Using Prototype (You Do - 10 Minutes/5 Minutes Review)

Pair up once more and work on rewriting your right triangle constructor to leverage prototypes. It should have a custom toString method and there should be no methods directly on your instances of RightTriangle

Using Object.create to Model Inheritance (10 Minutes)

So far the prototypes of our constructors have inherited directly from the Object prototype. In this final example, I'm going to demonstrate how we could define a constructor Square which inherits from the prototype of Rectangle.

function Square (side) {
  Rectangle.call(this, side, side)
}

Square.prototype = Object.create(Rectangle.prototype)

Square.prototype.constructor = Square

Square.prototype.toString = function () {
  return `Square: ${this.width}^2`
}

Issues with Inheritance (5 Minutes)

Inheritance is a centeral idea in object oriented programming but when overused can be a huge source of complexity in an application. You will certainly use inheritance when working with certain libraries and you may have use cases when working on large projects but if you find yourself regularly defining new, ever more specific types it is worth considering alternative approaches and making your types more general.

Further Reading

This is one of the trickiest subjects we will cover. If you are new to programming, this may have been a bit overwhelming. That's fine -- good even; it means you're learning. This lesson has been largely based on the following resources which go into much more detail on these topics and provide alternative examples and exercises.