/** section: Language * class Function * * Extensions to the built-in `Function` object. **/ Object.extend(Function.prototype, (function() { var slice = Array.prototype.slice; function update(array, args) { var arrayLength = array.length, length = args.length; while (length--) array[arrayLength + length] = args[length]; return array; } function merge(array, args) { array = slice.call(array, 0); return update(array, args); } /** * Function#argumentNames() -> Array * * Reads the argument names as stated in the function definition and returns * the values as an array of strings (or an empty array if the function is * defined without parameters). * * ### Examples * * function fn(foo, bar) { * return foo + bar; * } * fn.argumentNames(); * //-> ['foo', 'bar'] * * Prototype.emptyFunction.argumentNames(); * //-> [] **/ function argumentNames() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } /** related to: Function#bindAsEventListener * Function#bind(context[, args...]) -> Function * - context (Object): The object to bind to. * - args (?): Optional additional arguments to curry for the function. * * Binds this function to the given `context` by wrapping it in another * function and returning the wrapper. Whenever the resulting "bound" * function is called, it will call the original ensuring that `this` is set * to `context`. Also optionally curries arguments for the function. * * ### Examples * * A typical use of `Function#bind` is to ensure that a callback (event * handler, etc.) that is an object method gets called with the correct * object as its context (`this` value): * * var AlertOnClick = Class.create({ * initialize: function(msg) { * this.msg = msg; * }, * handleClick: function(event) { * event.stop(); * alert(this.msg); * } * }); * var myalert = new AlertOnClick("Clicked!"); * $('foo').observe('click', myalert.handleClick); // <= WRONG * // -> If 'foo' is clicked, the alert will be blank; "this" is wrong * $('bar').observe('click', myalert.handleClick.bind(myalert)); // <= RIGHT * // -> If 'bar' is clicked, the alert will be "Clicked!" * * `bind` can also *curry* (burn in) arguments for the function if you * provide them after the `context` argument: * * var Averager = Class.create({ * initialize: function() { * this.count = 0; * this.total = 0; * }, * add: function(addend) { * ++this.count; * this.total += addend; * }, * getAverage: function() { * return this.count == 0 ? NaN : this.total / this.count; * } * }); * var a = new Averager(); * var b = new Averager(); * var aAdd5 = a.add.bind(a, 5); // Bind to a, curry 5 * var aAdd10 = a.add.bind(a, 10); // Bind to a, curry 10 * var bAdd20 = b.add.bind(b, 20); // Bind to b, curry 20 * aAdd5(); * aAdd10(); * bAdd20(); * bAdd20(); * alert(a.getAverage()); * // -> Alerts "7.5" (average of [5, 10]) * alert(b.getAverage()); * // -> Alerts "20" (average of [20, 20]) * * (To curry without binding, see [[Function#curry]].) **/ function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var __method = this, args = slice.call(arguments, 1); return function() { var a = merge(args, arguments); return __method.apply(context, a); } } /** related to: Function#bind * Function#bindAsEventListener(object[, args...]) -> Function * - object (Object): The object to bind to. * * An event-specific variant of [[Function#bind]] which ensures the function * will recieve the current event object as the first argument when * executing. **/ function bindAsEventListener(context) { var __method = this, args = slice.call(arguments, 1); return function(event) { var a = update([event || window.event], args); return __method.apply(context, a); } } /** * Function#curry(args...) -> Function * Partially applies the function, returning a function with one or more * arguments already "filled in." * * Function#curry works just like [[Function#bind]] without the initial * scope argument. Use the latter if you need to partially apply a function * _and_ modify its execution scope at the same time. **/ function curry() { if (!arguments.length) return this; var __method = this, args = slice.call(arguments, 0); return function() { var a = merge(args, arguments); return __method.apply(this, a); } } /** * Function#delay(seconds[, args...]) -> Number * - seconds (Number): How long to wait before calling the function. * * Schedules the function to run after the specified amount of time, passing * any arguments given. * * Behaves much like `window.setTimeout`. Returns an integer ID that can be * used to clear the timeout with `window.clearTimeout` before it runs. * * To schedule a function to run as soon as the interpreter is idle, use * [[Function#defer]]. **/ function delay(timeout) { var __method = this, args = slice.call(arguments, 1); timeout = timeout * 1000 return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); } /** * Function#defer(args...) -> Number * Schedules the function to run as soon as the interpreter is idle. * * A "deferred" function will not run immediately; rather, it will run as soon * as the interpreter's call stack is empty. * * Behaves much like `window.setTimeout` with a delay set to `0`. Returns an * ID that can be used to clear the timeout with `window.clearTimeout` before * it runs. **/ function defer() { var args = update([0.01], arguments); return this.delay.apply(this, args); } /** * Function#wrap(wrapperFunction) -> Function * - wrapperFunction (Function): The function to act as a wrapper. * * Returns a function "wrapped" around the original function. * * `Function#wrap` distills the essence of aspect-oriented programming into * a single method, letting you easily build on existing functions by * specifying before and after behavior, transforming the return value, or * even preventing the original function from being called. **/ function wrap(wrapper) { var __method = this; return function() { var a = update([__method.bind(this)], arguments); return wrapper.apply(this, a); } } /** * Function#methodize() -> Function * Wraps the function inside another function that, at call time, pushes * `this` to the original function as the first argument. * * Used to define both a generic method and an instance method. **/ function methodize() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { var a = update([this], arguments); return __method.apply(null, a); }; } return { argumentNames: argumentNames, bind: bind, bindAsEventListener: bindAsEventListener, curry: curry, delay: delay, defer: defer, wrap: wrap, methodize: methodize } })());