The Definitive Guide to Object-Oriented JavaScript

By: James Shore

4121   47   253224

Uploaded on 07/18/2013

The object visualizer mentioned in this video is at http://www.objectplayground.com. For more great videos on professional JavaScript development, visit http://www.letscodejavascript.com.

VIDEO DESCRIPTION:
If you hang around the JavaScript world long enough, you'll come across a bunch of different recipes for object-oriented programming. The "standard way," so much as there can be a standard way, is the classical model. But why this? Why this... mess? In this episode, we'll build it up from first principles.

Many thanks to Sergio Ariel Minutoli for the Spanish subtitles!

Comments (5):

By anonymous    2017-09-20

What you need to understand is the concept of the Prototype.

When you create an instance using new, you are constructing an object based upon a prototype.

Consider the following:

function Person(name) {
    this.name = name;
    this.speak = function (msg) {
        console.log('Person says:' + msg);
    };
}
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', mom.speak === dad.speak);

Each time you construct a new instance of Person, a new speak prototype now floats around somewhere in your logic. This is very inefficient.

To fix this, we need to modify the prototype of our function:

function Person(name) {
    this.name = name;
}
Person.prototype.speak = function (msg) {
    console.log('Person says:' + msg);
};
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', dad.speak === dad.speak);

This way, we only have the function created once, on the prototype which is inherited to all instances. This is easier to maintain and a lot more efficient.

Now we can extend DOM object via their prototype, but it isn't recommended because you start to mess with the web standards, making troubleshooting much more difficult.

Array.prototype.isLengthGreaterThanFive = function(thisArg) {
  return this.length > 5;
};
console.log([1, 2, 3, 4].isLengthGreaterThanFive(), [1, 2, 3, 4, 5, 6].isLengthGreaterThanFive());

A better way of handling this is to create extended objects or to simply use functions:

//Using functions
function isLengthGreaterThanFive(array) {
  return array.length > 5;
}
console.log(isLengthGreaterThanFive([1, 2, 3, 4]), isLengthGreaterThanFive([1, 2, 3, 4, 5, 6]));

//Using a wrapper class
var MyArray = (function() {
  function MyArray(array) {
    if (array === void 0) {
      array = [];
    }
    this.array = array;
  }
  MyArray.prototype.isLengthGreaterThanFive = function() {
    return this.array.length > 5;
  };
  return MyArray;
}());
console.log(new MyArray([1, 2, 3, 4]).isLengthGreaterThanFive(), new MyArray([1, 2, 3, 4, 5, 6]).isLengthGreaterThanFive());

The benefits of using a class is that we can extend upon our idea of the object:

//Base class
function Person(firstname, lastname, says) {
    if (firstname === void 0) {
        firstname = "Leonado";
    }
    this.firstname = firstname;
    if (lastname === void 0) {
        lastname = "Da Vinci";
    }
    this.lastname = lastname;
    if (says === void 0) {
        says = "hello";
    }
    this.says = says;
}
//Base methods
Person.prototype.iAm = function () {
    return this.firstname + " " + this.lastname;
};
Person.prototype.Speak = function () {
    return this.says + " my name is " + this.iAm();
};
//Extended class
function Warrior(firstname, lastname, says) {
    //Call in constructor
    Person.call(this, firstname, lastname, says);
}
//Inheriting
Warrior.prototype = Object.create(Person.prototype);
Warrior.prototype.constructor = Warrior;
//Overruling "Speak"
Warrior.prototype.Speak = function () {
    return "My name is " + this.iAm() + ", " + this.says;
};
console.log([new Warrior("Peter", "Allan", "Ahoyhoy").Speak(), new Person("Peter", "Allan", "Ahoyhoy").Speak()]);

In the example above we extend the prototype of Person for Warrior so that we retain the functionality of Person, and then simply modify what's different about a Warrior. This way we get to reuse the prototype method iAm, and we can focus on only changing what needs to change in the Speak method.

EDIT 1

I noticed too late that the question had changed a little.

You can treat DOM elements like any other class in JavaScript. The following setup has all Persons sharing a single DIV to speakUp:

