Angular Architecture Students Web

(72) JavaScript Prototypes

What are JavaScript prototypes and what are they important?

This topic is important to architects because… JavaScript is an important language, JavaScript is unusual this way, and architects need to understand the languages that developers are using.

Covering constructors?  Constructors are a familiar concept with most modern programming languages.  We would not normally cover the topic of constructors on a site devoted to software architecture, but this gets complicated with JavaScript because it is not inherently object-oriented.  There is a little more work with JavaScript to get it to construct objects like Java or C# – you have to use both constructor functions and prototypes.

Great example of design patterns in action.  As we get to the bottom of this blog post, we will see a great reusable solution to a common JavaScript problem.  This is a perfect example of the importance of design patterns generally.

Avoiding Complexity.  This topic can get very complex and academic, but we will try to avoid that here.

JavaScript has no Class

JavaScript does not natively support classes.  You can simulate the functionality of classes with JavaScript constructor function and prototype support, but JavaScript does not have classes.

What is ECMAScript?  ECMA stands for “European Computer Manufacturers Association”.  ECMA created and maintains the standards for JavaScript.  The standards are published as ECMAScript standards with updates on a yearly basis.

ECMAScript 2015 Supports Class Syntax.  ECMAScript 2015, often referred to as “ES2015” will let you create classes that look very similar to classes in Java or C#, but in a way they don’t.  The “Class” declaration in ES2015 just extends the JavaScript function and prototype functionality.  That is, ES2015 classes are just fancy functions, so we still have to understand prototypes.

Why are JavaScript and ECMAScript Different?  ECMAScript is a standard of what JavaScript shouldsupport, but it is not necessarily what you are running in your browser right now.  Each browser’s JavaScript implementation typically lags the ECMAScript standard a bit.  Even if the browser does support newer ECMAScript standards, it does not matter until you upgrade your browser.  Developers upgrade their browsers often, but consumers don’t upgrade very often.

We are not going to discuss JavaScript classes in more detail in this post.

Prototypes Obscure Because They Are Often Hidden

Figure #1, "Source for Google's Main Page"
Figure #1, “Source for Google’s Main Page”

Prototypes really quite common.  As we will explain below, prototypes are a critical part of JavaScript, but many developers have not heard of them.  As the screenshot above suggests, the source Google’s main search page has 287 instance of the word “prototype”, so it has to be important.

There are three reasons developers may not have heard of prototypes:

Not needed for simple examples.  As will become clear below, you can use object-oriented JavaScript without prototypes and developers often do, but that is not a best practice.

Typically hidden when using ES2015 or TypeScript.  When we create complex JavaScript, we often use ES2015 or TypeScript.  In those cases, we create “classes”, but it will become clear below that these “classes” are really just generating more fundamental JavaScript.

KEY POINT:  Most real-world JavaScript has a lot of prototypes, but they are typically hidden from the developers creating the JavaScript code. 

Some Assumptions

What makes an “object” useful?  As a minimum, we want an object to “encapsulate” properties and methods.  The properties describe the object and the methods perform actions against the properties.

JavaScript Functions.  JavaScript functions work the same way as in Java and C#.  Therefore, we won’t discuss them in more detail here.  If you would like more information on JavaScript functions, please see https://www.w3schools.com/js/js_function_definition.asp

The “this” keyword.  The this keyword works the same way as it does in C# or Java.  Specifically, it refers to the current instance of a class.  

JavaScript Constructor Example

With languages that support classes, we are accustomed to the classes supporting constructors and default constructors.

Constructors without classes? For those who have worked with C# or Java, it is surprising that we can discuss constructors without classes, but that is the way JavaScript works. 

Creating a constructor.  To create a constructor, simply create a JavaScript function that is similar to the example below.  The key difference between this sample function and a generic function definition is the use of the “this” keyword.  Using the “this” keyword assigns the values of the parameters passed to the function to the instance that is being created.

function Person(firstName, lastName) {
    this.firstName = firstName,
    this.lastName = lastName
    }
}

KEY POINT:  The constructor function does not actually “do” anything other than create a new object.  Usually we want objects to perform an action, but not for constructor functions.

