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> <b>Objects</b>
<br /> <br />
<span class="methods"><a href="#keys">keys</a>, <a href="#values">values</a>, <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="#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="#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> <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> <h2>Function (uh, ahem) Functions</h2>
<p id="bind"> <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 /> <br />
Bind a <b>function</b> to a <b>context</b> object, meaning that whenever 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>context</b>. 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, Optionally, bind <b>arguments</b> to the <b>function</b> to pre-fill them,
also known as <b>currying</b>. also known as <b>currying</b>.
</p> </p>
@@ -631,13 +631,14 @@ func();
</pre> </pre>
<p id="bindAll"> <p id="bindAll">
<b class="header">bindAll</b><code>_.bindAll(*methodNames, context)</code> <b class="header">bindAll</b><code>_.bindAll(object, [*methodNames])</code>
<br /> <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 <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 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 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> </p>
<pre> <pre>
var buttonView = { var buttonView = {
@@ -645,7 +646,11 @@ var buttonView = {
onClick : function(){ alert('clicked: ' + this.label); }, onClick : function(){ alert('clicked: ' + this.label); },
onHover : function(){ console.log('hovering: ' + 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); jQuery('#underscore_button').bind('click', buttonView.onClick);
=&gt; When the button is clicked, this.label will have the correct value... =&gt; When the button is clicked, this.label will have the correct value...
</pre> </pre>
@@ -729,6 +734,18 @@ _.keys({one : 1, two : 2, three : 3});
<pre> <pre>
_.values({one : 1, two : 2, three : 3}); _.values({one : 1, two : 2, three : 3});
=&gt; [1, 2, 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> </pre>
<p id="extend"> <p id="extend">
@@ -939,17 +956,6 @@ result;
<pre> <pre>
_.uniqueId('contact_'); _.uniqueId('contact_');
=&gt; 'contact_104' =&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> </pre>
<p id="template"> <p id="template">

View File

@@ -1,27 +1,27 @@
$(document).ready(function() { $(document).ready(function() {
module("Function functions (bind, bindAll, and so on...)"); module("Function functions (bind, bindAll, and so on...)");
test("functions: bind", function() { test("functions: bind", function() {
var context = {name : 'moe'}; var context = {name : 'moe'};
var func = function(arg) { return "name: " + (this.name || arg); }; var func = function(arg) { return "name: " + (this.name || arg); };
var bound = _.bind(func, context); var bound = _.bind(func, context);
equals(bound(), 'name: moe', 'can bind a function to a context'); equals(bound(), 'name: moe', 'can bind a function to a context');
bound = _(func).bind(context); bound = _(func).bind(context);
equals(bound(), 'name: moe', 'can do OO-style binding'); equals(bound(), 'name: moe', 'can do OO-style binding');
bound = _.bind(func, null, 'curly'); bound = _.bind(func, null, 'curly');
equals(bound(), 'name: curly', 'can bind without specifying a context'); equals(bound(), 'name: curly', 'can bind without specifying a context');
func = function(salutation, name) { return salutation + ': ' + name; }; func = function(salutation, name) { return salutation + ': ' + name; };
func = _.bind(func, this, 'hello'); func = _.bind(func, this, 'hello');
equals(func('moe'), 'hello: moe', 'the function was partially applied in advance'); equals(func('moe'), 'hello: moe', 'the function was partially applied in advance');
var func = _.bind(func, this, 'curly'); 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() { test("functions: bindAll", function() {
var curly = {name : 'curly'}, moe = { var curly = {name : 'curly'}, moe = {
name : 'moe', name : 'moe',
@@ -29,39 +29,49 @@ $(document).ready(function() {
sayHi : function() { return 'hi: ' + this.name; } sayHi : function() { return 'hi: ' + this.name; }
}; };
curly.getName = moe.getName; curly.getName = moe.getName;
_.bindAll('getName', 'sayHi', moe); _.bindAll(moe, 'getName', 'sayHi');
curly.sayHi = moe.sayHi; curly.sayHi = moe.sayHi;
equals(curly.getName(), 'name: curly', 'unbound function is bound to current object'); 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'); 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() { asyncTest("functions: delay", function() {
var delayed = false; var delayed = false;
_.delay(function(){ delayed = true; }, 100); _.delay(function(){ delayed = true; }, 100);
_.delay(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50); _.delay(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
_.delay(function(){ ok(delayed, 'delayed the function'); start(); }, 150); _.delay(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
}); });
asyncTest("functions: defer", function() { asyncTest("functions: defer", function() {
var deferred = false; var deferred = false;
_.defer(function(bool){ deferred = bool; }, true); _.defer(function(bool){ deferred = bool; }, true);
_.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50); _.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
}); });
test("functions: wrap", function() { test("functions: wrap", function() {
var greet = function(name){ return "hi: " + name; }; var greet = function(name){ return "hi: " + name; };
var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); }); var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
equals(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function'); equals(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
}); });
test("functions: compose", function() { test("functions: compose", function() {
var greet = function(name){ return "hi: " + name; }; var greet = function(name){ return "hi: " + name; };
var exclaim = function(sentence){ return sentence + '!'; }; var exclaim = function(sentence){ return sentence + '!'; };
var composed = _.compose(exclaim, greet); var composed = _.compose(exclaim, greet);
equals(composed('moe'), 'hi: moe!', 'can compose a function that takes another'); equals(composed('moe'), 'hi: moe!', 'can compose a function that takes another');
composed = _.compose(greet, exclaim); composed = _.compose(greet, exclaim);
equals(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative'); 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'); 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() { test("objects: extend", function() {
var source = {name : 'moe'}, dest = {age : 50}; var source = {name : 'moe'}, dest = {age : 50};
_.extend(dest, source); _.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'); 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() { test("utility: template", function() {
var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
var result = basicTemplate({thing : 'This'}); var result = basicTemplate({thing : 'This'});

View File

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