Merely adding “prototypal” in front to distinguish the actually nearly opposite behavior in JavaScript has left in its wake nearly two decades of miry confusion.
Aside from a few primitives, everything in JavaScript is an object. Yet for something that we deal with routinely, objects continue to be the most intimidating, hard-to-get-right parts of the language. The most common question that I hear is “How should I write the prototype chain to relate X, Y, and Z?” Every article or book you read does it in slightly different ways, and for some reason, even experienced developers need to turn to a search engine to relearn the process once in a while. The reason is twofold: on one side, a lot of boilerplate code is required, and on the other, we’re confusing the terms inheritance and prototype.
Inheritance is a powerful pattern of code reuse and is something we’ll take advantage of in this book, but we should not limit our understanding of prototypes to creating parent-child relationships. Inheritance is only one of the many applications of prototypes (and an important one indeed), but prototypes can do much more. Because one of the largest segments of JavaScript developers comes from class-oriented languages, to ensure their seamless transition, it was decided to make classes a first-class citizen of the language in ECMAScript 2015. Support for classes snowballed into a bunch of new features to support private and static properties. Once again, JavaScript’s history is tainted with attempts to make it look like Java. All this syntax is not welcomed with open arms by many JavaScript purists because it masks the underlying mechanics of JavaScript’s great object system.
For better or for worse, a lot of the domain modeling has been moving to use the streamlined setup of classes instead of the unnecessary boilerplate code of direct prototype configuration. It’s important to have a firm understanding of how JavaScript’s object system works, however. In this chapter, we’ll discuss two patterns that use the prototype feature to model inheritance relationships: constructor functions and ECMAScript 2015 classes. Both these patterns give you the benefit of sharing data and behavior through JavaScript’s internal prototype references and property resolution mechanism.
Let’s begin by reviewing the basic prototype inheritance configuration that you’ve probably seen many times.
JavaScript borrowed prototypes from a language called Self. The prototype mechanism is a big part of what allows JavaScript to be object-oriented; without it, you would not be able to send messages to objects higher up in the hierarchy of a complex network of interconnected objects (aka inheritance).
In this section, we’ll review the code needed to set up a basic prototype chain and lay the groundwork for learning about JavaScript’s property resolution mechanism, which is JavaScript’s central mechanism for object access.
Given your experience, you’ve probably dabbled with objects and their prototypes, so I’ll jump straight into some code. The first API we’ll look at to establish what looks like a parent-to-child relationship is
Object.create(proto[,propertiesObject]);
This API creates a new object linked to a prototype and optionally accompanied by a collection of new property definitions. For now, we’ll focus only on the first argument (proto), as shown in the following listing.
Listing 2.1 Using Object.create to create an object from a prototype
const proto = {
sender: 'luis@tjoj.com',
};
const child = Object.create(proto); ❶
child.recipient = 'luke@tjoj.com';
child.sender; // 'luis@tjoj.com'
child.recipient; // 'luke@tjoj.com'
❶ Using Object.create to configure a new child object based on a parent object (proto). Internally, the child object has a reference to the parent to access any of its properties. Another way to do this is to call the Object.setPrototypeOf(child, proto) API.
With this code, any properties of the parent (proto) object, hereafter called the prototype, are accessible from the child object. Nothing interesting is going on here, let’s do something more meaningful and model our first blockchain concept, a transaction.
A transaction represents an exchange of certain goods, such as money, from a sender to a receiver. For the most part, a transaction in the blockchain world looks exactly like that of a traditional banking system. To start, we’ll make sender and recipient simple email addresses and funds a number of fake Bitcoin amount as our form of currency. In our example, Luke uses Bitcoin from his digital wallet to buy coffee from Ana’s Café, as shown in figure 2.1.

Figure 2.1 A transaction object captures the details of a sender (Luke) sending Bitcoin to a receiver (Ana) to purchase coffee.
Let’s begin constructing this example. Listing 2.2 uses Object.create to establish a prototype configuration between two objects, moneyTransaction and transaction, and adds support for funds. In the wild, you’ll find some slight variations of this setup, but the general idea is always the same.
Listing 2.2 Transaction objects linked by a basic prototype setup
const transaction = { ❶
sender: 'luis@tjoj.com',
recipient: 'luke@tjoj.com'
};
const moneyTransaction = Object.create(transaction); ❷
moneyTransaction.funds = 0.0;
moneyTransaction.addFunds = function addFunds(funds = 0) { ❸
this.funds += Number(funds);
}
moneyTransaction.addFunds(10.0);
moneyTransaction.funds; // 10.0
❶ Prototype object from which to derive other objects—a regular object, not some abstract blueprint
❷ Creates a derived object from the prototype
❸ Adds new methods to the child object. Repeating the function name in the declaration helps build more-informative stack traces.
Let’s check whether our assumptions continue to be valid in the next listing.
Listing 2.3 Inspecting the new transaction objects
Object.getPrototypeOf(moneyTransaction) === transaction; // true ❶ moneyTransaction.sender; // 'luis@tjoj.com' ❷ moneyTransaction.funds; // 10
❶ Checks whether the prototype link has been established
❷ Verifies that inherited properties are accessible from the child object
Let’s unpack listing 2.2 a bit further. The prototype object (transaction) is in fact an arbitrary object literal that we’ll use to group common properties. As you can see, prototypes are objects that can be manipulated at any time, even at runtime, not created from thin air at the point of forming an inheritance association. This fact is important to understand; we’ll come back to why it matters when we talk about classes in section 2.3.
Here’s another take on this code, using Object.create’s second parameter, which receives an object of data descriptors:
const moneyTransaction = Object.create(transaction, {
funds: {
value: 0.0,
enumerable: true,
writable: true,
configurable: false
}
});
This second argument gives us fine control over how this newly created object’s properties behave:
Enumerable — Controls whether the property can be enumerated or viewed (as when you pass the object to console.log, enumerating the keys with Object .keys), or whether it’s seen by Object.assign (a topic that we’ll circle back to in chapter 3).
Configurable — Controls whether you’re allowed to delete an object’s property with the delete keyword or whether you can reconfigure the field’s property descriptor. Deleting a property alters the shape of an object and makes your code more unpredictable, which is why I prefer to use this attribute’s default value (false) or omit it from the data descriptor.
Writable — Controls whether you can reassign the value of this field, effectively making its assignment immutable.
When you create a property by using the dot notation directly on the object, as in listing 2.2, that act is equivalent to defining a property with a descriptor with all settings set to true. Typically, most developers don’t bother with data descriptors, but they can come in handy when you’re writing your own libraries and frameworks for others to use and want to do things such as hide a certain field from view or make some fields immutable. Data descriptors help enforce certain design principles and communicate clear intentions about how your APIs work. We’ll come back to this issue of immutability and why it’s important in chapter 4.
As you can see, Object.create offers a simple, elegant way to create objects from a shared prototype and establishes the proper inheritance linkage to resolve property lookups.
A discussion of JavaScript’s prototype mechanism is moot without a discussion of its property lookup mechanism, which is the most important concept behind implementing object-oriented patterns in JavaScript. According to the ECMAScript specification, an internal reference known as [[Prototype]] (accessible via the __proto__ property in objects) is configured by Object.create and effectively links moneyTransaction to transaction, as shown in figure 2.2. This is the sole reason why we can properly resolve moneyTransaction.sender to the value 'luis@tjoj.com', as shown in figure 2.2.

