Object Recipes - JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

Object Recipes

15

In This Chapter

In this chapter, we’ll review some of the things you can do with JavaScript’s objects, especially with the way properties are defined and used.

Structs and Value Objects

Sometimes we want to share objects by reference for performance and space reasons, but we don’t want them to be mutable. One motivation is when we want many objects to be able to share a common entity without worrying that one of them may inadvertently change the common entity.

JavaScript provides a way to make properties immutable:

var rentAmount = {};

Object.defineProperty(rentAmount, 'dollars', {

enumerable: true,

writable: false,

value: 420

});

Object.defineProperty(rentAmount, 'cents', {

enumerable: true,

writable: false,

value: 0

});

rentAmount.dollars

//=> 420

// Strict Mode:

!function () {

"use strict"

rentAmount.dollars = 600;

}();

//=> TypeError: Cannot assign to read only property 'dollars' of #<Object>

// Beware: Non-Strict Mode

rentAmount.dollars = 600;

//=> 600

rentAmount.dollars

//=> 420

Object.defineProperty is a general-purpose method for providing fine-grained control over the properties of any object. When we make a property enumerable, it shows up whenever we list the object’s properties or iterate over them. When we make it writable, assignments to the property change its value. If the property isn’t writable, assignments are ignored.

When we want to define multiple properties, we can also write:

var rentAmount = {};

Object.defineProperties(rentAmount, {

dollars: {

enumerable: true,

writable: false,

value: 420

},

cents: {

enumerable: true,

writable: false,

value: 0

}

});

rentAmount.dollars

//=> 420

rentAmount.dollars = 600;

//=> 600

rentAmount.dollars

//=> 420

We can make properties immutable, but that doesn’t prevent us from adding properties to an object:

rentAmount.feedbackComments = []

rentAmount.feedbackComments.push("The rent is too damn high.")

rentAmount

//=>

{ dollars: 420,

cents: 0,

feedbackComments: [ 'The rent is too damn high.' ] }

Immutable properties make an object closed for modification. This is a separate matter from making it closed for extension. But we can do that too:

Object.preventExtensions(rentAmount);

function addCurrency(amount, currency) {

"use strict";

amount.currency = currency;

return currency;

}

addCurrency(rentAmount, "CAD")

//=> TypeError: Can't add property currency, object is not extensible

structs

Many other languages have a formal data structure that has one or more named properties that are open for modification, but closed for extension. Here’s a function that makes a Struct:

// Mutable:

function Struct (template) {

if (Struct.prototype.isPrototypeOf(this)) {

var struct = this;

Object.keys(template).forEach(function (key) {

Object.defineProperty(struct, key, {

enumerable: true,

writable: true,

value: template[key]

});

});

return Object.preventExtensions(struct);

}

else return new Struct(template);

}

var rentAmount2 = Struct({dollars: 420, cents: 0});

addCurrency(rentAmount2, "ISK");

//=> TypeError: Can't add property currency, object is not extensible

// Immutable:

function Value (template) {

if (Value.prototype.isPrototypeOf(this)) {

var immutableObject = this;

Object.keys(template).forEach(function (key) {

Object.defineProperty(immutableObject, key, {

enumerable: true,

writable: false,

value: template[key]

});

});

return Object.preventExtensions(immutableObject);

}

else return new Value(template);

}

Value.prototype = new Struct({});

function copyAmount(to, from) {

"use strict"

to.dollars = from.dollars;

to.cents = from.cents;

return to;

}

var rentValue = Value({dollars: 1000, cents: 0});

copyAmount(rentValue, rentAmount);

//=> TypeError: Cannot assign to read only property 'dollars' of #<Struct>

Structs and Values are a handy way to prevent inadvertent errors and to explicitly communicate that an object is intended to be used as a struct and not as a dictionary.16

value objects

A dictum that we will repeat from time to time is:

With few exceptions, a programming system cannot be improved solely by removing features that can be subject to abuse. Instead, a system is improved by removing harmful features in such a way that they enable the addition of other, more powerful features that were “blocked” by the existence of harmful features.

