JavaScript Constructor Functions and Prototypes.

Photo by toine G on Unsplash

JavaScript is sometimes a bit confusing. It has a native type Object. How does that relate to Object-Orientation? And how does the desire to avoid repeating ourselves relate to them both?

Constructor Functions

It’s sometime handy to represent data with objects, which gives us key/value pairs. For example, we may represent a user as the following:

const bobby = {name: ‘bobby’, age: 20, hometown: ‘Philadelphia’}

Now imagine that we had a couple of users. This code will grow out of proportion very quickly! We would be repeating ourselves!

function User(name, age, hometown) {return {name, age,hometown,}}let bob = User(“Mr. Bob”, 45, “Manhattan”)bob.age // => 45

Interestingly typeof confirms that bob is an Object:

> typeof bob‘object’

However, something’s not quite as clear as we might like it to be. If we ask bob what made him, the answer is...

> bob.constructor[Function: Object]

bob is certainly an Object but it's more specific than that: it's a User. We'd really like for this special kind of object to be reflected when we ask it what it is. We'd like for a mystical process to come along and say you are not merely an Object, you are a User. We tell JavaScript to bless the thing created by the constructor function into being something more specific than Object by using the keyword new. Using new requires that we evolve our factory function into a constructor function. It's the same idea, but with a few subtle additions.

What Is new and How Does It Work With the Constructor Function

Lets create a constructor function. Constructor functions must be paired with the new keyword.

function User(name, email) {this.name = name;this.email = email;}

this here refers to the function's context. Since functions in JavaScript are also Objects, a function can say “Hey, constructor function, when you run, create a new copy of yourself, leaving the original unchanged and on that particular copy, set the properties based on the arguments passed into the function.

The keyword new tells the constructor function to do exactly that.

Put the two together like so:

function User(name, email) {this.name = name;this.email = email;}let lauren = new User(“Lauren”, “lauren@example.com”);lauren.name //=> “Lauren”

What’s happening here hinges on the new keyword. When we invoke the function with new before it, you can imagine an imaginary JavaScript Object being copied for use in the User function. The constructor function is not changed; the freshly-created, new context is changed.

This sorta makes sense, the function that constructed lauren, or the (constructor ) User. The instance lauren is an object. Given what we know about the types available in JavaScript, object makes good sense (not a Number or a String, that's for sure!)

Since we know one OO pattern, we might be wondering how to add methods to the User instances. It should be obvious that if we can set a property to point to a value like "Lauren" or "lauren@example.com", we should be able to set an anonymous function to a property. That function would have access to the this context created when the instance was new'd into existence.

function User(name, email) {this.name = name;this.email = email;this.sayHello = function() {console.log(`Hello everybody, my name is ${this.name} whom you’ve beenmailing at ${this.email}!`);};}let lauren = new User(‘lauren’, ‘lauren@example.com’);lauren.sayHello(); //=> Hello everybody, my name is lauren whom you’ve been mailing at lauren@example.com!

Identify Inefficiency In Constructor Function Object Orientation

Let’s assume the following. Let’s assume that the name property costs 32 bytes of space. Let's also assume that the email property costs 32 bytes of space. Let's lastly assume that a function costs 64 bytes of space.

So to create our lauren instance we pay a cost of 32 + 32 + 64 = 128 bytes. But now let's assume that we want to create many more Users - Facebook numbers of users. Lets suppose a paltry 1 million users. That would be: 128 million bytes of space. While memory and disk are getting bigger and cheaper all the time, we'd like to be efficient whenever possible.

In our example the names vary, so we can't economize there. The emails vary, so we can't economize there either. But the method, sayHello is the same in every instance: "in my current context return a template string with this current context's values."

The key to gaining efficiency is the prototype.

Prototype

We access the prototype of a constructor function by typing the constructor function’s name, and adding the attribute .prototype. So for User it's User.prototype. Attributes that point to functions in this JavaScript Object will be shared to all instances made by that constructor function.

function User(name, email) {this.name = name;this.email = email;}User.prototype.sayHello = function() {console.log(`Hello everybody, my name is ${this.name}`);};let sarah = new User(‘sarah’, ‘sarah@example.com’);let lauren = new User(‘Lauren’, ‘lauren@example.com’);sarah.sayHello(); //=> // “Hello everybody, my name is sarah!”lauren.sayHello(); //=> // “Hello everybody, my name is Lauren!”

The prototype is just a JavaScript Object.

User.prototype;// {sayHello: ƒ, constructor: ƒ}typeof User.prototype;// object

To prove the efficiency of sharing methods via prototype:

lauren.sayHello === sarah.sayHello; //=> true

Conclusion

We’ve seen the constructor function and the new keyword work to create new instances with instance data and instance methods. We’ve also seen the constructor’s prototype and seen how it can be used to share functionality between instances.

Passionate Programmer. Independent Thinker. Caring Father. Graduate of Flatiron Bootcamp for Software Development. Currently seeking new opportunities.