Skip to content

Instantly share code, notes, and snippets.

@jaymcgavren
Last active September 12, 2018 09:23
Show Gist options
  • Star 50 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save jaymcgavren/5190970 to your computer and use it in GitHub Desktop.
Save jaymcgavren/5190970 to your computer and use it in GitHub Desktop.
In this screencast, Jay McGavren gives you a clear, no-nonsense look at Javascript objects. You'll learn how properties and functions are woven together to make a complete object. You'll learn what's special about constructor functions (not that much), and how prototypes form the basis for creating new objects. If you're ready to take the next s…

Objects

To create a new object, use the new keyword followed by a call to a constructor function. Javascript provides the Object() constructor out-of-the-box:

var toilet = new Object();

Properties

Once you have an object, you can set and get properties on it, like this:

toilet.tankCapacity = 4.8;

toilet.tankCapacity;

Curly brackets are a shorthand that lets you create a new object and set a bunch of properties on it. You could instead create the toilet object this way:

var toilet = {tankCapacity: 4.8, brand: 'Sloan'};

In the brackets, you provide a bunch of property names and corresponding values, separated by commas.

Functions

Functions are reusable pieces of code. You can declare a function like this. Functions can be stored in a variable or object property, just like numbers and strings:

var flush = function() {
  console.log("clearing out contents");
}

A shorthand is available for storing a function in a variable. This is exactly equivalent to assigning the function to the flush variable:

function flush() {
  console.log("clearing out contents");
}

Calling function

Accessing a function variable with just its name will only print it:

> flush
function flush() {
  console.log("clearing out contents");
}

To invoke it, you need to add parentheses after its name.

> flush();

Functions can of course accept arguments. Just include them in the function definition between the parentheses. The arguments will then be available for use by statements within the function.

var fillTank = function(volume) {
  console.log("Filling tank with " + volume + " liters of water");
}

Then include the arguments in the parenthesis in the function call:

fillTank(4.8);

Functions vs. Methods

Methods are just an object property that happens to be a function. If we want to add a flush() method to our toilet object from before, we can just assign a function object to its flush property:

toilet.flush = function() {
  console.log("clearing out contents");
}

Then we just access the flush property of toilet to get our function, and follow it with parenthesis to call it:

toilet.flush();

this

Within an object's methods, you can't access other properties directly. You can see that if we try to access the brand property by itself, it's not found.

toilet.brand = "Sloan";

toilet.flush = function() {
  console.log("clearing out contents of " + brand);
}

> toilet.flush();
ReferenceError: brand is not defined

Instead, use the keyword this, which is a reference to the object the method was called on, and access brand as a property of that:

toilet.flush = function() {
  console.log("clearing out contents of " + this.brand);
}

> toilet.flush();
clearing out contents of Sloan

Methods are only loosely tied to the objects they're attached to. For example, they have a call() method that lets you specify any value you want for this. We can use it to call the flush method with a completely different object.

> toilet.flush.call( {brand: 'Delta'} )

Constructor

Obviously setting up each object from scratch each time would be a lot of work. So instead, make a constructor.

A constructor is just another function, but by convention it's named with upper-case. Here's a constructor we could use to make as many toilet objects as we like. It accepts a brand as an argument, then sets up a new object with the given brand and a default tank capacity.

function Toilet(brand) {
  this.brand = brand;
  this.tankCapacity = 4.8;
}

When a call to the function is preceded by the keyword new, the this variable gets set to a new object instance, which will automatically be the function's return value.

> var myToilet = new Toilet("Sloan");

> console.log(myToilet.tankCapacity);
4.8
> console.log(myToilet.brand);
Sloan

The returned instance will also have a reference to the constructor used to create it:

> myToilet.constructor
function Toilet(brand) {
  this.brand = brand;
  this.tankCapacity = 4.8;
}

The constructor may be just another function, but you probably don't want to call it without the new keyword! Otherwise, the this variable may be set to something unexpected...

function ThisTest() {
  console.log(this);
}

> new ThisTest()
ThisTest

