The Joy of Javascript

This book starts out terrible. No idea what they’re talking about, need to fill in lots of blanks from other sources like developer.mozilla.org. And on the side we’re building a blockchain? But… after filling in all the blanks you actually learn a lot of interesting things about what’s going on under the hood of JavaScript…

Object prototypes

Prototypes are the mechanism by which JavaScript objects inherit features from one another. They’re not the same as class inheritance, though class inheritance uses prototypes.
The prototype object is an arbitrary object literal that we can use to group common properties. Prototypes can be manipulated at any time, even at runtime
const myObject = { city: "Haruru", greet() { console.log(`Greetings from ${this.city}`); }, }; myObject.greet(); // Greetings from Haruru console.log(Object.getPrototypeOf(myObject));
Every object in JavaScript has a built-in property called prototype (access via Object.getPrototypeOf(). The prototype is itself an object, making what’s called a prototype chain.
We can shadow properties in the prototype chain:
const myDate = new Date(1995, 11, 17); console.log(myDate.getYear()); // 95 myDate.getYear = function () { console.log("something else!"); }; myDate.getYear(); // 'something else!'

Setting a prototype

  1. Via Object.create() creates a new object and allows you to specify an object that will be used as the new object’s prototype.
    1. const personPrototype = { greet() { console.log('hello!'); }, }; const carl = Object.create(personPrototype); carl.greet(); // hello!
      Object.create can also give us fine control over how this newly create object’s properties behave via its second parameter (which receives an object of data descriptors):
      const moneyTransaction = Object.create(transaction, { funds: { value: 0.0, enumerable: true, writable: true, configurable: false } });
  1. Using a constructor. In this example we’re creating a Person function (that we can use as a constructor) and then injecting inheritance into it by retroactively assigning the personPrototype Object to be its prototype (via Object.assign(Person.prototype, personPrototype)) - this has to be some sort of crime, no? 😱
    1. function Person(name) { //this is a constructor function this.name = name; } const personPrototype = { greet() { console.log(`hello, my name is ${this.name}!`); }, }; Object.assign(Person.prototype, personPrototype); // put the methods defined in personPrototype onto the Person function's prototype, which automaticall contains the greet method // or //Person.prototype.greet = personPrototype.greet; const franz = new Person('Franz'); franz.greet(); // hello, my name is Franz

Prototype resolution process

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]], traveling up the chain until it finds it and returns it. If it doesn’t it finally terminates at the empty object literal {} (aka Object.prototype) and returns undefined (for a value property) or a TypeError (for a function-valued property).
notion imagenotion image
 
A transaction:
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); console.log(moneyTransaction.funds);
Configuring the prototype chain using the constructor functions pattern:
Note how to facilitate code sharing and reuse, especially with methods, where it’s unnecessary to define more than one copy. It adds calculateHash to HashTransaction’s prototype so that it’s shared among all HashTransaction instances
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) { // defensive coding if someone forgets *new* 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'); // MUST call with *new*! 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] }
using the new keyword with a function implicitly sets what this points to in newly created objects.

Objects Linked to Other Objects (OLOO)

OLOO relies on Object.create to create associations among the objects that constitute your domain model.
In JS you can associate objects in two ways:
  • Implicit - not visible in code. Association is formed via an “is a” relationship. In this example, we say that Foo “is a” UpperCaseFormatter
    • const Foo = Object.create(UpperCaseFormatter); Foo.saySomething = function saySomething(msg) { console.log(this.format(msg)); } Foo.saySomething('hello'); // Prints HELLO
  • Explicit - link is well known and visibly set in code. Association is formed via a “uses” label, also known as object composition.
    • const UpperCaseFormatter = { format: function(msg) { return msg.toUpperCase(); } }; const Foo = { formatter: UpperCaseFormatter, saySomething: function print(msg) { console.log(this.formatter !== null ? this.formatter.format(msg) : msg ); } }; Foo.saySomething('hello'); // Prints HELLO
OLOO’s view of differential inheritance is different from the mental model of classes in that it doesn’t consider child objects to derive from a base object. Rather, it considers objects peers that link together and delegate functionality by passing messages to each other.
All that inheritance-related terminology disappears, and we no longer say that an object inherits from another; we say that it links to another, which is a much simpler model to understand.
Let’s look at Object.create under the hood:
Object.create = function(proto, propertiesObject) { if (typeof proto !== 'object' && typeof proto !== 'function') { throw new TypeError('Object prototype may only be an Object: ' + proto); } function F() {} F.prototype = proto; return new F(); };
let’s take proper advantage of it and use it to wire up the chain connecting all our objects. Basic Blockchain data structure:
const MyStore = { init(element) { this.length = 0; this.push(element); }, push(b) { this[this.length] = b; return ++this.length; } } const Blockchain = Object.create(MyStore); const chain = Object.create(Blockchain); chain.init(createGenesisBlock); chain.push(new Block(...)); chain.length; // 2
 

Leave a comment