Our Value type above removes the ability to modify or extend a value. This does remove the possibility of making an accidental mistake. But does it make something else possible?

Yes.

Consider this problem:

var juneRent = {dollars: 420, cents: 0},

julyRent = {dollars: 420, cents: 0};

juneRent === julyRent;

//=> false

The june and july rents aren’t ===, because === performs an object identity check for non-primitive values. juneRent and julyRent are structurally equivalent, but not references to the same object, so they aren’t ===. No problem, let’s whip up a stuctural equivalence function:

function eqv (a, b) {

var akeys, bkeys;

if (a === b) {

return true;

}

else if (typeof a === 'number') {

return false;

}

else if (typeof a === 'boolean') {

return false;

}

else if (typeof a === 'string') {

return false;

}

else {

akeys = Object.keys(a);

bkeys = Object.keys(b);

if (akeys.length !== bkeys.length) {

return false;

}

else return akeys.every(function (key) {

return eqv(a[key], b[key]);

});

}

}

eqv(juneRent, julyRent);

//=> true

This looks very handy. For example, if we are fetching rows of data in JSON from a server, structural equivalence is the only way to compare two rows for “equality.” But there is a flaw. Note:

eqv(juneRent, julyRent);

//=> true

juneRent.cents = 6;

Our test for equivalence can be broken, it only tests what was true at one moment in time. Thus, objects that are Values don’t behave like numbers, booleans, or strings that are values. If 3 === 3, it will always be ===, because numbers, booleans, and strings are immutable. We can create newvalues with functions and operators, but the old ones don’t change.

If we want object equivalence to mean anything, we need it to only apply to immutable objects. That sounds familiar, and thats why we called an immutable Struct a Value. Let’s do one more thing with it:

(function () {

function eqv (a, b) {

var akeys, bkeys;

if (a === b) {

return true;

}

else if (a instanceof Value && b instanceof Value){

akeys = Object.keys(a);

bkeys = Object.keys(b);

if (akeys.length !== bkeys.length) {

return false;

}

else return akeys.every(function (key) {

return eqv(a[key], b[key]);

});

}

else return false;

}

Value.eqv = eqv;

Value.prototype.eqv = function (that) {

return eqv(this, that);

};

return Value;

})();

Value.eqv(juneRent, julyRent);

//=> false

var juneRentValue = new Value({dollars: 420, cents: 0}),

julyRentValue = new Value({dollars: 420, cents: 0});

Value.eqv(juneRentValue, julyRentValue);

//=> true

juneRentValue.eqv(julyRentValue);

//=> true

Immutability makes structural equivalence testing meaningful, which in turn makes value objects meaningful.

Accessors

TODO: This belongs in a general sense to describe private values and private methods for a naïve object. Need to discuss constructing multiple objects with this technique?

The Java and Ruby folks are very comfortable with a general practice of not allowing objects to modify each other’s properties. They prefer to write getters and setters, functions that do the getting and setting. If we followed this practice, we might write:

var mutableAmount = (function () {

var _dollars = 0;

var _cents = 0;

return immutable({

setDollars: function (amount) {

return (_dollars = amount);

},

getDollars: function () {

return _dollars;

},

setCents: function (amount) {

return (_cents = amount);

},

getCents: function () {

return _cents;

}

});

})();

mutableAmount.getDollars()

//=> 0

mutableAmount.setDollars(420);

mutableAmount.getDollars()

//=> 420

We’ve put functions in the object for getting and setting values, and we’ve hidden the values themselves in a closure, the environment of an Immediately Invoked Function Expression (“IIFE”).

Of course, this amount can still be mutated, but we are now mediating access with functions. We could, for example, enforce certain validity rules:

var mutableAmount = (function () {

var _dollars = 0;

var _cents = 0;

return immutable({

setDollars: function (amount) {

if (amount >= 0 && amount === Math.floor(amount))

return (_dollars = amount);

},

getDollars: function () {

return _dollars;

},

setCents: function (amount) {

if (amount >= 0 && amount < 100 && amount === Math.floor(amount))

return (_cents = amount);

},

getCents: function () {

return _cents;

}

});

})();