Figure 2.2 The internal reference [[Prototype]] is used to link an object (moneyTransaction) to another (transaction) in a unidirectional fashion, eventually ending in Object.prototype.
This figure points out the relationship among the objects through the prototype chain, which guides the JavaScript engine to find a property by a certain key. I’ll explain this process in more detail. When requesting a member field, the JavaScript engine first looks for the property in the calling object. If JavaScript can’t find the property there, it looks in [[Prototype]]. The property sender is not declared in moneyTransaction, yet it still resolves successfully. Why? Any property access or method invocation in moneyTransaction will travel up the prototype chain, continuing to transaction until it finds the property there and returns it. But what if it doesn’t? The lookup process would continue further, finally terminating at the empty object literal {} (aka Object.prototype). If resolution fails, the result of the operation is undefined for a value property or a TypeError for a function-valued property.
Behind the scenes, you can think of the hidden __proto__ property as being the bridge that allows you to traverse the chain. When we use prototypes to implement inheritance, which is the most common scenario, we say that property resolution “moves up” the inheritance chain.
You should never use __proto__ directly in your applications, as it’s meant to be used internally by the JavaScript engine. Hypothetically, if surfaced in userland code, it would look something like this:
const moneyTransaction = {
__proto__: transaction,
funds: 0.0,
addFunds: function addFunds(funds = 0) {
this.funds += Number(funds);
return this;
}
}
NOTE The use of __proto__ has been the subject of a heated debate over the years, and it’s currently being deprecated. It was standardized in ECMAScript 2015 as a legacy feature only so that web browsers and other JavaScript runtimes could maintain compatibility. Please don’t use it directly (even though you might see it used in the book for teaching purposes), as it might cease to work after some time. If you need to manipulate this field, the recommended APIs are Object.getPrototypeOf and Object.setPrototypeOf. You can also call the Object#isPrototypeOf method directly on the object.
With regard to notation, when referring to a property accessible from a constructor function’s prototype, as in Object.prototype.isPrototypeOf, throughout this book the # symbol is used instead: Object#isPrototypeOf.
Figure 2.2 looks straightforward but can get tricky with long, intertwined object graphs. I won’t delve into those specific use cases to keep the discussion centered on object construction techniques, but you can find out more by exploring the great resources in the following sidebar.
Now that we’ve reviewed the basic prototype setup in JavaScript, let’s discuss why it’s fundamentally inaccurate to use the overloaded term inheritance to describe JavaScript’s object-oriented model.
Differential inheritance, in which derived objects maintain references to the objects from which they are derived, is common in prototypal languages. In JavaScript, differential inheritance is called [[Prototype]]. By contrast, in class-based inheritance, a derived object copies all the state and behavior from its own class, as well as all its derived classes. The key distinction is copy versus link.
Although this term sounds a bit intimidating, differential inheritance is a simple concept referring to how extended behavior separates a derived object from its linked generic parent. If you think about a JavaScript object as being a dynamic bag of properties, differentiation means adding properties to another bag and linking the two bags. As you saw in figure 2.2, because the prototype resolution mechanism flows unidirectionally from a calling object to its linked object (and so on), any newly derived object is meant to differentiate itself from its parent with new behavior. New behavior includes adding new properties or even overriding an existing property from a linked object (known as shadowing). I don’t cover shadowing in this book, but you can visit http://mng.bz/OEmR for more information.
Consider another scenario in which we extend the generic transaction object to define hashTransaction. This object differentiates itself from its parent by adding a function (calculateHash) to compute its own hash value. At a high level, hashing is using an object’s state to generate a unique string value, much as JSON.stringify does, but we need to target only the values, not the entire shape of the object. This hash value has many uses in industry, such as fast insert/retrieval from hash tables or dictionaries, as well as data integrity checks.
In the world of blockchains, a hash is typically used as a transactionId that uniquely identifies a certain transaction that took place. For simplicity, we’ll start with a simple (insecure) hashing function in the next listing.
Listing 2.4 Creating hashTransaction with basic hashing calculation
const hashTransaction = Object.create(transaction);
hashTransaction.calculateHash = function calculateHash() { ❶
const data = [this.sender, this.recipient].join(''); ❷
let hash = 0, i = 0;
while (i < data.length) {
hash = ((hash << 5) - hash + data.charCodeAt(i++)) << 0;
}
return hash**2; ❸
}
hashTransaction.calculateHash(); // 237572532174000400
❶ Adds a method to calculate its own hash
❷ Properties that become input to the hashing algorithm
❸ Uses the exponentiation operator to square the hash value
To take another approach, you can also use Object.setPrototypeOf to differentiate a child object. Suppose that you want to extend moneyTransaction from hashTransaction. All the same mechanisms apply:
const moneyTransaction = Object.setPrototypeOf({}, hashTransaction);
moneyTransaction.funds = 0.0;
moneyTransaction.addFunds = function addFunds(funds = 0) {
this.funds += Number(funds);
};
moneyTransaction.addFunds(10);
moneyTransaction.calculateHash(); // 237572532174000400
moneyTransaction.funds; // 10
moneyTransaction.sender; // 'luis@tjoj.com'
moneyTransaction.recipient; // 'luke@tjoj.com'
Now that we’ve reviewed a couple of examples involving simple object literals, it’s much more useful to create new transactions with different data in them. Section 2.2 jumps into using constructor functions.
The constructor functions (aka object constructors pattern) have been the modus operandi for building objects in JavaScript for many years. Although object literals offer a terse way to define a single object, this method doesn’t scale when you need to create hundreds of objects of the same shape. In this case, the constructor function acts as a template to initialize objects populated with different data. You’re probably familiar with this pattern, but this section discusses some advanced techniques that you may not have encountered before.
Using functions instead of straight object literals to build objects allows your model to better evolve because you have much more control of how the objects are built. Functions allow you to export a facade to the caller under which changes don’t necessarily need to propagate to the calling code. The details of how an object gets initialized, such as enforcing any preconditions, are properly tucked away inside the constructor.
The following code snippet, for example, never reveals unnecessary details about the shape of HashTransaction or any operations that might take place during instantiation. Encapsulation is always a good choice:
const tx = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com');
This fundamental design decision makes your code less fragile and more maintainable, so in most cases, using functions to build objects is the preferred approach.
By convention, a constructor function name is capitalized to denote a kind of poor man’s class, if you will. Let’s take the use case from listing 2.4 and refactor it using constructors (listing 2.5). We have several options here. The simplest way to have an object inherit properties from another is to add all its properties to this new object; there’s no need to rely on the prototype chain. Because your objects are created dynamically (when the function is invoked), you need to pack these properties (fill the bag) into a single object context (this) within each constructor invocation.
Listing 2.5 Building and linking objects using the constructor functions pattern
function Transaction(sender, recipient) { ❶
this.sender = sender;
this.recipient = recipient;
}
function HashTransaction(sender, recipient) {
if (!new.target) { ❷
return new HashTransaction(sender, recipient);
}
Transaction.call(this, sender, recipient); ❸
this.calculateHash = function calculateHash() { ❹
//...
}
}
const tx = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com'); ❺
tx.calculateHash(); // 237572532174000400
tx.sender; // 'luis@tjoj.com'
❷ Detects whether the instantiation of the child object omits the new keyword and fixes the call. This line helps developers who forget to write new. I’ll come back to this topic in section 2.2.2.
❸ Calls the parent’s constructor to initialize any parent member properties into this object’s context
❹ Adds a new calculateHash method to every instance created
❺ Uses the new keyword to instantiate new objects. The new keyword is required to pass the newly created object as the this context.
By using functions, you can easily instantiate as many HashTransaction objects as you like, all of them containing the properties defined in Transaction as well. One caveat is that you need to call the function with the new keyword to ensure the context (this) is initialized properly.
These objects do not share references to any properties, however. You defined calculateHash directly on HashTransaction’s context (this variable), for example, adding a new calculateHash property to each instance of HashTransaction. In other words, if you create two instances, you’ll see two copies of the same method:
const tx1 = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com');
const tx2 = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com');
tx1.calculateHash === tx2.calculateHash; // false
To fix this problem, you need to configure how prototypes links are set up as new objects are created.
One interesting aspect of using constructors is that for every constructor F, JavaScript automatically creates the object F.prototype:
HashTransaction.prototype; // HashTransaction {}
This object is added to facilitate code sharing and reuse, especially with methods, where it’s unnecessary to define more than one copy. Hence, a more optimal approach is to add calculateHash to HashTransaction’s prototype so that it’s shared among all HashTransaction instances, for example:
HashTransaction.prototype.calculateHash = function calculateHash() {
//...
}
With this slight twist, these two properties refer to the same memory location:
tx1.calculateHash === tx2.calculateHash; // true
The same applies to any methods added to Transaction.prototype. Suppose that you add a new method called displayTransaction that you want all objects to share:
Transaction.prototype.displayTransaction = function displayTransaction() {
return `Transaction from ${this.sender} to ${this.recipient}`;
}
As the code is set up, calling it would yield a TypeError, indicating that the JavaScript engine tried to resolve that property but couldn’t:
TypeError: tx.displayTransaction is not a function
This error is expected because you had not configured the prototype chain:
Transaction.prototype.isPrototypeOf(tx); // false
You can fix this problem easily. As before, you can use Object.create. The following listing shows the complete prototype configuration.
Listing 2.6 Configuring the prototype chain using the constructor functions pattern
function Transaction(sender, recipient) {
this.sender = sender;
this.recipient = recipient;
}
Transaction.prototype.displayTransaction = function displayTransaction() {
return `Transaction from ${this.sender} to ${this.recipient}`;
}
function HashTransaction(sender, recipient) {
if (!new.target) {
return new HashTransaction(sender, recipient);
}
Transaction.call(this, sender, recipient);
}
HashTransaction.prototype.calculateHash = function calculateHash() {
const data = [this.sender, this.recipient].join('');
let hash = 0, i = 0;
while (i < data.length) {
hash = ((hash << 5) - hash + data.charCodeAt(i++)) << 0;
}
return hash**2;
}
HashTransaction.prototype = Object.create(Transaction.prototype); ❶
HashTransaction.prototype.constructor = HashTransaction; ❷
const tx = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com');
const tx2 = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com');
Transaction.prototype.isPrototypeOf(tx); // true
tx.calculateHash === tx2.calculateHash; // true
tx.displayTransaction === tx2.displayTransaction; // true
tx.__proto__.__proto__;
// Transaction { displayTransaction: [Function: displayTransaction] }
❶ Links prototypes for the lookup mechanism to work in case you need to resolve properties from Transaction.prototype
❷ Fixes or sets the constructor value. Without this line, tx would be a Transaction object or constructed from Transaction.
From the caller’s point of view, whether you pack all the properties into a single object or use prototype resolution, both pieces of code behave and are called in exactly the same way. Internally, the object layout in memory is different, but it’s abstracted away by the powerful and efficient JavaScript engine. Figure 2.3 illustrates the inner workings of listing 2.6.

Figure 2.3 The instantiation of tx per listing 2.6, together with a complete picture of all prototype links and constructor references. In JavaScript, constructor functions automatically obtain a reference to the prototype property upon instantiation with the new keyword. The navigation annotated with [[Prototype]] represents the internal __proto__ link between objects.
Although constructor functions are a bit more sophisticated and powerful than traditional object literals, the drawback of using this pattern is that it leaks a lot of the internal plumbing of JavaScript’s prototype mechanism, as you need to deal with the nitty-gritty details of the prototype configuration. If you don’t write everything perfectly, you run the risk of strange and unexpected behavior.
As mentioned earlier, always remember to call the constructor with new. Many developers forget. Again, using the new keyword with a function implicitly sets what this points to in newly created objects. This task has been a nuisance because forgetting to write it changes the resulting object’s context, so we needed to include the defensive bit of code I highlighted earlier:
if (!new.target) {
return new HashTransaction(sender, recipient);
}
Old-timers probably remember that the workaround (pre-ECMAScript 2015) was to insert the control in the following listing into the body of each constructor.
Listing 2.7 Pre-ECMAScript 2015 way to check for proper constructor call
if (!(this instanceof HashTransaction)) {
return new HashTransaction(sender, recipient);
}
Let’s look at what could happen if you didn’t. Suppose that instead of writing the preceding control code, you left it as
function HashTransaction(sender, recipient) {
Transaction.call(this, sender, recipient);
}
Then you tried to create a new instance:
const instance = HashTransaction('luis@tjoj.com', 'luke@tjoj.com');
Oops! This code throws a TypeError because the implicit this context is undefined. The error message is alluding to setting a member property of undefined, but not to the actual user error:
TypeError: Cannot set property 'sender' of undefined
Here, the developer forgot to write new in front of the function call:
const instance = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com');
Now let’s look at a different, more subtle trap. Suppose that we want transactions to have a descriptive name too:
function HashTransaction(name, sender, recipient) {
Transaction.call(this, sender, recipient);
this.name = name;
}
HashTransaction.prototype = Object.create(Transaction);
const instance = new HashTransaction(
'Coffee purchase',
'luis@tjoj.com',
'luke@tjoj.com'
);
Boom! Another type error occurs. This time, the error is even more cryptic and doesn’t happen in all JavaScript engines:
TypeError: Cannot assign to read only property 'name' of object '[object Object]'
Can you find the issue? Don’t worry; I’ll spare you from wasting your time. The issue is forgetting to link prototypes correctly. The code should have read
HashTransaction.prototype = Object.create(Transaction.prototype);
Again, writing this code manually every time is painful, leading to different behavior that easily escapes you or any linting tool you use.
This idea of using a constructor function with new to create new instances is what we know today as the pseudoclassical model. With the advent of ECMAScript 2015, this model has been largely replaced by a more familiar, streamlined class-oriented model that also addresses the amount of boilerplate needed. In fact, with classes, forgetting to write new when invoking a constructor now generates a clear error, as in this example for a Transaction class:
const tx = Transaction(...); TypeError: Class constructor Transaction cannot be invoked without 'new'
Section 2.3 explores the advantages of classes, as well as some of the newer proposals that accompany them.
In this section, we’ll pick up the discussion of the classes and prototypes dichotomy. Next, we’ll look at how the mental model of classes makes it simpler to represent inheritance hierarchies, as well as provide the syntactical advantage of cleaning up and smoothing the rough edges over the complex boilerplate code of constructor functions.
We’ve been trained to think that the only form of object orientation is through classes, and that’s not the case. Class-oriented does not equate to object-oriented, and JavaScript was an object-oriented language long before classes.
Classes were introduced to solve a specific problem, which is to make domain modeling in terms of inheritance easier, especially for developers coming from class-oriented languages such as Java, C#, and TypeScript. All the cruft and boilerplate code of prototype references had to be removed. Ideally, TC39 should have done this in a way that remained compatible with JavaScript’s origins, but the community clamored for the familiar class-like design.
In a language such as Java, a class is the basic unit of computation. Every object derives from some class, which provides the template that gets filled with data and allocated in memory during the process of instantiation. During this time, all of a class’s member properties, together with any inherited properties, get copied into a new object and populated at construction time.
As you learned in section 2.2. however, prototypes in JavaScript work differently. Prototypes are well-formed, concrete objects that get created at the same time they are declared (object literal) or as a byproduct of calling a function (constructor function), not through a separate instantiation process involving some inanimate blueprint or template. In fact, you can use a prototype object as you would any other before it’s even added to any inheritance chain.
Remember that the key factor that separates JavaScript from a language such as Java is that JavaScript links to instead of copies from objects higher up in the chain. In chapter 3, we’ll discuss patterns that rely heavily on linking and delegation.
In terms of classes, inheritance is configured with keywords class and extends. Although inheritance looks dramatically different from direct prototype references, it’s syntactic sugar over constructor functions (pseudoclassical model) that accomplishes the same thing. As an example, de-sugaring a class like
class Transaction {
constructor(sender, recipient, funds = 0.0) {
this.sender = sender;
this.recipient = recipient;
this.funds = Number(funds);
}
displayTransaction() {
return `Transaction from ${this.sender} to ${this.recipient}
for ${this.funds}`;
}
}
function Transaction(sender, recipient, funds = 0.0) {
this.sender = sender;
this.recipient = recipient;
this.funds = Number(funds);
}
Transaction.prototype.displayTransaction = function displayTransaction() {
return `Transaction from ${this.sender} to ${this.recipient}
for ${this.funds}`;
}
Another enormous difference from class-based languages is that JavaScript objects can access parent properties declared even after the child object was instantiated. Inherited properties from some base object are shared across all instances of child objects, so any changes to it dynamically ripple to all instances as well, which might lead to undesired, hard-to-trace behavior. This powerful, yet dangerous, mechanism leads to a well-known issue called prototype pollution. Encapsulation certainly helps, which is why exporting functions to build your objects as discussed in section 2.2.1 is much better than exporting the actual objects literals themselves. By the same token, exporting classes has the same benefits.
Let’s look at the pros and cons of classes more concretely. To do so, we’ll refactor Transaction yet again, this time using classes, and add a bit more code toward the real-life implementation that we’ll need for the rest of the book. As listing 2.8 shows, funds is now a property of Transaction, and we’ve added support for computing transaction fees, which is a common banking task.
To illustrate the ease with which classes allow you to set up the prototype chain, let’s refactor Transaction and HashTransaction. I’ll also take the opportunity to showcase new syntax proposals related to private class fields (http://mng.bz/YqVB) and static fields (http://mng.bz/5jgB) that you may not be familiar with.
Listing 2.8 Transaction and HashTransaction objects defined using classes
class Transaction {
sender = ''; ❶
recipient = ''; ❶
funds = 0.0;
#feePercent = 0.6; ❷
constructor(sender, recipient, funds = 0.0) {
this.sender = sender;
this.recipient = recipient;
this.funds = Number(funds);
}
displayTransaction() {
return `Transaction from ${this.sender} to ${this.recipient}
for ${this.funds}`;
}
get netTotal() {
return Transaction.#precisionRound(this.funds * this.#feePercent, 2);
}
static #precisionRound(number, precision) { ❷
const factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
}
}
class HashTransaction extends Transaction { ❸
transactionId;
constructor(sender, recipient, funds = 0.0) {
super(sender, recipient, funds); ❹
this.transactionId = this.calculateHash();
}
calculateHash() {
const data = [
this.sender,
this.recipient,
this.funds
].join('');
let hash = 0, i = 0;
while (i < data.length) {
hash = ((hash << 5) - hash + data.charCodeAt(i++)) << 0;
}
return hash**2;
}
displayTransaction() {
return `${this.transactionId}: ${super.displayTransaction()}`;
}
}
const tx = new HashTransaction('luis@tjoj.com', 'luke@tjoj.com', 10);
tx.displayTransaction();
// Prints:
// 64284210552842720: Transaction from luis@tjoj.com to luke@tjoj.com for 10
❶ Declares public fields for this class with default values. I recommend using default values, because they help code editors perform rudimentary type hinting for you.
❷ Uses the static private field and method declarations
❸ The prototype setup is cleanly tucked away behind the use of class and extends.
❹ Uses the keyword super to invoke the parent constructor. When you override a constructor, you must remember to invoke the super constructor with the required arguments as the first line.
At a glance, the refactoring done in listing 2.8 looks clean, terse, and elegant. I took the liberty of embellishing the code a little by adding private access to variables that need to be encapsulated, as well as a couple of private static functions for validation. As you know, these functions are shared by all instances, giving us true private access control. So querying for a private field from outside the class throws a SyntaxError:
tx.#feePercent; // SyntaxError
It’s worth pointing out the use of private fields and the private methods feature, prefixed with the hash (#) modifier. This feature was much needed to get proper encapsulation with classes, something you could have done by using modules and closures with the Module pattern, shown in listing 2.9 for comparison. (I’ll revisit this pattern in chapter 6.) By the same token, private fields and privileged methods are emulated by taking advantage of the closure or lexical scope that exists within the class—a function behind the scenes.
Listing 2.9 Transaction object implemented using the Module pattern
const Transaction = (function() {
const feePercent = 0.6; ❶
function precisionRound(number, precision) {
const factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
}
return { ❷
construct: function(sender, recipient, funds = 0.0) {
this.sender = sender;
this.recipient = recipient;
this.funds = Number(funds);
return this;
},
netTotal: function() {
return precisionRound(this.funds * feePercent, 2);
}
}
})();
const coffee = Transaction.construct('luke@tjoj.com', 'ana@tjoj.com', 2.5);
coffee.netTotal(); // 1.5
❶ Private variables and/or privileged functions
❷ Public variables and/or functions
With classes, the private state is visible only to methods in the scope of the class itself—also known as privileged. Also, static methods such as static #precisionRound won’t unnecessarily leak out to outside users—something that is cumbersome to achieve with regular constructor functions or even the Module pattern.
Taking a look back at listing 2.8, do you see a reference to the prototype property anywhere in this snippet of code? Nope! Classes have a well-defined structure, which makes them great at abstracting the mundane prototype details away from you and, hence, are less error-prone. Also, they offer a syntactical advantage for grouping data and behavior in a cohesive manner. Furthermore, class and extends literally put the icing on the cake for us and make third-party libraries such as Prototype’s extend, Lodash’s _.extend, or even Node’s util.inherits obsolete. Figure 2.4 illustrates this new, simplified mental model.

Figure 2.4 Building the HashTransaction class and its ancestor Transaction. Instances of HashTransaction will inherit all the public fields present in the parent. Using class and extends properly sets up the prototype chain so that property lookup is done effectively and constructor references line up perfectly.
This figure is somewhat similar to figure 2.3 but severely cuts the number of artifacts to achieve the same prototype configuration. The basic resemblance is deliberate because classes work like functions in JavaScript behind the scenes. The most obvious difference from figure 2.3, however, is that all of a class’s properties (fields and methods) are automatically part of the object’s prototype, accessible via the internal __proto__ object. You don’t have the option that you did with constructor functions. You lost that flexibility in favor of more structure.
Creating a new instance looks like the previous pseudoclassical approach (hence its name), with no changes:
const tx = new HashTransaction( 'luis@tjoj.com', 'luke@tjoj.com', 10 ); tx.transactionId; // 197994095955825630
On the surface, this code looks clean and compact. Classes are simpler to work with than constructor functions, without a doubt. But it’s important to realize that you’re adding a familiar façade over prototypes only to be able to think of inheritance as done in a class-oriented language.
This chapter covered two object construction patterns: constructor functions and classes. Both are inheritance-centric in that one way or another, you need to explicitly configure how child objects (or classes) relate to parent objects (or classes). Chapter 3 takes a different approach, presenting patterns that shift the mental model from inheritance to behavior delegation and linking.
JavaScript offers many choices for building objects, including prototypal inheritance, constructor functions, and classes.
The phrase prototype inheritance is an oxymoron because the idea of a shared linked prototype object is contradictory to the class inheritance model, in which instances gain copies of the inherited data.
Constructor functions have been the standard mechanisms used to mimic the idea of classes in JavaScript.
Classes smooth over the details of the prototype configuration for newcomers or developers coming from other class-based languages and have become the preferred choice of JavaScript developers.
The class syntax can blur your understanding of JavaScript’s prototype inheritance mechanism. Classes are useful, but remember that JavaScript is different from other class-based languages you may have seen or used.