> ThisTest()
Window {chrome: Object, myToilet: Toilet, eventLog: Array[29], document: #document, v8Intl: Object…}

Prototype

A prototype is an object on which other objects are based. If you attempt to access a property (or method) on an object, and it can't be found, Javascript will look for the property on that object's prototype.

Here, we add a sanitize() method to the prototype associated with the Toilet constructor:

Toilet.prototype.sanitize = function() {
  console.log("Sanitizing " + this.brand);
}

That will allow us to call sanitize() on any instance created via that constructor:

> myToilet.sanitize();
Sanitizing Sloan

When we made the call to myToilet.sanitize(), Javascript looked first on the myToilet instance. When it saw there was no sanitize() method there, it switched to the prototype. What if we defined sanitize() on myToilet?

myToilet.sanitize = function() {
  console.log("Setting fire to " + this.brand);
}
> myToilet.sanitize();
Setting fire to Sloan

The next time myToilet.sanitize() is called, its own sanitize() method is found and executed first, meaning the prototype is never checked. We've overridden sanitize for myToilet only.

Prototype chaining

Javascript has a form of inheritance, too. Each prototype has its own parent prototype. If Javascript checks the prototype for a property or method and it doesn't have it, it'll look on the prototype's prototype, and so on up the chain.

Let's make a FancyToilet constructor based on the Toilet constructor. Remember our call method for functions from before? We can use it with constructors too. Here, we'll use it to call the Toilet constructor from within the FancyToilet constructor.

function FancyToilet(brand) {
  Toilet.call(this, brand);
}

This creates a new object, initializes it via the Toilet constructor, and then returns it from the FancyToilet constructor.

By setting a parent object as a constructor's prototype, objects built with that constructor gain access to all the methods on that object, including the methods on its constructor's prototype, and so on. Let's use an instance of Toilet as the prototype for FancyToilet:

FancyToilet.prototype = new Toilet();

But then we can also add our own new methods to this prototype:

FancyToilet.prototype.useBidet = function() {
  console.log("Ahh, refreshing!");
}

Now, let's call the constructor:

> var newToilet = new FancyToilet("Toto");

We can call methods from the prototype on the resulting instance:

> newToilet.useBidet();

Or methods from the prototype's prototype:

> newToilet.sanitize();

The call will fail only if no ancestor has the method:

> newToilet.startEngine();
TypeError: Object #<Toilet> has no method 'startEngine'

There's one problem here. The error message says newToilet is just a Toilet, shouldn't it be a FancyToilet? Let's look at its constructor property.

> newToilet.constructor
function Toilet(brand) {
  this.brand = brand;
  this.tankCapacity = 4.8;
}

Yup, it points to the Toilet constructor, not FancyToilet. That's because we set the FancyToilet.prototype object to a new instance created with the Toilet constructor. So of course, that object points back to its own constructor, not the FancyToilet constructor.

The fix is to set the constructor manually within the FancyToilet constructor.

function FancyToilet(brand) {
  Toilet.call(this, brand);
  this.constructor = FancyToilet;
}

Now, if we create a new instance, and check its constructor property, we can see that it points to FancyToilet.

> var fixedToilet = new FancyToilet("Kohler");
> fixedToilet.constructor
function FancyToilet(brand) {
  Toilet.call(this, brand);
  this.constructor = FancyToilet;
}

Object.create

I should mention one last way to create objects. Recent browsers support an Object.create method that lets you create a new object with a given prototype, without invoking any constructors.

We can create a new object and use our most recently created object as a prototype, like this:

> var createdToilet = Object.create(fixedToilet);

The object has no methods of its own until you define them, so anything you call will be passed to the prototype:

> createdToilet.useBidet();
Ahh, refreshing!

The same is true for properties:

> createdToilet.brand
"Kohler"

References

Eloquent Javascript

Javascript: The Good Parts

How Javascript Objects Work

HTML, CSS, and Javascript from the Ground Up

Simple "Class" Instantiation

Outline for this course

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment