May 17, 2015

Notes on object-oriented JavaScript for o-o developers (part 3)

This post continues with more notes on distinctive features of object-oriented JavaScript as described in the book The Principles of Object-Oriented JavaScript.

The post is a follow-up to the earlier Part 1 and Part 2.


**  Although Javascript does not have classes, it does have constructors.

Here's a simple example of using a constructor to instantiate an object.

  function Mountain(name, height) {
    this.name = name;
    this.height = height;

    this.howTall = function () {
      return this.height;
    }
  }

  mt1 = new Mountain('Kilimanjaro', 19340);


A constructor is much like other functions, but it is different in the following ways.

-  By convention, the name of a constructor starts with a capital letter.

-  The constructor should be invoked via the new operator.

-  Within the body of a constructor, the keyword this refers to the object created by the new operator. There is no requirement to return a value because new does this.

The newly-created object is an instance of the constructor (which makes the constructor sound a bit like a class, doesn't it?).

This can be checked by using the instanceof operator. An object remains an instance of its constructor even as properties of the object are added and removed.

  console.log(mt1 instanceof Mountain);  // true

  // Add a property, and delete a different one.
  mt1.location = 'Earth';
  delete mt1.name;

  console.log(mt1 instanceof Mountain);  // still true


**  Prototypes fill a role similar to classes.

Using the Mountain constructor as defined above means that every object that is created by using the constructor gets its own copy of the howTall() method.

That redundancy can be eliminated by using the constructor's prototype.

Almost every function (except for a few that are built-in) has a prototype property that is a reference to an object. This includes functions that are used as constructors.

When an object is created, it is given a prototype property whose value is a reference to the same prototype object as its constructor. Therefore, the Mountain constructor and every object that is created by using Mountain have a reference to the same prototype object.

The properties and methods of the prototype are shared among all these objects and are visible through them.

So another way to provide the howTall() method is to do this:

  function Mountain(name, height) {
    this.name = name;
    this.height = height;
  }

  Mountain.prototype.howTall = function () {
    return this.height;
  }

  mt2 = new Mountain('Fuji', 12388);
  console.log(mt2.howTall());

That is, howTall() is available to all instances of Mountain through their prototype.

Because prototypes are just objects, you can add more methods to a prototype. Those methods then become available to objects which have that prototype, even after the objects have already been created.

  function Mountain(name, height) {
    this.name = name;
    this.height = height;
  }

  mt2 = new Mountain('Fuji', 12388);

  Mountain.prototype.heightInMeters = function () {
    return this.height * 0.305;
  }

  console.log(mt2.heightInMeters());

It's a bit like having classes that can change dynamically, adding and removing methods at will. Sounds like a lot of fun and some interesting debugging.

(Continued at Part 4.)

No comments:

Post a Comment