by default __defineGetter__ support only one function. Same for __defineSetter
This is a annoying limitation. This little library declares 2 functions
Object.__defineQGetter__ and Object.__defineQGetter__.
They behave the same as their native sibling but support multiple functions.
Those functions are called in the same order they got registered.
(I have no idea of the reasoning behind this limitation to one function. It seems
useless to me. This remind me of onclick of the DOM instead of a proper .addEventListener)
Class to implement queueable getter/setter
parameter: {Object} baseObject The base object on which we operate
parameter: {String} property The string of property
var QGetterSetter = {};
Define a getter/setter for a property
parameter: {Object} baseObject the base object which is used
parameter: {String} property the name of the property
QGetterSetter.Property = function(baseObject, property){
// sanity check
console.assert( typeof(baseObject) === 'object' );
console.assert( typeof(property) === 'string' );
// backup the initial value
var originValue = baseObject[property];
// init some local variables
var _this = this;
this._getters = [];
this._setters = [];
// define the root getter
baseObject.__defineGetter__(property, function getterHandler(){
var value = baseObject['__'+property];
for(var i = 0; i < _this._getters.length; i++){
// TODO why those extra param are needed// - needed for privateforjs to identify the origin// - is that the proper format ?// - is that important for setter
value = _this._getters[i](value, getterHandler.caller, property)
}
return value;
});
// define the root setter
baseObject.__defineSetter__(property, function(value){
for(var i = 0; i < _this._setters.length; i++){
value = _this._setters[i](value)
}
baseObject['__'+property] = value;
});
// set the originValue
baseObject['__'+property] = originValue;
};// export the class in node.js - if running in node.js
if( typeof(window) === 'undefined' ) module.exports = QGetterSetter;
define a getter
parameter: {Obejct} baseObject the object containing the property
parameter: {string} property the property name which gonna get the getter
parameter: {Function} getterFn function which handle the getter
QGetterSetter.defineGetter = function(baseObject, property, getterFn){
var name = "__dbgGetSet_" + property;
// init QGetterSetter for this property if needed
baseObject[name]= baseObject[name] || new QGetterSetter.Property(baseObject, property);
// setup the new getter
baseObject[name]._getters.push(getterFn)
}
define a setter
parameter: {Object} baseObject the object containing the property
parameter: {string} property the property name which gonna get the setter
parameter: {Function} setterFn function which handle the setter
QGetterSetter.defineSetter = function(baseObject, property, setterFn){
var name = "__dbgGetSet_" + property;
// init QGetterSetter for this property if needed
baseObject[name]= baseObject[name] || new QGetterSetter.Property(baseObject, property);
// setup the new setter
baseObject[name]._setters.push(setterFn)
}
parse stacktrace for v8 - works in node.js and chrome
function _parserV8(error){
// start parse the error stack string
var lines = error.stack.split("\n").slice(1);
var stacktrace = [];
lines.forEach(function(line){
if( line.match(/\(native\)/) ){
var matches = line.match(/^\s*at (.+) \((.+):(\d+):(\d+)\)/);
stacktrace.push(new Stacktrace.Frame({
fct : matches[1],
url : matches[2],
line : parseInt(matches[3], 10),
column : parseInt(matches[4], 10)
}));
}else{
var matches = line.match(/^\s*at (.+):(\d+):(\d+)/);
stacktrace.push(new Stacktrace.Frame({
url : matches[1],
fct : '<anonymous>',
line : parseInt(matches[2], 10),
column : parseInt(matches[3], 10)
}));
}
});
return stacktrace;
};
parse the stacktrace from firefox
function _parserFirefox(error){
// start parse the error stack string
var lines = error.stack.split("\n").slice(0, -1);
var stacktrace = [];
lines.forEach(function(line){
var matches = line.match(/^(.*)@(.+):(\d+)
Dump current state of the tracker in console.log().
see: Stacktrace.Tracker.reportString()
Stacktrace.Tracker.prototype.dump = function(){
var report = this.reportString.apply(this, arguments)
console.log(report);
}
Reporter in a String
parameter: {RegExp} classNameRegExp regexp of the classname to keep
parameter: {Number} maxNOrigin nb origin to display per class
Stacktrace.Tracker.prototype.reportString = function(classNameRegExp, maxNOrigin){
// handle polymorphism
classNameRegExp = classNameRegExp || /./;
maxNOrigin = maxNOrigin !== undefined ? maxNOrigin : 3;
// define local variable
var output = [];
var classNames = Object.keys(this._klasses);
// sort classes by descending .counter
classNames.sort(function(a, b){
return this._klasses[b].counter - this._klasses[a].counter;
}.bind(this));
// filter by classname
classNames = classNames.filter(function(className){
return className.match(classNameRegExp) ? true : false
});
// display the rest
classNames.forEach(function(className){
var klass = this._klasses[className];
output.push(className+': total '+klass.counter+' times');
var perOrigins = klass.perOrigins;
var ranks = Object.keys(perOrigins);
ranks.sort(function(a, b){
return perOrigins[b] - perOrigins[a];
});
//console.dir(aClass._newCounters)//console.log('ranks', ranks)
ranks.slice(0, maxNOrigin).forEach(function(originId){
var perOrigin = perOrigins[originId];
output.push('\t'+originId+' - '+perOrigin+' times')
//console.log(counters[origin], "new aClass at ", origin);
});
}.bind(this));
return output.join('\n');
};
assert which actually try to stop the excecution
if debug.assert.useDebugger is falsy, throw an exception. else trigger the
debugger. It default to false. unclear how usefull it is for node.js
to overload console.assert just do ```console.assert = assertWhichStop;```.
Based on gist.github.com/2651899 done with jensarps.
parameter: {Boolean} condition the condition which is asserted
parameter: {String} message the message which is display is condition is falsy
parameter: {Boolean} [useDebugger] if true, a failled assert will trigger js debugger
if true, a fail assert will trigger js debugger
@type {Boolean}
assertWhichStop.useDebugger = false;
// export the class in node.js - if running in node.js - unclear how usefull it is in node.js
if( typeof(window) === 'undefined' ) module.exports = assertWhichStop;
Little helper to overload console.assert
@api public
@todo a .offConsoleAPI() or noConflict() which restore it
formater which doesnt change anything
parameter: {Array} args the ```arguments``` of the logger funciton
parameter: {Stacktrace.Frame} stackFrame the stackframe of the origin
parameter: {string} severity severity of the message
returns: {Array} the formated args
formater which add a timestamp as prefix to the message - with color if in node
parameter: {Array} args the ```arguments``` of the logger funciton
parameter: {Stacktrace.Frame} stackFrame the stackframe of the origin
parameter: {string} severity severity of the message
returns: {Array} the formated args
ConsoleLogger.formatterTimeStamp = function(args, stackFrame, severity){
// build prefix with time
var present = new Date();
var prefixColor = ConsoleLogger._formatterSeverityColor(severity);
var prefix = prefixColor + pad(present.getHours(),2)
+ ':'
+ pad(present.getMinutes(),2)
+ ':'
+ pad(present.getSeconds(),2)
+ ConsoleLogger._formatterColor.reset;
// convert arguments into actual Array
args = Array.prototype.slice.call(args, 0);
// prepend the prefix
args.unshift(prefix);
// return the result
return args;
function pad(val, len) {
val = String(val);
while(val.length < len) val = "0" + val;
return val;
};};
formater which add the origin as prefix to the message - with color if in node
parameter: {Array} args the ```arguments``` of the logger funciton
parameter: {Stacktrace.Frame} stackFrame the stackframe of the origin
parameter: {string} severity severity of the message
returns: {Array} the formated args
ConsoleLogger.formatterOrigin = function(args, stackFrame, severity)
{
// compute prefix
var prefixColor = ConsoleLogger._formatterSeverityColor(severity);
var prefix = prefixColor + stackFrame.originId() + ConsoleLogger._formatterColor.reset;
// convert arguments into actual Array
args = Array.prototype.slice.call(args, 0);
// prepend the prefix
args.unshift(prefix);
// return the result
return args;
}
flag to know if it is running in node.js or browser
@type {Boolean}
push a new filter - stackFrame
parameter: {Function(stackFrame)} validFor function which determine which stackFrame is valid
parameter: {[type]} severity [description]
returns: {[type]} [description]
test if a given instance of severity and stackframe is valid for ConsoleLogger
parameter: {string} severity Severity to test
parameter: {Object} stackFrame stackframe to test (in stacktrace.js format)
returns: {Boolean} true if the filter is valid, false otherwise
ConsoleLogger.filter = function(severity, stackFrame){
stackFrame = stackFrame || Stacktrace.parse()[2];
// sanity check - sanity level MUST be defined
console.assert(Object.keys(ConsoleLogger.Severity).indexOf(severity) !== -1, 'unknown severity level');
// go thru all the filters
var filters = ConsoleLogger._filters;
for(var i = 0; i < filters.length; i++){
var filter = filters[i];
if( filter.validFor(stackFrame) === false ) continue;
return ConsoleLogger.Severity[severity] >= filter.severity ? true : false;
}
// if this point is reach, check with Severity.dfl
return ConsoleLogger.Severity[severity] >= ConsoleLogger.Severity.dfl ? true : false;
}
detect gabarge collector occurance. Require v8 for now, so chrome or node.js.
typical usage: <code>new GcMonitor().start();</code>
@class monitor gabage collector activities
see details in http://buildnewgames.com/garbage-collector-friendly-code/
var GcMonitor = function(){
// init a timer
var _this = this;
var timerid = null;
// define function to return used heap size
var inNode = typeof(window) === 'undefined' ? true : false;
var usedHeapSize= inNode ? function(){
return process.memoryUsage().heapUsed;
} : function(){
if( !window.performance || !window.performance.memory ) return 0;
return window.performance.memory.usedJSHeapSize;
};
// sanity check - if not available, output a warning
if( GcMonitor.isAvailable() === false ){
// open -a "/Applications/Google Chrome.app" --args --enable-memory-info
console.warn('memory info are unavailable... for chrome, use --enable-memory-info. Other browsers dont have this feature.')
}
Start monitoring periodically
parameter: {Function|undefined} onChange optional function to notify when gc occurs
parameter: {Number|undefined} period period of the check. default to 50ms
this.start = function(onChange, period){
period = period || 1000/60;
onChange= onChange || function(delta){
function bytesToSize(bytes, nFractDigit) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) return '0';
nFractDigit = nFractDigit !== undefined ? nFractDigit : 2;
var precision = Math.pow(10, nFractDigit);
var i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes*precision / Math.pow(1024, i))/precision + ' ' + sizes[i];
};
console.warn(new Date + " -- GC occured! saved", bytesToSize(delta), 'consuming at', bytesToSize(burnRate), 'per second')
}
timerid = setInterval(function(){
_this.check(onChange);
}, period);
return this; // for chained api
};
Stop monitoring periodically
this.stop = function(){
cancelInterval(timerid);
};
var lastUsedHeap = null;
var lastTimestamp = null;
var burnRate = null;
Check if currently used memory is less than previous check. If so it
is assume a GC occured
parameter: {Function|undefined} onChange callback notified synchronously if gc occured
this.check = function(onChange){
// parameter polymorphism
onChange = onChange || function(property){}
var present = Date.now();//console.log('present', (present - lastTimestamp)/1000)
var currUsedSize= usedHeapSize();
if( lastUsedHeap === null ){
lastUsedHeap = currUsedSize;
lastTimestamp = present;
return;
}
// check if the heap size in this cycle is LESS than what we had last
// cycle; if so, then the garbage collector has kicked in
var deltaMem = currUsedSize - lastUsedHeap;
if( deltaMem < 0 ){
onChange(-deltaMem, burnRate);
}else{
var deltaTime = present - lastTimestamp
var newBurnrate = deltaMem / (deltaTime/1000);
if( burnRate === null ) burnRate = newBurnrate;
var friction = 0.99;
burnRate = burnRate * friction + newBurnrate * (1-friction);
}
lastUsedHeap = currUsedSize;
lastTimestamp = present;
}
var GlobalDetector = function(){
// take the namespace for global
var inBrowser = typeof(window) !== 'undefined' ? true : false
var _global = inBrowser ? window : global;
// sanity check - a global namespace MUST be found
console.assert( _global, 'failed to find a global namespace! bailing out!' );
// init initialGlobals
var initialGlobals = {};
// loop on _global _global object
for(var propname in _global){
initialGlobals[propname] = true;
}
// init foundGlobals
var foundGlobals= {};
// init a timer
var _this = this;
var timerid = null;
Start periodically monitoring
parameter: {Function+} onChange optional callback called when a new global is found
parameter: {Number+} period period at which it is checked
this.start = function(onChange, period){
period = period || 1000;
onChange= onChange || function(newProperty){
var str = new Date + " -- Warning Global Detected!!! "
str += (inBrowser ? 'window': 'global');
str += "['"+newProperty+"'] === " + _global[newProperty];
console.warn(str)
};
timerid = setInterval(function(){
_this.check(onChange);
}, period);
return this; // for chained API
};
Check if any new global has been declared
parameter: {Function+} onChange optional callback called synchronously if a new global is found
returns: {Boolean} true if some new globals have been detected, false otherwise
this.check = function(onChange){
var newGlobal = false;
// parameter polymorphism
onChange = onChange || function(property){}
// new loop on _global object
for(var property in _global){
// if it is in the initialGlobals, goto the next
if( initialGlobals[property] ) continue;
// if this is already in the ignoreList, goto the next
if( GlobalDetector.ignoreList.indexOf(property) !== -1 ) continue;
// if it already found, goto the next
if( foundGlobals[property] ) continue;
// mark this property as init
foundGlobals[property] = true;
// mark newGlobal
newGlobal = true;
// notify callback
onChange(property);
}
return newGlobal;
};
getter for the foundGlobals up to now
returns: {Object} object with keys as property names
list of variables name to ignore. populated at constructor() time
@type {String[]}
GlobalDetector.ignoreList = [];
// export the class in node.js - if running in node.js
if( typeof(window) === 'undefined' ) module.exports = GlobalDetector;
ensure private property/method stays private
@namespace Strong typing for javascript
var PrivateForJS = {};
// include dependancies
var Stacktrace = Stacktrace || require('./stacktrace.js');
var QGetterSetter = QGetterSetter || require('./qgettersetter.js')
// export the namespace in node.js - if running in node.js
if( typeof(window) === 'undefined' ) module.exports = PrivateForJS;
determine which function is considered private for klass
parameter: {function} klass the constructor of the class
parameter: {object|function|function[]} [source] private functions - if Function, use it directly.
if Array.<function>, then each item is a private function
if object, then each of its property which is a function is private
if undefined, use klass.prototype which trigger the Object case
PrivateForJS.pushPrivateOkFn = function(klass, source){
// if source isnt provided, use klass.prototype
if( source === undefined ) source = klass.prototype;
// init ._privateOkFn if needed
klass._privateOkFn = klass._privateOkFn || [];
// handle various case of source
if( typeof(source) === 'function' ){
klass._privateOkFn.push(source)
}else if( typeof(source) === 'object' ){
Object.keys(source).forEach(function(key){
var val = source[key];
if( typeof(val) !== 'function' ) return;
klass._privateOkFn.push(val);
});
}else if( source instanceof Array ){
source.forEach(function(fn){
console.assert( typeof(val) !== 'function' );
klass._privateOkFn.push(fn);
});
}else console.assert(false);
}
return the functions allowed to access private values
parameter: {Function} klass the class to query
returns: {Array.} return the function
PrivateForJS.getPrivateOkFn = function(klass){
// init ._privateOkFn if needed
klass._privateOkFn = klass._privateOkFn || [];
// return current ones
return klass._privateOkFn;
}
define a private property on a given instance of a object class
parameter: {Function} klass the class of the intenciated object
parameter: {Object} baseObject the object instance
parameter: {String} property the property name
returns: {undefined} nothing
PrivateForJS.privateProperty = function(klass, baseObject, property){
// @TODO should i put a setter too ?
QGetterSetter.defineGetter(baseObject, property, function aFunction(value, caller, property){
// generate privateOkFns if needed - functions which can access private properties
klass._privateOkFn = klass._privateOkFn || PrivateForJS.pushPrivateOkFn(klass);
// if caller not privateOK, notify the caller
var privateOkFns= klass._privateOkFn;
if( privateOkFns.indexOf(caller) === -1 ){
// get stackFrame for the originId of the user
var stackFrame = Stacktrace.parse()[2];
// log the event
console.assert(false, 'access to private property', "'"+property+"'", 'from', stackFrame.orginId());
}
// actually return the value
return value;
});
};
define a private function
parameter: {Function} klass the class of the intenciated object
parameter: {Function} fn the function to overload
returns: {Function} the overloaded function
PrivateForJS.privateFunction = function(klass, fn){
// MUST NOT use .bind(this) as it change the .caller value
var _this = this;
return function _checkPrivateFunction(){
// get caller
var caller = _checkPrivateFunction.caller;
// generate privateOkFns if needed - functions which can access private properties
klass._privateOkFn = klass._privateOkFn || PrivateForJS.pushPrivateOkFn(klass);
// if caller not privateOK, notify the caller
var privateOkFns= klass._privateOkFn;
if( privateOkFns.indexOf(caller) === -1 ){
// get stackFrame for the originId of the user
var stackFrame = Stacktrace.parse()[1];
// log the event
console.assert(false, 'access to private property', "'"+property+"'", 'from', stackFrame.orginId());
}
// forward the call to the original function
return fn.apply(_this, arguments);
};};
var TypeCheck = {};
// dependancy
var QGetterSetter = QGetterSetter || require('../src/qgettersetter.js')
// export the namespace in node.js - if running in node.js
if( typeof(window) === 'undefined' ) module.exports = TypeCheck;
Check type with a object setter
parameter: {Object} baseObject the base object which contains the property
parameter: {String} property the string of the property name
parameter: {Array} types the allows tipe
TypeCheck.setter = function(baseObject, property, types){
// check initial value
var value = baseObject[property];
var isValid = TypeCheck.value(value, types)
console.assert(isValid, 'initial value got invalid type');
// setup the setter
QGetterSetter.defineSetter|(baseObject, property, function(value){
// check the value type
var isValid = TypeCheck.value(value, types);
console.assert(isValid, 'invalid type');
// return the value
return value;
});
};
function wrapper to check the type of function parameters and return value
parameter: {Function} originalFn the function to wrap
parameter: {Array} paramsTypes allowed types for the paramter. array with each item is the allowed types for this parameter.
parameter: {Array} returnTypes allowed types for the return value
returns: {boolean} return isValid, so true if types matche, false otherwise
TypeCheck.fn = function(originalFn, paramsTypes, returnTypes){
return function(){
// check parameters type
console.assert(arguments.length <= paramsTypes.length, 'funciton received '+arguments.length+' parameters but recevied only '+returnTypes.length+'!');
for(var i = 0; i < paramsTypes.length; i++){
var isValid = TypeCheck.value(arguments[i], paramsTypes[i]);
console.assert(isValid, 'argument['+i+'] type is invalid. MUST be of type', paramsTypes[i], 'It is ===', arguments[i])
}
// forward the call to the original function
var result = originalFn.apply(this, arguments);
// check the result type
var isValid = TypeCheck.value(result, returnTypes);
console.assert(isValid, 'invalid type for returned value. MUST be of type', returnTypes, 'It is ===', result);
// return the result
return result;
}
}
Check the type of a value
parameter: {*} value the value to check
parameter: {Array.} types the types allowed for this variable
returns: {boolean} return isValid, so true if types matche, false otherwise
TypeCheck.value = function(value, types){
// handle parameter polymorphism
if( types instanceof Array === false ) types = [types];
// if types array is empty, default to ['always'], return true as in valid
if( types.length === 0 ) return true;
// go thru each type
var result = false;
for(var i = 0; i < types.length; i++){
var type = types[i];
if( type === Number ){
var valid = typeof(value) === 'number';
}else if( type === String ){
var valid = typeof(value) === 'string';
}else if( typeof(type) === 'string' && type.toLowerCase() === 'always' ){
var valid = true;
}else if( typeof(type) === 'string' && type.toLowerCase() === 'never' ){
// return immediatly as a failed validator
return false;
}else if( typeof(type) === 'string' && type.toLowerCase() === 'nonan' ){
var valid = value === value;
if( valid === false ) return false;
continue; // continue as it is a validator
}else if( type instanceof TypeCheck._ValidatorClass ){
var valid = type.fn(value);
if( valid === false ) return false;
continue; // continue as it is a validator
}else {
var valid = value instanceof type;
}
result = result || valid;
}
// return the just computed result
return result;
}
Validator creator. a Validator is a function which is used to validate .value().
All the validators MUST be true for the checked value to be valid.
parameter: {Function(value)} fn function which return true if value is valid, false otherwise
TypeCheck.Validator = function(fn){
return new TypeCheck._ValidatorClass(fn)
}
Internal class to be recognisable by TypeCheck.value()
parameter: {Function} fn function which return true if value is valid, false otherwise
Function attribute creator
returns: {FunctionAttr.Builder} a FunctionAttr.Builder builder
FunctionAttr.define = function(originalFn, fnName){
return new FunctionAttr.Builder(originalFn, fnName)
}
Wrap a function between 2 functions
parameter: {Function} originalFn the original function
parameter: {Function} beforeFn the function to call *before* the original function
parameter: {Function} afterFn the function to call *after* the original function
returns: {Function} The modified function
FunctionAttr.wrapCall = function(originalFn, beforeFn, afterFn){
return function(){
var stopNow = false;
// call beforeFn if needed
if( beforeFn ) stopNow = beforeFn(originalFn, arguments);
// forward the call to the original function
var result = originalFn.apply(this, arguments);
// call afterFn if needed
if( afterFn ) afterFn(originalFn, arguments, result);
// return the result
return result;
}
}
Internal class to build the attributes on the funciton
@constructor
parameter: {Function} originalFn the function on which the attributes are set
parameter: {String} fnName optional name of the function - default to 'aFunction'
mark the end of the attributes declaration
returns: {Function} The actual function with the attributes
FunctionAttr.Builder.prototype.done = function(){
return this._currentFn;
}
// export the class in node.js - if running in node.js
if( typeof(window) === 'undefined' ) module.exports = FunctionAttr;
display a message with a timestamp every time the function is used
returns: {string} message optional message to display
FunctionAttr.Builder.prototype.timestamp = function(message){
this._currentFn = FunctionAttr.wrapCall(this._currentFn, function(){
console.log(''+ new Date + ': '+this._fnName+' being called');
}.bind(this));
return this; // for chained API
};
log a message when the function is call
parameter: {string} message the message to display
FunctionAttr.Builder.prototype.log = function(message){
this._currentFn = FunctionAttr.wrapCall(this._currentFn, function(){
console.log(message);
});
return this; // for chained API
};
mark the function as deprecated - aka you can use it but it will disapears soon
parameter: {string} message the optional message to provide
returns: {FunctionAttr.Builder} for chained API
FunctionAttr.Builder.prototype.deprecated = function(message){
var used = false;
this._currentFn = FunctionAttr.wrapCall(this._currentFn, function(){
if( used ) return;
used = true;
console.warn(message || "Deprecated function "+this._fnName+" called. Please update your code.");
}.bind(this));
return this; // for chained API
}
mark the function as obsolete
parameter: {string} message obsolete message to display
returns: {FunctionAttr.Builder} for chained API
FunctionAttr.Builder.prototype.obsolete = function(message){
var used = false;
this._currentFn = FunctionAttr.wrapCall(this._currentFn, function(){
if( used ) return;
used = true;
console.assert(false, message || "Obsoleted function "+this._fnName+" called. Please update your code.");
}.bind(this));
return this; // for chained API
}
hook a function be be caller before the actual function
parameter: {Function} beforeFn the function to call
returns: {FunctionAttr.Builder} for chained API
FunctionAttr.Builder.prototype.before = function(beforeFn){
this._currentFn = FunctionAttr.wrapCall(this._currentFn, beforeFn, null);
return this; // for chained API
};
hook a function to be called after the actual function
parameter: {Function} afterFn the function to be called after
returns: {FunctionAttr.Builder} for chained API
FunctionAttr.Builder.prototype.after = function(afterFn){
this._currentFn = FunctionAttr.wrapCall(this._currentFn, null, afterFn);
return this; // for chained API
};
Warp the function call in a console.time()
parameter: {String} label the label to use for console.time(label)
returns: {FunctionAttr.Builder} for chained API
Warp the funciton call in console.profile()/.profileEnd()
parameter: {String} label label to use for console.profile()
returns: {FunctionAttr.Builder} for chained API
Trigger the debugger when the function is called
parameter: {Function} originalFn the original function
parameter: {Function} [conditionFn] this function should return true, when the breakpoint should be triggered. default to function(){ return true; }
returns: {FunctionAttr.Builder} for chained API
FunctionAttr.Builder.prototype.breakpoint = function(fn, conditionFn){
conditionFn = conditionFn || function(){ return true; };
this._currentFn = function(){
var stopNow = conditionFn();
// if stopNow, trigger debugger
if( stopNow === true ) debugger;
// forward the call to the original function
return this._currentFn.apply(this, arguments);
}.bind(this);
return this;
}
check function type as in ```TypeCheck.fn``` from typecheck.js
parameter: {Array} paramsTypes allowed types for the paramter. array with each item is the allowed types for this parameter.
parameter: {Array} returnTypes allowed types for the return value
returns: {FunctionAttr.Builder} for chained API
var Stacktrace = Stacktrace || require('../src/stacktrace.js');
// create the tracker for .trackUsage
FunctionAttr.usageTracker = new Stacktrace.Tracker();
track where this property is used (getter and setter)
parameter: {String|undefined} trackName optional name for Stacktrace.Tracker. default to originId
returns: {FunctionAttr.Builder} for chained API
FunctionAttr.Builder.prototype.trackUsage = function(trackName){
var tracker = FunctionAttr.usageTracker;
// handle polymorphism
trackName = trackName || 'FunctionAttr.trackUsage:'+Stacktrace.parse()[1].originId();
// actually record the usage
this._currentFn = FunctionAttr.wrapCall(this._currentFn, function(){
tracker.record(trackName, 1);
});
// for chained API
return this;
}
Define a property attribute
parameter: {Object} baseObject the base object to which the property belong
parameter: {String} property the name of the property
returns: {PropertyAttr} builder for property attributes
PropertyAttr.define = function(baseObject, property){
return new PropertyAttr.Builder(baseObject, property);
};
Constructor
parameter: {Object} baseObject the base object to which the property belong
parameter: {String} property the name of the property
PropertyAttr.Builder = function(baseObject, property){
// sanity check
console.assert(typeof(baseObject) === 'object');
console.assert(typeof(property) === 'string');
// set local values
this._baseObject= baseObject;
this._property = property;
}
// export the class in node.js - if running in node.js
if( typeof(window) === 'undefined' ) module.exports = PropertyAttr;
var QGetterSetter = QGetterSetter || require('../src/qgettersetter.js');
var Stacktrace = Stacktrace || require('../src/stacktrace.js');
// create the tracker for .trackUsage
PropertyAttr.usageTracker = new Stacktrace.Tracker();
track where this property is used (getter and setter)
parameter: {String|undefined} trackName optional name for Stacktrace.Tracker. default to originId
returns: {PropertyAttr.Builder} for chained API
var PrivateForJS = PrivateForJS || require('../src/privateforjs.js');
Mark this property as private
parameter: {Function} klass the class to which it is private
returns: {PropertyAttr.Builder} for chained API
PropertyAttr.Builder.prototype.private = function(klass){
PrivateForJS.privateProperty(klass, this._baseObject, this._property);
return this; // for chained API
};
// allocationtracker-monitor.js in examples/js
monitor for allocation tracker. It open another window and display
the AllocationTracker.reportString() in it
typical usage: new AllocationTrackerMonitor().start();
var AllocationTrackerMonitor = function(){
this._timerId = null;
}
AllocationTrackerMonitor.prototype.start = function(){
// content of the other frame
var content = [
"<!doctype html>",
"<h1>AllocationTracker.js Reports</h1>",
"Date: <span id='reportTime'></span>",
"<pre id='report'></pre>",
"<script>",
" window.addEventListener('message', function(event){",
" document.getElementById('reportTime').innerText = new Date().toString();",
" document.getElementById('report').innerText = event.data;",
" },false);",
"<\/script> "
].join('\n');
// build the url for the other frame
var url = "data:text/html;base64,"+window.btoa(content);
// open a window and report current allocation
var myPopup = window.open(url,'newtab');
setInterval(function(){
var reportString = AllocationTracker.reportString();
myPopup.postMessage(reportString,'*'); //send the message and target URI
// reset counters every time
AllocationTracker.reset();
}, 1000);
}
vuemeter on gc activity - stats.js like
- union of GcMonitorStatsCollection/GcMonitorStatsBurnRate
var GcMonitorStats = function (){
// build domElement container
var container = document.createElement('div');
// instanciate GcMonitorStatsBurnRate
var statsBurnRate = new GcMonitorStatsBurnRate();
container.appendChild(statsBurnRate.domElement);
// instanciate GcMonitorStatsCollection
var statsCollection = new GcMonitorStatsCollection();
container.appendChild(statsCollection.domElement);
// return domElement and update()
return {
domElement : container,
update : function(){
statsCollection.update();
statsBurnRate.update();
}
};
}
dump trackUsage() from globaldetector.js.
GlobalDetector.prototype.usageTrackerCode = function(){
// for each foundGlobals
var foundGlobals = this.foundGlobals();
// build the output
var output = '/* Include PropertyAttr.js before. */\n';
output += '/* globalDetector.usageTrackerDump() to dump usage records of all tracked properties */\n';
// add tracking code for each foundGlobals
Object.keys(foundGlobals).forEach(function(globalName){
// take the namespace for global
var inBrowser = typeof(window) !== 'undefined' ? true : false
var _global = inBrowser ? window : global;
var globalStr = inBrowser ? 'window': 'global';
// use functionattr.js if it is a function, propertyattr.js otherwise
var cmd = "PropertyAttr.define("+globalStr+", '"+globalName
+"').trackUsage('"+globalStr+"."+globalName+"');";
output += cmd+'\n';
})
// return the output
return output;
};
display .usageTrackerCode() in a new popup window
GlobalDetector.prototype.usageTrackerCodeWindow = function(){
var codeStr = this.usageTrackerCode();
var url = 'data:text/plain,' + codeStr;
window.open(url);
};
display .usageTrackerCode() in the javascript console
GlobalDetector.prototype.usageTrackerCodeConsole = function(){
var codeStr = this.usageTrackerCode();
console.log(codeStr)
};
dump the usage
see: Stacktrace.Tracker.reportString
Implement a Object Pool
- only track the free objects
- TODO implement one which track used object too
- which allocation is never released, from where
@class
parameter: {Function} klass the constructor of the class used in the pool
var ObjectPool = function(klass){
var _pool = new Array();
return {
reset : function() { _pool = []; },
put : function(obj) { _pool.push(obj); },
get : function(){
if( _pool.length === 0 ) this.grow();
return _pool.pop();
},
grow : function(){
_pool.push(new klass());
}
};};
mixin an ObjectPool into a class
parameter: {Function} klass constructor function of the class
ObjectPool.mixin = function(klass){
var pool = new ObjectPool(klass)
//
// * with microevent.js
// * where did you get it ?
// * where have i seen this ?
// * github.com/playcraft/gamecore.js/blob/master/src/pooled.js#L483
klass.create = function(){
var obj = pool.get();
klass.prototype.constructor.apply(obj, arguments)
return obj;
}
klass.prototype.release = function(){
pool.put(this);
}
};
// export the class in node.js - if running in node.js
if( typeof(window) === 'undefined' ) module.exports = ObjectPool;
var PrivateForJS = PrivateForJS || require('../../src/privateforjs.js');
Privatize all property/function which start with a _
parameter: {Function} klass constructor for the class
parameter: {object} baseObject the instance of the object
PrivateForJS.privatize = function(klass, baseObject){
console.assert( baseObject.constructor == klass );
console.assert( baseObject instanceof klass );
// TODO what about the .prototype
for(var property in baseObject){
var value = baseObject[property]
if( property[0] !== '_' ) continue;
if(!baseObject.hasOwnProperty(property))continue;
PrivateForJS.privateProperty(klass, baseObject, property);
}
};
var FunctionAttr = FunctionAttr || require('../../src/functionattr.js');
plugin for functionattr.js
parameter: {Function} klass constructor for the class
returns: {FunctionAttr.Builder} for chained API
FunctionAttr.Builder.prototype.privatize = function(klass){
var _this = this;
this._currentFn = function(){
// call the function
var value = _this._currentFn.apply(this, arguments);
// privatize
PrivateForJS.privatize(klass, this);
// actually return the value
return value;
};
return this; // for chained API
};
var Stacktrace = Stacktrace || require('../../src/stacktrace.js');
var QGetterSetter = QGetterSetter || require('../../src/qgettersetter.js');
(function(){
var _global = typeof(window) === 'undefined' ? global : window;
Same as __LINE__ in C
QGetterSetter.defineGetter(_global, '__LINE__', function(){
var stacktrace = Stacktrace.parse();
var stackFrame = stacktrace[2];
return stackFrame.line
});
Same as __LINE__ in C
QGetterSetter.defineGetter(_global, '__FILE__', function(){
var stacktrace = Stacktrace.parse();
var stackFrame = stacktrace[2];
return stackFrame.basename();
});
Same as __FUNCTION__ in C
QGetterSetter.defineGetter(_global, '__FUNCTION__', function(){
var stacktrace = Stacktrace.parse();
var stackFrame = stacktrace[2];
return stackFrame.fct;
});
})();
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.