API changes: _.bindAll now takes the context object as the first parameter, instead of the last, and _.functions (_.methods) now takes an explicitreceiver, returning a list of its methods

This commit is contained in:
Jeremy Ashkenas
2009-12-06 23:54:41 -05:00
parent 06c74e76f0
commit 39001bd029
5 changed files with 73 additions and 56 deletions

View File

@@ -204,7 +204,7 @@ _(lyrics).chain()
<b>Objects</b>
<br />
<span class="methods"><a href="#keys">keys</a>, <a href="#values">values</a>,
<a href="#extend">extend</a>, <a href="#clone">clone</a>,
<a href="#functions">functions</a>, <a href="#extend">extend</a>, <a href="#clone">clone</a>,
<a href="#isEqual">isEqual</a>, <a href="#isEmpty">isEmpty</a>, <a href="#isElement">isElement</a>,
<a href="#isArray">isArray</a>, <a href="#isFunction">isFunction</a>, <a href="#isString">isString</a>,
<a href="#isNumber">isNumber</a>, <a href="#isDate">isDate</a>, <a href="#isRegExp">isRegExp</a>
@@ -616,10 +616,10 @@ _.range(0);
<h2>Function (uh, ahem) Functions</h2>
<p id="bind">
<b class="header">bind</b><code>_.bind(function, context, [*arguments])</code>
<b class="header">bind</b><code>_.bind(function, object, [*arguments])</code>
<br />
Bind a <b>function</b> to a <b>context</b> object, meaning that whenever
the function is called, the value of <i>this</i> will be the <b>context</b>.
Bind a <b>function</b> to an <b>object</b>, meaning that whenever
the function is called, the value of <i>this</i> will be the <b>object</b>.
Optionally, bind <b>arguments</b> to the <b>function</b> to pre-fill them,
also known as <b>currying</b>.
</p>
@@ -631,13 +631,14 @@ func();
</pre>
<p id="bindAll">
<b class="header">bindAll</b><code>_.bindAll(*methodNames, context)</code>
<b class="header">bindAll</b><code>_.bindAll(object, [*methodNames])</code>
<br />
Binds a number of methods on the <b>context</b> object, specified by
Binds a number of methods on the <b>object</b>, specified by
<b>methodNames</b>, to be run in the context of that object whenever they
are invoked. Very handy for binding functions that are going to be used
as event handlers, which would otherwise be invoked with a fairly useless
<i>this</i>.
<i>this</i>. If no <b>methodNames</b> are provided, all of the object's
function properties will be bound to it.
</p>
<pre>
var buttonView = {
@@ -645,7 +646,11 @@ var buttonView = {
onClick : function(){ alert('clicked: ' + this.label); },
onHover : function(){ console.log('hovering: ' + this.label); }
};
_.bindAll('onClick', 'onHover', buttonView);
// In this case, the following two lines have the same effect.
_.bindAll(buttonView, 'onClick', 'onHover');
_(buttonView).bindAll();
jQuery('#underscore_button').bind('click', buttonView.onClick);
=&gt; When the button is clicked, this.label will have the correct value...
</pre>
@@ -729,6 +734,18 @@ _.keys({one : 1, two : 2, three : 3});
<pre>
_.values({one : 1, two : 2, three : 3});
=&gt; [1, 2, 3]
</pre>
<p id="functions">
<b class="header">functions</b><code>_.functions(object)</code>
<span class="alias">Alias: <b>methods</b></span>
<br />
Returns a sorted list of the names of every method in an object &mdash;
that is to say, the name of every function property of the object.
</p>
<pre>
_.functions(_);
=&gt; ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
</pre>
<p id="extend">
@@ -939,17 +956,6 @@ result;
<pre>
_.uniqueId('contact_');
=&gt; 'contact_104'
</pre>
<p id="functions">
<b class="header">functions</b><code>_.functions([prefix])</code>
<span class="alias">Alias: <b>methods</b></span>
<br />
Returns a sorted list of the name of every function in Underscore.
</p>
<pre>
_.functions();
=&gt; ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
</pre>
<p id="template">

View File

@@ -1,27 +1,27 @@
$(document).ready(function() {
module("Function functions (bind, bindAll, and so on...)");
test("functions: bind", function() {
var context = {name : 'moe'};
var func = function(arg) { return "name: " + (this.name || arg); };
var bound = _.bind(func, context);
equals(bound(), 'name: moe', 'can bind a function to a context');
bound = _(func).bind(context);
equals(bound(), 'name: moe', 'can do OO-style binding');
bound = _.bind(func, null, 'curly');
equals(bound(), 'name: curly', 'can bind without specifying a context');
func = function(salutation, name) { return salutation + ': ' + name; };
func = _.bind(func, this, 'hello');
equals(func('moe'), 'hello: moe', 'the function was partially applied in advance');
var func = _.bind(func, this, 'curly');
equals(func(), 'hello: curly', 'the function was completely applied in advance');
equals(func(), 'hello: curly', 'the function was completely applied in advance');
});
test("functions: bindAll", function() {
var curly = {name : 'curly'}, moe = {
name : 'moe',
@@ -29,39 +29,49 @@ $(document).ready(function() {
sayHi : function() { return 'hi: ' + this.name; }
};
curly.getName = moe.getName;
_.bindAll('getName', 'sayHi', moe);
_.bindAll(moe, 'getName', 'sayHi');
curly.sayHi = moe.sayHi;
equals(curly.getName(), 'name: curly', 'unbound function is bound to current object');
equals(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object');
curly = {name : 'curly'};
moe = {
name : 'moe',
getName : function() { return 'name: ' + this.name; },
sayHi : function() { return 'hi: ' + this.name; }
};
_.bindAll(moe);
curly.sayHi = moe.sayHi;
equals(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object');
});
asyncTest("functions: delay", function() {
var delayed = false;
_.delay(function(){ delayed = true; }, 100);
_.delay(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
_.delay(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
});
asyncTest("functions: defer", function() {
var deferred = false;
_.defer(function(bool){ deferred = bool; }, true);
_.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
});
test("functions: wrap", function() {
var greet = function(name){ return "hi: " + name; };
var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
equals(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
});
test("functions: compose", function() {
var greet = function(name){ return "hi: " + name; };
var exclaim = function(sentence){ return sentence + '!'; };
var composed = _.compose(exclaim, greet);
equals(composed('moe'), 'hi: moe!', 'can compose a function that takes another');
composed = _.compose(greet, exclaim);
equals(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
});
});

View File

@@ -10,6 +10,20 @@ $(document).ready(function() {
equals(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object');
});
test("objects: functions", function() {
var expected = ["all", "any", "bind", "bindAll", "breakLoop", "clone", "compact",
"compose","defer", "delay", "detect", "each", "every", "extend", "filter", "first",
"flatten", "foldl", "foldr", "forEach", "functions", "head", "identity", "include",
"indexOf", "inject", "intersect", "invoke", "isArray", "isDate", "isElement", "isEmpty", "isEqual",
"isFunction", "isNaN", "isNull", "isNumber", "isRegExp", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max",
"methods", "min", "noConflict", "pluck", "range", "reduce", "reduceRight", "reject", "rest", "select",
"size", "some", "sortBy", "sortedIndex", "tail", "template", "toArray", "uniq",
"uniqueId", "values", "without", "wrap", "zip"];
ok(_(expected).isEqual(_.methods(_)), 'provides a sorted list of functions');
var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce};
ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object');
});
test("objects: extend", function() {
var source = {name : 'moe'}, dest = {age : 50};
_.extend(dest, source);

View File

@@ -30,18 +30,6 @@ $(document).ready(function() {
equals(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
});
test("utility: functions", function() {
var expected = ["all", "any", "bind", "bindAll", "breakLoop", "clone", "compact",
"compose","defer", "delay", "detect", "each", "every", "extend", "filter", "first",
"flatten", "foldl", "foldr", "forEach", "functions", "head", "identity", "include",
"indexOf", "inject", "intersect", "invoke", "isArray", "isDate", "isElement", "isEmpty", "isEqual",
"isFunction", "isNaN", "isNull", "isNumber", "isRegExp", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max",
"methods", "min", "pluck", "range", "reduce", "reduceRight", "reject", "rest", "select",
"size", "some", "sortBy", "sortedIndex", "tail", "template", "toArray", "uniq",
"uniqueId", "values", "without", "wrap", "zip"];
ok(_(expected).isEqual(_.methods()), 'provides a sorted list of functions');
});
test("utility: template", function() {
var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
var result = basicTemplate({thing : 'This'});

View File

@@ -334,20 +334,19 @@
// Create a function bound to a given object (assigning 'this', and arguments,
// optionally). Binding with arguments is also known as 'curry'.
_.bind = function(func, context) {
_.bind = function(func, obj) {
var args = _.rest(arguments, 2);
return function() {
return func.apply(context || root, args.concat(_.toArray(arguments)));
return func.apply(obj || root, args.concat(_.toArray(arguments)));
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function() {
var context = Array.prototype.pop.call(arguments);
_.each(arguments, function(methodName) {
context[methodName] = _.bind(context[methodName], context);
});
_.bindAll = function(obj) {
var funcs = _.rest(arguments);
if (funcs.length == 0) funcs = _.functions(obj);
_.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
};
// Delays a function for the given number of milliseconds, and then calls
@@ -509,8 +508,8 @@
};
// Return a sorted list of the function names available in Underscore.
_.functions = function() {
return _.without(_.keys(_), 'VERSION', 'prototype', 'noConflict').sort();
_.functions = function(obj) {
return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
};
// JavaScript templating a-la ERB, pilfered from John Resig's
@@ -551,7 +550,7 @@
};
// Add all of the Underscore functions to the wrapper object.
_.each(_.functions(), function(name) {
_.each(_.functions(_), function(name) {
wrapper.prototype[name] = function() {
Array.prototype.unshift.call(arguments, this._wrapped);
return result(_[name].apply(_, arguments), this._chain);