var Person = (function () {
    function Person(age, firstname, lastname) {
        if (age === void 0) { age = 50; }
        if (firstname === void 0) { firstname = "Peter"; }
        if (lastname === void 0) { lastname = "Venkman"; }
        this.age = age;
        this.firstname = firstname;
        this.lastname = lastname;
    }
    Person.prototype.speakUp = function () {
        Person.bubble.innerHTML = this.firstname + " " + this.lastname + " is " + this.age + " years old";
    };
    return Person;
}());
Person.bubble = document.createElement("div");
document.body.appendChild(Person.bubble);
setInterval(function () {
    var p = new Person(Math.floor(Math.random() * 100));
    p.speakUp();
}, 3000);

This could easily become a DIV per Person, or a refereced DOM object (document.getElementById) shared among all Persons.

EDIT 2

In response to your comment:

In JavaScript everything is in essence and object. You create a function it registers an object with the functions name and returns and instance of that object. Everything like Arrays, Strings, DOM elements and custom functions has some master object hidden behind the scenes. Every time a new Array or DOM element or whatever is created, it has a reference to its master object (called the prototype). This is called the prototype chain.

If you look on my second example above, when dad.speak is called JavaScript first searches the instance for a speak property, but it won't find one because we haven't assigned it the way we did in example one were it was instance specific.

JavaScript will then try one level up the prototype chain and here it will find a matching property and use this instead. This way we can alter the default behavior of custom OR existing elements in JavaScript.

The idea being, that if you have some property that all instances of a prototype should have, then we simply modify the prototype once and they will all inherit this property.

Think of it this way. If you were to describe all living things on earth in JavaScript you would want some form of groupings. For instance the first level would be something like an Exists object that carries a property for a name and an id. From here you you could create Plant and Animal and have them both inherit the prototype of Exists. Now we could create a Flower class that inherits Plant and a Rose class that inherits Flower and so on.

The idea is to apply you properties in a way that makes sense to human beings via inheritance (an owl can fly because it is a bird / a shark can swim because it is a fish). Binding them at the level that makes sense, inheriting in a logical pattern and using your time efficiently.

If you are still confused, try looking up prototype tutorials.

Here is a good Youtube video to explain it:

https://www.youtube.com/watch?v=PMfcsYzj-9M

Original Thread

By anonymous    2017-09-20

This is the best video about Object-Oriented JavaScript on the internet:

The Definitive Guide to Object-Oriented JavaScript

Watch from beginning to end!!

Basically, Javascript is a Prototype-based language which is quite different than the classes in Java, C++, C#, and other popular friends. The video explains the core concepts far better than any answer here.

With ES6 (released 2015) we got a "class" keyword which allows us to use Javascript "classes" like we would with Java, C++, C#, Swift, etc.

Screenshot from the video showing how to write and instantiate a Javascript class/subclass: enter image description here

Original Thread

By anonymous    2018-03-26

You must replace all MyModule.getCases with MyModule.prototype.getCases.

This is because the class syntax is just syntactic sugar. Without this you would instead have (and indeed we used to do this).

function MyModule () {
    console.log('hello')
}

MyModule.prototype.getCases = function () {
    return 2;
};

MyModule.prototype.getOtherCases = function () {
    return 3;
};

module.exports = MyModule;

This is completely equivalent to your code and if you copied and pasted it you would see it worked in the same way.

As all objects created with new inherit the prototype, the above syntax is just a very manual way of creating shared functions for instances of objects. Prototypal inheritance can be quite confusing at first, and it took me a while to get it too, but you can read about it here, and this video is quite good also.

When you were performing myModule.getCases in your tests were not actually modifying the prototype property which your instance (created with new) would inherit.

Now seeing that getCases inside your class syntax and getCases on the prototype are the same thing, hopefully you can see why the replacement solves your problem.

If I haven't explained anything very clearly or you want me to clarify anything feel free to ask.

Original Thread

Popular Videos 141

Google Developers

Submit Your Video

If you have some great dev videos to share, please fill out this form.