Creating new objects.  To create a new object “instance”, we use the “new” keyword.  This will be familiar to C# and JavaScript developers.

var person1 = new Person ("Joe", "Francis");
var person2 = new Person ("John", "Doe");

KEY POINT:  We use the “new” keyword to create a new object instance.

Constructors with Methods

In our first example, we only showed a JavaScript constructor with properties, but no methods.  Adding methods get a little more complicated and will lead into a discussion of prototypes below.  First, however, let’s create a simple function with a method. 

This is not much different than our previous example.  We just added a function declaration as a property to the previous example.

function Person(firstName, lastName) {
    this.firstName = firstName,
    this.lastName = lastName,
    this.fullName = function() {
	return this.firstName + " " + this.lastName;
    }
}

A Memory Problem Creating Instances

Everything we have done is easy for an experienced developer to understand.  We have done the minimum we need to create “objects”. That is, we now have a design pattern (not a class!) for creating objects that have methods and properties.

Problem:  Multiple instances of functions.  When we create two instances of “Person”, we create two instances of the properties and functions for it in memory.  This is a little different than with Java or C#, which create instances of the properties for each class, but only one instance of the method that is shared among all instances of the class in the current program.

Why is this a problem?  This actually isn’t a problem for the simple examples we look at when we are learning JavaScript.  In simple examples we have a small number of objects, a small number of functions, and those functions do not have much code.  Keep in mind, however, that real project code may have thousands of instances, all of which have dozens of functions with a great deal of code.  We can easily consume dozens or even hundreds of MB of browser memory because of separate function code for each object instance.

Prototypes Fix the Memory Problem

What is a prototype?  All JavaScript constructor functions have a “prototype”.  The prototype is simply a hidden property that is shared among all instances of the class.  The prototype is used to access the same instance of the properties and methods of the constructor function.

How can you prove the existence of a prototype?  You can see the prototype property of a constructor function in a JavaScript debugger even though you did not create such a property. 

The problem with prototypes.  If we are going to create functionality that is as similar to classes as possible, then we want each object instance to have separate properties but share the same method code.  Unfortunately, prototypes access the single instance of the property that is defined with the constructor function.  

The bad example below.    We could solve the memory problem by using prototypes, but then we would overwrite the values of the properties as we work with different instances.  We could create an empty person constructor, then set the values of the properties and methods through the prototype.  Unfortunately, when we change a property value through one instance of the constructor function, we change the value of the property for all instances.

// BAD CODE!!
function Person(){
}

Person.prototype.firstName = "Joe";
Person.prototype.lastName = 26;

Person.prototype.fullName = function(){
    return this.firstName + " " + this.lastName;
}
// Create objects using the Person constructor function
var person1 = new Person();
var person2 = new Person();

// Change the property name of the 
person1.firstName = "John";

console.log(person1.firstName);  // Output: "John"
console.log(person2.firstName);  // Output: "John"

Recap

What we have established thus far.  We have established three things:

  1. Constructor Functions are the Minimum.  We can use constructor functions to use JavaScript in an object-oriented manner.
  2. Methods Get Duplicated in Constructor Functions.  This creates a problem by using too much memory.
  3. Single Property Instance for Prototypes.  This causes multiple object instances to overwrite the properties.

Solution:  Combine Constructor and Prototype

This is real-world JavaScript.  If you right-click a page to look at the source view for a lot of real-world JavaScript, you will see the use of constructor function and prototypes typically following the design pattern shown in the example below.  

We simply declare the properties in the constructor function, then attach the method to the prototype.  We end up with copies of the properties for each instance, but only one copy of the method.  This is a little more complicated than the examples above, but it is an effective and standard solution.

function Person(firstName, lastName){
    this.firstName = firstName,
    this.lastName = lastName
}
 
Person.prototype.fullName = function(){
    return this.firstName + " " + this.lastName;
}

var person1 = new Person("Joe", "Francis");
var person2 = new Person("John", "Doe"); 

Leave a Reply

Your email address will not be published. Required fields are marked *