mutableAmount.setDollars(-5)

//=> undefined

mutableAmount.getDollars()

//=> 0

Immutability is easy, just leave out the “setters:”

var rentAmount = (function () {

var _dollars = 420;

var _cents = 0;

return immutable({

getDollars: function () {

return _dollars;

},

getCents: function () {

return _cents;

}

});

})();

mutableAmount.setDollars(-5)

//=> undefined

mutableAmount.getDollars()

//=> 0

using accessors for properties

Languages like Ruby allow you to write code that looks like you’re doing direct access of properties but still mediate access with functions. JavaScript allows this as well. Let’s revisit Object.defineProperties:

var mediatedAmount = (function () {

var _dollars = 0;

var _cents = 0;

var amount = {};

Object.defineProperties(amount, {

dollars: {

enumerable: true,

set: function (amount) {

if (amount >= 0 && amount === Math.floor(amount))

return (_dollars = amount);

},

get: function () {

return _dollars;

}

},

cents: {

enumerable: true,

set: function (amount) {

if (amount >= 0 && amount < 100 && amount === Math.floor(amount))

return (_cents = amount);

},

get: function () {

return _cents;

}

}

});

return amount;

})();

//=>

{ dollars: [Getter/Setter],

cents: [Getter/Setter] }

mediatedAmount.dollars = 600;

mediatedAmount.dollars

//=> 600

mediatedAmount.cents = 33.5

mediatedAmount.cents

//=> 0

We can leave out the setters if we wish:

var mediatedImmutableAmount = (function () {

var _dollars = 420;

var _cents = 0;

var amount = {};

Object.defineProperties(amount, {

dollars: {

enumerable: true,

get: function () {

return _dollars;

}

},

cents: {

enumerable: true,

get: function () {

return _cents;

}

}

});

return amount;

})();

mediatedImmutableAmount.dollars = 600;

mediatedImmutableAmount.dollars

//=> 420

Once again, the failure is silent. Of course, we can change that:

var noisyAmount = (function () {

var _dollars = 0;

var _cents = 0;

var amount = {};

Object.defineProperties(amount, {

dollars: {

enumerable: true,

set: function (amount) {

if (amount !== _dollars)

throw new Error("You can't change that!");

},

get: function () {

return _dollars;

}

},

cents: {

enumerable: true,

set: function (amount) {

if (amount !== _cents)

throw new Error("You can't change that!");

},

get: function () {

return _cents;

}

}

});

return amount;

})();

noisyAmount.dollars = 500

//=> Error: You can't change that!

Hiding Object Properties

Many “OO” programming languages have the notion of private instance variables, properties that cannot be accessed by other entities. JavaScript has no such notion, we have to use specific techniques to create the illusion of private state for objects.

enumerability

In JavaScript, there is only one kind of “privacy” for properties. But it’s not what you expect. When an object has properties, you can access them with the dot notation, like this:

var dictionary = {

abstraction: "an abstract or general idea or term",

encapsulate: "to place in or as if in a capsule",

object: "anything that is visible or tangible and is relatively stable in form"

};

dictionary.encapsulate

//=> 'to place in or as if in a capsule'

You can also access properties indirectly through the use of [] notation and the value of an expression:

dictionary[abstraction]

//=> ReferenceError: abstraction is not defined

Whoops, the value of an expression: The expression abstraction looks up the value associated with the variable “abstraction.” Alas, such a variable hasn’t been defined in this code, so that’s an error. This works, because ‘abstraction’ is an expression that evaluates to the string we want:

dictionary['abstraction']

//=> 'an abstract or general idea or term'

One kind of privacy concerns who has access to properties. In JavaScript, all code has access to all properties of every object. There is no way to create a property of an object such that some functions can access it and others cannot.

So what kind of privacy does JavaScript provide? In order to access a property, you have to know its name. If you don’t know the names of an object’s properties, you can access the names in several ways. Here’s one:

Object.keys(dictionary)

//=>

[ 'abstraction',

'encapsulate',

'object' ]

This is called enumerating an object’s properties. Not only are they “public” in the sense that any code that knows the property’s names can access it, but also, any code at all can enumerate them. You can do neat things with enumerable properties, such as:

var descriptor = map(Object.keys(dictionary), function (key) {

return key + ': "' + dictionary[key] + '"';

}).join('; ');

descriptor

//=>

'abstraction: "an abstract or general idea or term"; encapsulate: "to place i\

n or as if in a

capsule"; object: "anything that is visible or tangible and is relatively sta\

ble in form"'

So, our three properties are accessible and also enumerable. Are there any properties that are accessible, but not enumerable? There sure can be. You recall that we can define properties using Object.defineProperty. One of the options is called, appropriately enough, enumerable.

Let’s define a getter that isn’t enumerable:

Object.defineProperty(dictionary, 'length', {

enumerable: false,

get: function () {

return Object.keys(this).length

}

});

dictionary.length

//=> 3

Notice that length obviously isn’t included in Object.keys, otherwise our little getter would return 4, not 3. And it doesn’t affect our little descriptor expression, let’s evaluate it again:

map(Object.keys(dictionary), function (key) {

return key + ': "' + dictionary[key] + '"';

}).join('; ')

//=>

'abstraction: "an abstract or general idea or term"; encapsulate: "to place i\

n or as if in a

capsule"; object: "anything that is visible or tangible and is relatively sta\

ble in form"'

Non-enumerable properties don’t have to be getters:

Object.defineProperty(dictionary, 'secret', {

enumerable: false,

writable: true,

value: "kept from the knowledge of any but the initiated or privileged"

});

dictionary.secret

//=> 'kept from the knowledge of any but the initiated or privileged'

dictionary.length

//=> 3

secret is indeed a secret. It’s fully accessible if you know it’s there, but it’s not enumerable, so it doesn’t show up in Object.keys.

One way to “hide” properties in JavaScript is to define them as properties with enumerable: false.

closures

We saw earlier that it is possible to fake private instance variables by hiding references in a closure, e.g.

function immutable (propertiesAndValues) {

return tap({}, function (object) {

for (var key in propertiesAndValues) {

if (propertiesAndValues.hasOwnProperty(key)) {

Object.defineProperty(object, key, {

enumerable: true,

writable: false,

value: propertiesAndValues[key]

});

}

}

});

}

var rentAmount = (function () {

var _dollars = 420;

var _cents = 0;

return immutable({

dollars: function () {

return _dollars;

},

cents: function () {

return _cents;

}

});

})();

_dollars and _cents aren’t properties of the rentAmount object at all, they’re variables within the environment of an IIFE. The functions associated with dollars and cents are within its scope, so they have access to its variables.

This has some obvious space and performance implications. There’s also the general problem that an environment like a closure is its own thing in JavaScript that exists outside of the language’s usual features. For example, you can iterate over the enumerable properties of an object, but you can’t iterate over the variables being used inside of an object’s functions. Another example: you can access a property indirectly with [expression]. You can’t access a closure’s variable indirectly without some clever finagling using eval.

Finally, there’s another very real problem: Each and every function belonging to each and every object must be a distinct entity in JavaScript’s memory. Let’s make another amount using the same pattern as above:

var rentAmount2 = (function () {

var _dollars = 600;

var _cents = 0;

return immutable({

dollars: function () {

return _dollars;

},

cents: function () {

return _cents;

}

});

})();

We now have defined four functions: Two getters for rentAmount, and two for rentAmount2. Although the two dollars functions have identical code, they’re completely different entities to JavaScript because each has a different enclosing environment. The same thing goes for the two centsfunctions. In the end, we’re going to create an enclosing environment and two new functions every time we create an amount using this pattern.

naming conventions

Let’s compare this to a different approach. We’ll write almost the identical code, but we’ll rely on a naming convention to hide our values in plain sight:

function dollars () {

return this._dollars;

}

function cents () {

return this._cents;

}

var rentAmount = immutable({

dollars: dollars,

cents: cents

});

rentAmount._dollars = 420;

rentAmount._cents = 0;

Our convention is that other entities should not modify any property that has a name beginning with _. There’s no enforcement, it’s just a practice. Other entities can use getters and setters. We’ve created two functions, and we’re using this to make sure they refer to the object’s environment. With this pattern, we need two functions and one object to represent an amount.

One problem with this approach, of course, is that everything we’re using is enumerable:

Object.keys(rentAmount)

//=>

[ 'dollars',

'cents',

'_dollars',

'_cents' ]

We’d better fix that:

Object.defineProperties(rentAmount, {

_dollars: {

enumerable: false,

writable: true

},

_cents: {

enumerable: false,

writable: true

}

});

Let’s create another amount:

var raisedAmount = immutable({

dollars: dollars,

cents: cents

});

Object.defineProperties(raisedAmount, {

_dollars: {

enumerable: false,

writable: true

},

_cents: {

enumerable: false,

writable: true

}

});

raisedAmount._dollars = 600;

raisedAmount._cents = 0;

We create another object, but we can reuse the existing functions. Let’s make sure:

rentAmount.dollars()

//=> 420

raisedAmount.dollars()

//=> 600

What does this accomplish? Well, it “hides” the raw properties by making them enumerable, then provides access (if any) to other objects through functions that can be shared amongst multiple objects.

As we saw earlier, this allows us to choose whether to expose setters as well as getters, it allows us to validate inputs, or even to have non-enumerable properties that are used by an object’s functions to hold state.

The naming convention is useful, and of course you can use whatever convention you like. My personal preference for a very long time was to preface private names with my, such as myDollars. Underscores work just as well, and that’s what we’ll use in this book.

summary

JavaScript does not have a way to enforce restrictions on accessing an object’s properties: Any code that knows the name of a property can access the value, setter, or getter that has been defined for the object.

Private data can be faked with closures, at a cost in memory.

javaScript does allow properties to be non-enumerable. In combination with a naming convention and/or setters and getters, a reasonable compromise can be struck between fully private instance variables and completely open access.

Proxies

When we discuss Metaobjects, we’ll look at a technique called forwarding, wherein one object’s functions call the exact same function in another object. In the simple case where the only methods an object has are those that it forwards to another object, we call that object a proxy for the other object, which we call the base object, because it looks and behaves like the base object.

For example:

var stackBase = {

array: [],

index: -1,

push: function (value) {

return this.array[this.index += 1] = value;

},

pop: function () {

var value = this.array[this.index];

this.array[this.index] = void 0;

if (this.index >= 0) {

this.index -= 1;

}

return value;

},

isEmpty: function () {

return this.index < 0;

}

};

var stackProxy = {

push: function (value) {

return stackBase.push(value);

},

pop: function () {

return stackBase.pop();

},

isEmpty: function () {

return stackBase.isEmpty();

}

};

A proxy behaves like the base object, but hides the base object’s properties:

stackProxy.push('hello');

stackProxy.push('base');

stackProxy.pop();

//=> 'base'

stackProxy

//=>

{ push: [Function],

pop: [Function],

isEmpty: [Function] }

Of course, we can automate the writing of functions:

function proxy (baseObject) {

var proxyObject = Object.create(null),

methodName;

for (methodName in baseObject) {

if (typeof(baseObject[methodName]) === 'function') {

(function (methodName) {

proxyObject[methodName] = function () {

var result = baseObject[methodName].apply(baseObject, arguments);

return (result === baseObject)

? proxyObject

: result;

}

})(methodName);

}

}

return proxyObject;

}

var stack = proxy(stackBase);

stack.push('auto');

stack.pop()

//=> 'auto'

A proxy isn’t the original object, but it is often more than good enough. In some designs, the base objects are only ever manipulated through proxies. We create proxies with a transformation function. Later on, we’ll see more sophisticated transformation functions that can create partial proxies (proxies for only some of the base object’s behaviours), as well as proxies that control the creation of private state.