Object-Oriented JavaScript

This lesson assumes you are familiar with the usage of JavaScript Objects and Methods and the fact that JavaScript is a hybrid language.

JavaScript is a hybrid of (at least) three styles:

In this lesson we explore how JavaScript implements object-oriented language features.

What is an Object?

This is a simple definition, but many programmers either don't know it, don't understand it, or don't agree with it.

Definition of Object

An object...

Pure Object-Oriented Programming

Pure Object-Oriented Example

let rectangle = {
    height: 10,
    width: 8,
    area: function() {
        return this.height * this.width;
    }
}

let a = rectangle.area()

the above code follows OO rules since it only accesses data held inside rectangle via the magic pointer this, and returns a new value, not a live reference to internal state

Using an Object, But Not Object-Oriented

Using the rectangle object from the previous slide, the following code is not object-oriented...

let p = calculatePerimeter(rectangle);

function calculatePerimeter(rectangle) {
  return rectangle.height * 2 + rectangle.width * 2;
}

...since height and width are owned by rectangle, not by the calculatePerimeter function

Q: How would an OO design calculate the rectangle's perimeter? (Answer on next slide.)

Pure Object-Oriented Example (Cont.)

instead, an OO design would add perimeter as a method, so rectangle.perimiter() would access properties with this, perform the calculation, and return the correct value:

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

let p = rectangle.perimeter()

Object vs. Object

The Linguistic Metaphor for Objects

One way to think about objects:

Objects are things that can be described and can do things, or...

Creating an object literally

This code

let dog = {color: "brown"}
  1. creates a new object
  2. gives that object a property named 'color' whose value is 'brown'
  3. assigns the variable dog to point to that object

References and Instances

Stack Heap
dog -> {color: "brown"}

Literals create instances

let abby = {color: "brown"}
let lula = {color: "brown"}
Stack Heap
abby -> {color: "brown"}
lula -> {color: "brown"}

Side effects

let abby = {color: "brown"}
let abby = dog
let lula = dog

lula.color = "gold"

abby.color // now we think that abby is gold too :-(

Encapsulating State

Instance variables are properties of the object:

if (abby.color === 'brown') {
  console.log("Abby is a brown dog.");
}

the DOT operator here says "get me the color that is attached to abby"

Encapsulating Behavior

Instance methods are also properties of the object:

let abby = {color: "brown"};
abby.speak = function() {
  console.log("Bark!")
}
abby.speak()     // prints "Bark!" to console

The above is fine as far as it goes, but it's not really object-oriented since speak isn't using any state...

Encapsulation?

Unfortunately, in JavaScript, any code with a pointer to an object can see -- and modify! -- all properties of that object.

This means that true encapsulation is difficult, since all properties are public, and none are private.

Other languages solve this problem in various ways -- e.g. Java has a private keyword, and in Ruby all properties are private -- but JavaScript does not have a clean way of doing it... at least not yet.

To work around this deficiency there are several options, but none is ideal: * create variables and accessors inside your constructor using closure scope * prefix private properties with _ * use WeakMaps * use symbols

"this" is it

var circle = {radius: 2};
circle.circumference = function() {
    return Math.PI * 2 * this.radius;
}
console.log(circle.circumference()); // 12.566370614359172
var biggerCircle = {radius: 4};
biggerCircle.circumference = circle.circumference;
console.log(biggerCircle.circumference()); // 25.132741228718345

...but "this" isn't always it!

JS Gotcha: you must remember this

Even inside an object, you must remember to use this., otherwise radius becomes a global variable, not a property.

var circle = {radius: 2};
circle.circumference = function() {
    return Math.PI * 2 * radius;  // Oops! Forgot "this."
}
circle.circumference() // returns NaN
// (or says 'radius is not defined' if you're lucky)

This is a terrible mistake in the language design; it undercuts one of the core principles of computer language design, which is to make the simplest way to do something also the easiest way to do that thing.

JS Gotcha: Fat Arrow and Binding

In most OO languages, the pointer this is managed automatically. Any time you're executing code inside class A, this is guaranteed to point to an instance of that class.

In JavaScript, this needs to be managed more actively.

Specifically, during callbacks this still points to the other object -- the one that is calling you back -- not the object where the function is defined!

One Solution: the => fat arrow re-binds this to point to the current object immediately before executing the function.

More on "this" and binding

circle1.circumference()      // OK -- this = circle1
circle2['circumference']()   // OK -- this = circle2
var g = circle.circumference;
g();                        // BAD -- this = window, so this.radius = undefined, so result is NaN

re-binding

var module = {
  x: 42,
  getX: function() {
    return this.x;
  }
}

var unboundGetX = module.getX;
console.log(unboundGetX());
// expected output: undefined

var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42

see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

 Previous Lesson Next Lesson 

Outline

[menu]

/