scoto
v0.3.0
Published
Prototype-based scope concept
Downloads
5
Readme
Scototypes (Scoto)
Nestable, Testable, Isolable Scopes using Prototype chains
Since ECMAscript 3, scopes are a foundational element of JavaScript.
Concepts like closures depend on the automatic creation of severally-nested scopes.
Scopes are not without complications. They all chain up to the root
object.
They shadow variables. They can't be shared directly.
For most purposes, though, scopes are quite like prototypes.
Enter Scototypes
Scototypes provide the nesting and hierarchy of scopes using JavaScript prototypes. You can walk the hierarchy of a scototype, isolate the current, attach it to a different chain, or even flatten the hierarchy into a new object.
Scototypes can mimic regular scopes by starting with your root object:
const myScope = Scoto.child(window);
const nestedScope = Scoto.child(myScope);
nestedScope.console.log('This works!');
Or you can make isolated scopes:
const myIsolatedScope = Scoto.new(null);
const nestedScope = Scoto.child(myIsolatedScope);
// console is undefined.
nestedScope.console.log('This will error.');
Scototypes use a few simple prototype methods to support nesting scope or data inheritance. Forty lines of code and comments cover most of the examples:
// isolate ensures no inherited prototype.
const defaults = Scoto.isolate({
players: 1,
gameTime: 60000, // one minute
maxEnemies: 5
});
// rebase puts keys into a new object with a prototype chain
const appSettings = Scoto.rebase(getAppSettings(), defaults);
const userSettings = {
gameTime: 45000,
maxEnemies: 8
};
// rebase again to keep building the hierarchy
const currentSettings = Scoto.rebase(userSettings, appSettings);
// flatten merges the properties into a single object.
// Useful for exporting or saving.
const exportState = Scoto.flatten(currentSettings);
// child continues to build on a hierarchy.
const gameState = Scoto.child(currentSettings);
// Now we can assign additional keys to our state
gameState.enemies = [];
gameState.direction = 0;
gameState.started = true;
if (shouldStartDangerMode()) {
// We don't need to track previous state for the scototype.
// We just shadow the "older" values.
gameState.maxEnemies = 12;
}
if (shouldEndDangerMode()) {
// Deleting variables eliminates shadowing.
delete gameState.maxEnemies;
}
All methods in Scoto
are non-mutating.
Scototype Context
Since scototypes provide nested scopes, we can optionally bind them as contexts.
function submitNewScore(score) {
// Clear variable name for nesting.
const scoto = this;
if (undefined === scoto.scores) {
getScores.call(scoto)
.then(function(scores) {
// Push scores onto the current scoto
scoto.scores = scores;
// Call this function again with the current scoto
submitNewScore.call(scoto, score);
}.bind(scoto));
return;
}
http.post(`${scoto.baseURL}/score`, {score})
.then(receiveScoreResponse.bind(scoto), handleError.bind(scoto));
}
function receiveAddScoreResponse(data) {
const scoto = this;
scoto.scores = data.scores;
scoto.lastGameRank = data.gameRank;
updateSplashScreen.call(scoto);
}
Because scototypes are standard objects, you can bind them as a context with the native
Function.prototype.bind
and with convenience tools like Lo-dash _.bindAll.
Scoto
includes bind
and binder
as well. These provide the ability to create a new
nested context instead of binding the current Scoto
. binder
provides a sort of
partial applicaiton to allow easy reuse of a particular bind behavior.
function getMethodsWithContext(context, nest) {
return myMethods.map(Scoto.binder(context, nest));
}