Skip to content

Instantly share code, notes, and snippets.

@mklymyshyn
Created March 29, 2013 08:54
Show Gist options
  • Save mklymyshyn/5269616 to your computer and use it in GitHub Desktop.
Save mklymyshyn/5269616 to your computer and use it in GitHub Desktop.
(function (g) {
var VERSION = "0.1.1";
var PREFIX = "___scope_cleaner__";
// check is some variable is array or not
var isArray = function (obj) {
return obj.constructor === Array;
};
// wrapper on Array.indexOf for old browser
var inArray = function (arr, key) {
// use native implementation if possible
if (Array.indexOf) {
return arr.indexOf(key);
}
for (var i = 0, j = arr.length; i < j; i++) {
if (arr[i] === key) {
return i;
}
}
return -1;
};
// make each shortcut
var each = function (arr, func) {
for (var i = 0, j = arr.length; i < j; i++) {
var result = func(arr[i], i);
if (typeof result === "boolean" && result === false) {
break;
}
}
};
// build array hash
var hash = function (arr) {
return arr.join("_");
};
// anyone can override "undefined" in global scope
// here we use hack to set a "real" undefined to the variable
var reservedUndefined = (function (undef) {
return undef;
})();
// Initialization of Scope class
var Scope = function () {
this._storage = null;
this.prefix = PREFIX;
this.VERSION = VERSION;
return this;
};
// Defining methods for scope
Scope.prototype = {
// internal method to provide ID of current storage
storage: function () {
if (this._storage === null) {
this._storage = this.prefix +
Math.random().toString(36).substring(7);
g[this._storage] = [];
}
return this._storage;
},
noConflict: function () {
if (typeof g.__ScopePrev !== "undefined") {
g.Scope = g.__ScopePrev;
}
return new Scope();
},
// save state into special variable.
// It's possible to specify explicitly what global variables
// should be restored on ``Scope.restore()`` call
freeze: function (_keys) {
var undef = [];
var scope = [];
var globals = [];
var key;
var i;
var j;
var keys = _keys || [];
if (!isArray(g[this.storage()])) {
g[this.storage()] = [];
}
if (!isArray(keys)) {
keys = Array.prototype.slice.call(arguments);
}
for (i = 0, j = keys.length; i < j; i++) {
key = keys[i];
if (typeof g[key] === "undefined") {
undef.push(key);
continue;
}
scope.push([key, g[key]]);
}
// watcher
for (key in g) {
globals.push(key);
}
g[this.storage()].push([undef, scope, globals, []]);
},
keep: function (_keys) {
var keys = _keys || [];
var lastIndex = isArray(g[this.storage()]) ? g[this.storage()].length - 1 : -1;
if (!isArray(keys)) {
keys = Array.prototype.slice.call(arguments);
}
if (typeof g[this.storage()][lastIndex] === "undefined") {
return;
}
g[this.storage()][lastIndex][3].concat(keys);
},
// Restore state of the variables before .keep() call
restore: function (_keepKeys) {
var scopeList = g[this.storage()];
var i = 0;
var j = 0;
var key;
var value;
var keepKeys = _keepKeys || [];
if (!isArray(keepKeys)) {
keepKeys = Array.prototype.slice.call(arguments);
}
if (typeof scopeList === "undefined") {
return false;
}
var scope = g[this.storage()].pop();
if (typeof scope === "undefined") {
return false;
}
var data = scope[1], undef = scope[0], globals = scope[2];
keepKeys = keepKeys.concat(scope[3]);
var keepVar = function (key) {
return inArray(keepKeys, key) !== -1;
};
// restore state of keeped variables
for (i = 0, j = data.length; i < j; i++) {
key = data[i][0];
value = data[i][1];
if (keepVar(key)) continue;
g[key] = value;
}
// set scope variables to undefined
for (i = 0, j = undef.length; i < j; i++) {
if (keepVar(undef[i])) continue;
if (typeof g[undef[i]] !== "undefined") {
if (!delete(g[undef[i]])) {
g[undef[i]] = reservedUndefined;
}
}
}
// cleanup variables which appears in global scope
// after code execution
for (key in g) {
if (keepVar(key)) continue;
if (inArray(globals, key) === -1) {
try {
delete g[key];
} catch(e) {}
g[key] = reservedUndefined;
}
}
return true;
},
// Scope.keep() may be called several times. In this
// case it behave like Semaphore and you should
// call equal amount Scope.restore(). Or you can
// call Scope.restoreAll() and it will restore
// state which was before first Scope.keep() call
restoreAll: function (keepKeys) {
do {} while (this.restore(keepKeys) === true);
},
// Display difference in variables before and after
// execution of some code (actually before Scope.keep() call
// and call of State.state())
state: function () {
// Display current state of the scope
var globals = [];
var differences = [];
var key;
var part = [];
var i, j, n, m;
var report = "";
var displayReport = function (_msg) {
var msg = "Scope state report: " + _msg;
if (console) {
console.log(msg);
return;
}
alert(msg);
};
if (g[this.storage()].length === 0) {
displayReport("Scope not used yet or already restored.");
return;
}
// collect all variables from all globals
for (i = 0, j = g[this.storage()].length; i < j; i++) {
var gs = g[this.storage()][i][2];
for (n = 0, m = gs.length; n < m; n++) {
if (inArray(globals, gs[n]) === -1) {
globals.push(gs[n]);
}
}
}
for (key in g) {
if (inArray(globals, key) === -1 && key.indexOf(PREFIX) === -1) {
differences.push(key);
}
}
displayReport("Variables which haven't exist before execution:\n" +
differences.join(", "));
}
};
// this one to have noConflict functionality
if (typeof g.Scope !== "undefined") {
g.__ScopePrev = g.Scope;
}
g.Scope = new Scope();
})(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment