In my previous post, I began my quest to help noob programmers by introducing the basic computer science topic of data structures. I provided a quick overview of queues and stacks, so please read all about ‘em before engaging your eyeballs with this blog post. This time around, we’ll check out four different patterns for creating classes in JavaScript while learning how to build stacks and queues from scratch.
Before we go, I should warn you that I’m going to focus on describing the class instantiation patterns rather than thoroughly explaining the implementation of the data structures. Just keep in mind that we are going to explore building stacks and queues with objects rather than arrays. More specifically, we will use a property called storage
that is an object, not an array. Now I know what you’re pondering.
Why?
That is a beautiful question. Please ask it all the time (but please don’t troll me by leaving “Why?” in the comments section of this blog post). In this case, the answer to “Why?” may be a tad disatisfying. The answer is “because we can”, but don’t worry! It’ll be fun. Now, let’s start instantiating some motherhugging classes.
1. Functional Instantiation
The simplest way to implement classes is with a “maker” function that creates a new instance of the class and returns that instance so it can be stored as a variable. The new instance is just like any other JavaScript object. It can have properties that store relevant information about the instance (e.g., with a Car
class, instances might have a price
property). It can also have properties that store functions. These functions serve as methods that are tightly associated with the instance.
Characteristics
- Creates new copies of the same functions when creating a new instance of the same class. This lack of reuse takes up more memory and can leave an unsavory taste in some programmers’ mouths.
- There is no quick way to modify all instances of the class after they’ve been created. This will become more clear after examining the other instantiation tactics.
- Private variables can be created/used by harnessing closure scope superpowers, but I won’t get into that today.
- If you understand JavaScript functions and objects, then you can understand classes implemented via functional instantiation (other instantiation techniques require knowledge of
this
and/ornew
). - Could be used to create callable instances (i.e., the class could return a function rather than an object filled with properties).
Example code:
2. Functional Instantiation w/Shared Methods
By utilizing an object filled with methods, several classes can be created that have the same methods without creating new copies of said methods. The classes will use their own function references to refer to the same set of shared methods. Therefore, using shared methods eats up less memory than functional instantiation without shared methods.
Characteristics
- Reuses functions (which conserves memory) by getting function references from a utility such as Underscore.js’s
_.extend(instance,methods)
. - Retains the same benefits as functional instantiation without shared methods.
Example code:
3. Prototypal Instantiation
The key to prototypal instantiation is the use of Object.create()
to utilize shared methods. Unlike functional instantiation with shared methods, there is no need to use an extend()
function.
While it’s possible to use a prototype’s functions with Object.create(ExampleClass.prototype);
, it’s also possible to (ironically) avoid the word “prototype” altogether by using Object.create(sharedMethods);
.
Characteristics
- Reuses shared functions via
Object.create(Example.prototype)
orObject.create(objectOfFunctions)
. - Unlike functional instantiation, function references are shared. Each instance of the class does not get its own function references that point to the shared methods. This saves even more memory (although it is a very small improvement).
- Unlike functional instantiation, there is no way to use closure scope to enforce privacy of variables.
- Variables are stored on the returned object (aka
instance
), which means the shared stack methods need to use the keywordthis
to access the necessary data. It’s not a huge bummer, but if you’re a beginner, thenthis
can be a confusing concept. - Can use prototype chains for dynamic method modification and inheritance (subclasses!).
Example code:
4. Pseudoclassical Instantiation
This is the most commonly used class pattern. It’s also the most complicated because, in addition to using the this
keyword, it involves two concepts that the other class patterns don’t require: the new
keyword and prototypes. Prototypal instantiation uses prototypes via Object.create()
, but pseudoclassical instantiation needs you to explicitly type out ClassName.prototype.methodName
, which is just another source of confusion for beginners.
Also, it’s the only class pattern that uses a true constructor for creating new instances. Other class patterns use instantiator functions that explicitly return a new instance. The pseudoclassical class pattern does not perform such a return thanks to the new
keyword.
Characteristics
- Uses prototype chains to provide methods to instances of a class.
- Allows for dynamic method modification and inheritance (subclasses!) via prototype chains.
- Refers to the instance that’s being created with the
this
keyword. - Needs the
new
keyword to make an instance of a class. - Has a true constructor that is named with a noun rather than a verb. The name is capitalized.
- Is the most commonly used class pattern.