Skip to content

Instantly share code, notes, and snippets.

@datchley
Last active August 29, 2015 13:58
Show Gist options
  • Select an option

  • Save datchley/10177630 to your computer and use it in GitHub Desktop.

Select an option

Save datchley/10177630 to your computer and use it in GitHub Desktop.
Function.prototype extensions, including a bind polyfill and a Function.delay (setTimeout equiv) and Function.repeat (setInterval equiv).
var has_bind_native = Function.prototype.bind ? true : false;
/**
* An ES5 compaitible polyfill for Function.prototype.bind(thisArg, arg1, arg2, ...). Allows you to
* rebind a function to a different this context. This is already available in browsers implementing
* the ES5 standards.
*
* @param {Object} _context - the new object to bind this to in the function
* @returns {Function} - the newly bound function
*/
Function.prototype.bind = Function.prototype.bind || function(_context){
if(typeof this!=="function"){
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var slice = [].slice,
args = slice.call(arguments, 1),
self = this,
context = _context || (this instanceof Function ? this : window),
bound = function () {
return self.apply(context, args.concat(slice.call(arguments)));
};
/** @constructor */
function fn() {}
fn.prototype = self.prototype;
bound.prototype = new fn();
return bound;
};
/**
* Delay a function call for `timeout' milliseconds. You can optionally bind the function using a different
* context as well.
*
* @param _this {Object} - the context to bind to the function when calling (defaults to the function if null)
* @param timeout {Number} - the time to wait before calling the function in milliseconds
* @returns id to use with clearTimeout for clearing the timeout
* Any other arguments are passed as standard arguments to the function when called.
*/
Function.prototype.delay = Function.prototype.delay || function(/* context, timeout, arguments */) {
if (typeof this !== 'function') {
throw new TypeError("Function.prototype.delay - being called on a non-function object");
}
var has_context = (typeof arguments[0] == 'object') ? true : false,
args = [].slice.call(arguments, has_context ? 1 : 0),
timeout = parseInt(args.shift(), 10),
func = this,
context = has_context ? arguments[0] : func;
return window.setTimeout(function() {
func.apply(context, args)
}, timeout);
};
/**
* Repeat a function on a periodic interval, with an optional upper limit to the number of times to repeat
* the function call. You can optionally bind the function using a different context as well.
*
* @param {Object} context - the context to bind to the function when calling (defaults to the function if null)
* @param {Number} timeout - the time to wait before calling the function in milliseconds
* @param {Number} max - the number of calls to limit this too (null or 0 means no limit)
* @returns {Number} id - the interval id to use with clearInterval
*
* Any other arguments are passed as standard arguments to the function when called.
*/
Function.prototype.repeat = Function.prototype.repeat || function(/* context, timeout, max, arguments */) {
if (typeof this !== 'function') {
throw new TypeError("Function.prototype.repeat - being called on a non-function object");
}
var has_context = (typeof arguments[0] == 'object') ? true : false,
args = [].slice.call(arguments, has_context ? 1 : 0),
timeout = parseInt(args.shift(), 10),
limit = parseInt(args.shift(), 10) || false,
func = this,
context = has_context ? arguments[0] : func,
interval;
interval = window.setInterval((function() {
var count = 0;
return function() {
count++;
if (limit && count >= limit) {
window.clearInterval(interval);
return;
}
func.apply(context, args);
};
})(), timeout);
return interval;
};
//
// UNIT TEST
//
// function to be delayed
function test_timer(a) {
console.log("test_timer: a=%d - (context=%s)", a, typeof this);
}
// function to generate a function to call on an interval
var gen_test_interval = function(ident) {
var count = 0,
fident = ident;
return function(a) {
count++;
console.log("[%d] test_interval(%s): a=%d - (context=%s)", count, fident, a, typeof this);
};
};
// generate interval test functions
var test_interval_a = gen_test_interval('a'),
test_interval_b = gen_test_interval('b');
var _context = { a: 1, b: 2 };
// test running Function.delay with and w/o new context
test_timer.delay(300, 4);
test_timer.delay(_context, 300, 5);
// test running Function.repeat with and w/o new context
test_interval_a.repeat(1000, 3, 10);
test_interval_b.repeat(_context, 1000, 3, 10);
// test Function.bind
console.log("Using native ES5 Function.bind? ", has_bind_native);
function times(a, b) {
console.log("[rebound] result=%d", (a || this.a) * (b || this.b));
console.log("-> context=%o", this);
}
rebound = times.bind({ a: 2, b: 3 });
times(3, 4);
rebound();
// test binding and then using delay or repeat
function foo() { console.log("[foo] called - (context=%s)", this.name); }
var obj = { name: 'foo.delay' }; // new context
_foo = foo.bind(obj);
_foo.delay(1000);
obj = { name: 'foo.repeat' }; // new context
_foo = foo.bind(obj);
_foo.repeat(1000, 5);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment