Source Code Examples
Object Recipes
proxy
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;
}
record
function Record (template) {
if (Record.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 Record(template);
}
value
var Value = (function () {
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 Record({});
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;
})();
Methods
when
function equals (x) {
return function eq (y) { return (x === y); };
}
function not (fn) {
var name = fn.name === ''
? "not"
: "not_" + fn.name;
return nameAndLength(name, fn.length, function () {
return !fn.apply(this, arguments)
});
}
function getWith (prop, obj) {
function gets (obj) {
return obj[prop];
}
return obj === undefined
? gets
: gets(obj);
}
function mapWith (fn, mappable) {
function maps (collection) {
return collection.map(fn);
}
return mappable === undefined
? maps
: maps(collection);
}
function pluckWith (prop, collection) {
var plucker = mapWith(getWith(prop));
return collection === undefined
? plucker
: plucker(collection);
}
function nameAndLength(name, length, body) {
var abcs = [ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
'z', 'x', 'c', 'v', 'b', 'n', 'm' ],
pars = abcs.slice(0, length),
src = "(function " + name + " (" + pars.join(',') + ") { return body.appl\
y(this, arguments); })";
return eval(src);
}
function imitate(exemplar, body) {
return nameAndLength(exemplar.name, exemplar.length, body);
}
function when (guardFn, optionalFn) {
function guarded (fn) {
return imitate(fn, function () {
if (guardFn.apply(this, arguments))
return fn.apply(this, arguments);
});
}
return optionalFn == null
? guarded
: guarded(optionalFn);
}
match
function Match () {
var fns = [].slice.call(arguments, 0),
lengths = pluckWith('length', fns),
length = Math.min.apply(null, lengths),
names = pluckWith('name', fns).filter(function (name) { return name !== \
''; }),
name = names.length === 0
? ''
: names[0];
return nameAndLength(name, length, function () {
var i,
value;
for (i in fns) {
value = fns[i].apply(this, arguments);
if (value !== undefined) return value;
}
});
}
function isType (type) {
return function (arg) {
return typeof(arg) === type;
};
}
function instanceOf (clazz) {
return function (arg) {
return arg instanceof clazz;
};
}
function isPrototypeOf (proto) {
return Object.prototype.isPrototypeOf.bind(proto);
}
when-args-are
function whenArgsAre () {
var matchers = [].slice.call(arguments, 0, arguments.length - 1),
body = arguments[arguments.length - 1];
function typeChecked () {
var i,
arg,
value;
if (arguments.length != matchers.length) return;
for (i in arguments) {
arg = arguments[i];
if (!matchers[i].call(this, arg)) return;
}
value = body.apply(this, arguments);
return value === undefined
? null
: value;
}
return imitate(body, typeChecked);
}
Metaobjects
extend-privately
function extendPrivately (receiver, mixin) {
var methodName,
privateProperty = Object.create(null);
for (methodName in mixin) {
if (mixin.hasOwnProperty(methodName)) {
receiver[methodName] = mixin[methodName].bind(privateProperty);
};
};
return receiver;
};
forward
function forward (receiver, metaobject, methods) {
if (methods == null) {
methods = Object.keys(metaobject).filter(function (methodName) {
return typeof(metaobject[methodName]) == 'function';
});
}
methods.forEach(function (methodName) {
receiver[methodName] = function () {
var result = metaobject[methodName].apply(metaobject, arguments);
return result === metaobject ? this : result;
};
});
return receiver;
};
delegate
function delegate (receiver, metaobject, methods) {
if (methods == null) {
methods = Object.keys(metaobject).filter(function (methodName) {
return typeof(metaobject[methodName]) == 'function';
});
}
methods.forEach(function (methodName) {
receiver[methodName] = function () {
return metaobject[methodName].apply(receiver, arguments);
};
});
return receiver;
};
delegate-to-own
function delegateToOwn (receiver, propertyName, methods) {
var temporaryMetaobject;
if (methods == null) {
temporaryMetaobject = receiver[propertyName];
methods = Object.keys(temporaryMetaobject).filter(function (methodName) {
return typeof(temporaryMetaobject[methodName]) == 'function';
});
}
methods.forEach(function (methodName) {
receiver[methodName] = function () {
var metaobject = receiver[propertyName];
return metaobject[methodName].apply(receiver, arguments);
};
});
return receiver;
};
mixin-with-policy
var policies = {
overwrite: function overwrite (fn1, fn2) {
return fn1;
},
discard: function discard (fn1, fn2) {
return fn2;
},
before: function before (fn1, fn2) {
return function () {
var fn1value = fn1.apply(this, arguments),
fn2value = fn2.apply(this, arguments);
return fn2value !== void 0
? fn2value
: fn1value;
}
},
after: function after (fn1, fn2) {
return function () {
var fn2value = fn2.apply(this, arguments),
fn1value = fn1.apply(this, arguments);
return fn2value !== void 0
? fn2value
: fn1value;
}
},
around: function around (fn1, fn2) {
return function () {
var argArray = [fn2.bind(this)].concat(__slice.call(arguments, 0));
return fn1.apply(this, argArray);
}
}
};
function inverse (hash) {
return Object.keys(hash).reduce(function (inversion, policyName) {
var methodNameOrNames = hash[policyName],
methodName;
if (typeof(methodNameOrNames) === 'string') {
methodName = methodNameOrNames;
inversion[methodName] = policies[policyName];
}
else if (typeof(methodNameOrNames.forEach) === 'function') {
methodNameOrNames.forEach(function (methodName) {
inversion[methodName] = policies[policyName];
});
}
return inversion;
}, {});
}
function mixinWithPolicy (provider, policyDefinition) {
var policiesByMethodName = inverse(policyDefinition || {}),
defaultPolicy = policies.overwrite;
if (policiesByMethodName['*'] != null) {
defaultPolicy = policiesByMethodName['*'];
delete policiesByMethodName['*'];
}
return function (receiver) {
receiver = receiver || {};
Object.keys(provider).forEach(function (key) {
if (receiver[key] == null) {
receiver[key] = provider[key];
}
else if (policiesByMethodName[key] != null) {
receiver[key] = policiesByMethodName[key](receiver[key], provider[key]);
}
else {
receiver[key] = defaultPolicy(receiver[key], provider[key]);
}
});
return receiver;
};
};
safe-extend-privately
var number = 0;
function safeExtendPrivately (prototype, mixin) {
var safekeepingName = "__" + ++number + "__",
methodName;
for (methodName in mixin) {
if (mixin.hasOwnProperty(methodName)) {
(function (methodName) {
prototype[methodName] = function () {
var context = this[safekeepingName],
result;
if (context == null) {
context = {};
Object.defineProperty(this, safekeepingName, {
enumerable: false,
writable: false,
value: context
});
}
result = mixin[methodName].apply(context, arguments);
return (result === context) ? this : result;
};
})(methodName);
};
};
return prototype;
}
Greenspun’s Tenth Rule
fluent and selfie
function fluent (methodBody) {
return function () {
var value = methodBody.apply(this, arguments);
return value === undefined
? this
: value;
}
}
function selfie (methodBody) {
return function () {
var value = methodBody.apply(this, arguments);
return value === undefined
? (this.self || this)
: value;
}
}
Encapsulation and Composition
partialProxy
function partialProxy (baseObject, methods, proxyPrototype) {
var proxyObject = Object.create(proxyPrototype || null);
methods.forEach(function (methodName) {
proxyObject[methodName] = function () {
var result = baseObject[methodName].apply(baseObject, arguments);
return (result === baseObject)
? proxyObject
: result;
}
});
return proxyObject;
}
policies
var policies = {
overwrite: function overwrite (fn1, fn2) {
return fn1;
},
discard: function discard (fn1, fn2) {
return fn2;
},
before: function before (fn1, fn2) {
return function () {
var fn1value = fn1.apply(this, arguments),
fn2value = fn2.apply(this, arguments);
return fn2value !== void 0
? fn2value
: fn1value;
}
},
after: function after (fn1, fn2) {
return function () {
var fn2value = fn2.apply(this, arguments),
fn1value = fn1.apply(this, arguments);
return fn2value !== void 0
? fn2value
: fn1value;
}
},
around: function around (fn1, fn2) {
return function () {
var argArray = [fn2.bind(this)].concat(__slice.call(arguments, 0));
return fn1.apply(this, argArray);
}
}
};
encapsulate
function isUndefined (value) {
return typeof value === 'undefined';
}
function isntUndefined (value) {
return typeof value !== 'undefined';
}
function isFunction (value) {
return typeof value === 'function';
}
var __slice = [].slice;
function extend () {
var consumer = arguments[0],
providers = __slice.call(arguments, 1),
key,
i,
provider;
for (i = 0; i < providers.length; ++i) {
provider = providers[i];
for (key in provider) {
if (Object.prototype.hasOwnProperty.call(provider, key)) {
consumer[key] = provider[key];
};
};
};
return consumer;
};
function methodsOfType (behaviour, list, type) {
return list.filter(function (methodName) {
return typeof(behaviour[methodName]) === type;
});
}
function propertyFlags (behaviour) {
var properties = [],
propertyName;
for (propertyName in behaviour) {
if (behaviour[propertyName] === null) {
properties.push(propertyName);
}
}
return properties;
}
var number = 0;
function encapsulate (behaviour) {
var safekeepingName = "__" + ++number + "__",
properties = Object.keys(behaviour),
methods = properties.filter(function (methodName) {
return typeof behaviour[methodName] === 'function';
}),
privateMethods = methods.filter(function (methodName) {
return methodName[0] === '_';
}),
publicMethods = methods.filter(function (methodName) {
return methodName[0] !== '_';
}),
dependencies = properties.filter(function (methodName) {
return isUndefined(behaviour[methodName]);
}),
methodsToProxy = publicMethods.concat(dependencies),
encapsulatedObject = {};
function createContext (methodReceiver) {
var innerProxy = partialProxy(methodReceiver, methodsToProxy);
privateMethods.forEach(function (methodName) {
innerProxy[methodName] = behaviour[methodName];
});
return Object.defineProperty(
innerProxy,
'self',
{ writable: false, enumerable: false, value: methodReceiver }
);
}
function getContext (methodReceiver) {
var context = methodReceiver[safekeepingName];
if (context == null) {
context = createContext(methodReceiver);
Object.defineProperty(methodReceiver, safekeepingName, {
enumerable: false,
writable: false,
value: context
});
}
return context;
}
publicMethods.forEach(function (methodName) {
var methodBody = behaviour[methodName];
Object.defineProperty(encapsulatedObject, methodName, {
enumerable: true,
writable: false,
value: function () {
var context = getContext(this),
result = methodBody.apply(context, arguments);
return (result === context) ? this : result;
}
});
});
dependencies.forEach(function (methodName) {
if (encapsulatedObject[methodName] == null) {
encapsulatedObject[methodName] = void 0;
}
});
return Object.preventExtensions(encapsulatedObject);
}
compose-metaobjects
function orderStrategy2 () {
if (arguments.length === 1) {
return arguments[0];
}
else {
var fns = __slice.call(arguments, 0);
return function composed () {
var args = arguments,
context = this,
values = fns.map(function (fn) {
return fn.apply(context, args);
}).filter(isntUndefined);
if (values.length > 0) {
return values[values.length - 1];
}
}
}
}
function propertiesToArrays (metaobjects) {
return metaobjects.reduce(function (collected, metaobject) {
var key;
for (key in metaobject) {
if (key in collected) {
collected[key].push(metaobject[key]);
}
else collected[key] = [metaobject[key]]
}
return collected;
}, Object.create(null))
}
function resolveUndefineds (collected) {
return Object.keys(collected).reduce(function (resolved, key) {
var values = collected[key];
if (values.every(isUndefined)) {
resolved[key] = undefined;
}
else resolved[key] = values.filter(isntUndefined);
return resolved;
}, {});
}
function applyProtocol(seed, resolveds, protocol) {
return Object.keys(resolveds).reduce( function (applied, key) {
var value = resolveds[key];
if (isUndefined(value)) {
applied[key] = value;
}
else if (value.every(isFunction)) {
applied[key] = protocol.apply(null, value);
}
else throw "Don't know what to do with " + value;
return applied;
}, seed);
}
function canBeMergedInto (object1, object2) {
var prototype1 = Object.getPrototypeOf(object1),
prototype2 = Object.getPrototypeOf(object2);
if (prototype1 === null) return prototype2 === null;
if (prototype2 === null) return true;
if (prototype1 === prototype2) return true;
return Object.prototype.isPrototypeOf.call(prototype2, prototype1);
}
// shim if allong.es.callLeft not available
var callLeft2 = (function () {
if (typeof callLeft == 'function') {
return callLeft;
}
else if (typeof allong === 'object' && typeof allong.es === 'object' && typeof\
allong.es.callLeft === 'function') {
return allong.es.callLeft;
}
else {
return function callLeft2 (fn, arg2) {
return function callLeft2ed (arg1) {
return fn.call(this, arg1, arg2);
};
};
}
})();
function seedFor (objectList) {
var seed = objectList[0] == null
? Object.create(null)
: Object.create(Object.getPrototypeOf(objectList[0])),
isCompatibleWithSeed = callLeft2(canBeMergedInto, seed);
if (!objectList.every(isCompatibleWithSeed)) throw 'incompatible prototypes';
return seed;
}
function composeMetaobjects () {
var metaobjects = __slice.call(arguments, 0),
arrays = propertiesToArrays(metaobjects),
resolved = resolveUndefineds(arrays),
seed = seedFor(metaobjects),
composed = applyProtocol(seed, resolved, orderStrategy2);
return composed;
}
newable
function Newable (optionalName, metaobject, optionalSuper) {
var name = typeof(optionalName) === 'string'
? optionalName
: '',
metaobject = typeof(optionalName) === 'string'
? metaobject
: optionalName,
superClazz = typeof(optionalName) === 'string'
? optionalSuper
: metaobject,
source = "(function " + name + " () { " +
"var r = constructor.apply(this, arguments); " +
"return r === undefined ? this : r; " +
"})",
clazz = eval(source),
constructor;
if (typeof(metaobject.constructor) === 'function' && typeof(optionalSuper) ===\
'function') {
constructor = function () {
optionalSuper.apply(this, arguments);
return metaobject.constructor.apply(this, arguments);
}
}
else if (typeof(metaobject.constructor) === 'function') {
constructor = metaobject.constructor;
}
else if (typeof(optionalSuper) === 'function') {
constructor = optionalSuper;
}
else constructor = function () {}
clazz.prototype = extend({}, metaobject);
delete clazz.prototype.constructor;
return clazz;
}
Inheritance
struct
function Struct () {
var name = arguments[0],
keys = [].slice.call(arguments, 1),
constructor = eval("(function "+name+"(argument) { return initialize.call(\
this, argument); })");
function initialize (argument) {
if (constructor.prototype.isPrototypeOf(this)) {
var argument = argument,
struct = this;
keys.forEach(function (key) {
Object.defineProperty(struct, key, {
enumerable: true,
writable: true,
value: argument[key]
});
});
return Object.preventExtensions(struct);
}
else return constructor.prototype.isPrototypeOf(argument);
};
constructor.assertIsPrototypeOf = function (argument) {
if (!constructor.prototype.isPrototypeOf(argument)) {
var name = constructor.name === ''
? "Struct(" + keys.join(", ") + ")"
: constructor.name;
throw "Type Error: " + argument + " is not a " + name;
}
else return argument;
}
return constructor;
}
Utility Functions
Throughout this book, we have used utility functions in our code snippets and examples. Here is a list of functions we’ve borrowed from other sources. Most are from underscore or allong.es.
var allong = require('allong.es');
var _ = require('underscore');
extend
extend can be found in the underscore library:
var extend = _.extend;
Or you can use this formulation:
var __slice = [].slice;
function extend () {
var consumer = arguments[0],
providers = __slice.call(arguments, 1),
key,
i,
provider;
for (i = 0; i < providers.length; ++i) {
provider = providers[i];
for (key in provider) {
if (provider.hasOwnProperty(key)) {
consumer[key] = provider[key];
};
};
};
return consumer;
};
pipeline
pipeline composes functions in the order they are applied:
var pipeline = allong.es.pipeline;
function square (n) { return n * n; }
function increment (n) { return n + 1; }
pipeline(square, increment)(6)
//=> 37
tap
tap passes a value to a function, discards the result, and returns the original value:
var tap = allong.es.tap;
tap(6, function (n) {
console.log("Hello there, " + n);
});
//=>
Hello there, 6
6
map
map applies function to an array, returning an array of the results:
var map = allong.es.map;
map([1, 2, 3], function (n) {
return n * n;
});
//=> [1, 4, 9]
variadic
variadic takes a function and returns a “variadic” function, one that collects arguments in an array and passes them to the last parameter:
var variadic = allong.es.variadic;
var a = variadic(function (args) {
return { args: args };
});
a(1, 2, 3, 4, 5)
//=> { args: [1, 2, 3, 4, 5] }
var b = variadic(function (first, second, rest) {
return { first: first, second: second, rest: rest };
});
b(1, 2, 3, 4, 5)
//=> { first: 1, second: 2, rest: [3, 4, 5] }
unvariadic
unvariadic takes a variadic function and turns it into a function with a fixed arity:
var unvariadic = allong.es.unvariadic;
function ensuresArgumentsAreNumbers (fn) {
return unvariadic(fn.length, function () {
for (var i in arguments) {
if (typeof(arguments[i]) !== 'number') {
throw "Ow! NaN!!"
}
}
return fn.apply(this, arguments);
});
}
function myAdd (a, b) {
return a + b;
}
var myCheckedAdd = ensuresArgumentsAreNumbers(myAdd);
myCheckedAdd.length
//=> 2
