From 39001bd02937106c7841b9d2bc1521aaccf32d38 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 23:54:41 -0500 Subject: [PATCH] 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 --- index.html | 44 +++++++++++++++++++++++++------------------- test/functions.js | 40 +++++++++++++++++++++++++--------------- test/objects.js | 14 ++++++++++++++ test/utility.js | 12 ------------ underscore.js | 19 +++++++++---------- 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/index.html b/index.html index 70669fc13..0d49f955d 100644 --- a/index.html +++ b/index.html @@ -204,7 +204,7 @@ _(lyrics).chain() Objects
keys, values, - extend, clone, + functions, extend, clone, isEqual, isEmpty, isElement, isArray, isFunction, isString, isNumber, isDate, isRegExp @@ -616,10 +616,10 @@ _.range(0);

Function (uh, ahem) Functions

- bind_.bind(function, context, [*arguments]) + bind_.bind(function, object, [*arguments])
- Bind a function to a context object, meaning that whenever - the function is called, the value of this will be the context. + Bind a function to an object, meaning that whenever + the function is called, the value of this will be the object. Optionally, bind arguments to the function to pre-fill them, also known as currying.

@@ -631,13 +631,14 @@ func();

- bindAll_.bindAll(*methodNames, context) + bindAll_.bindAll(object, [*methodNames])
- Binds a number of methods on the context object, specified by + Binds a number of methods on the object, specified by methodNames, 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 - this. + this. If no methodNames are provided, all of the object's + function properties will be bound to it.

 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);
 => When the button is clicked, this.label will have the correct value...
 
@@ -729,6 +734,18 @@ _.keys({one : 1, two : 2, three : 3});
 _.values({one : 1, two : 2, three : 3});
 => [1, 2, 3]
+
+ +

+ functions_.functions(object) + Alias: methods +
+ Returns a sorted list of the names of every method in an object — + that is to say, the name of every function property of the object. +

+
+_.functions(_);
+=> ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
 

@@ -939,17 +956,6 @@ result;

 _.uniqueId('contact_');
 => 'contact_104'
-
- -

- functions_.functions([prefix]) - Alias: methods -
- Returns a sorted list of the name of every function in Underscore. -

-
-_.functions();
-=> ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
 

diff --git a/test/functions.js b/test/functions.js index a764d75c0..7633a6b08 100644 --- a/test/functions.js +++ b/test/functions.js @@ -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'); }); - + }); diff --git a/test/objects.js b/test/objects.js index 97556747c..18c4b3c2b 100644 --- a/test/objects.js +++ b/test/objects.js @@ -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); diff --git a/test/utility.js b/test/utility.js index 99aa7cab2..92bac6b2f 100644 --- a/test/utility.js +++ b/test/utility.js @@ -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'}); diff --git a/underscore.js b/underscore.js index b734430cb..cf3c6c3f7 100644 --- a/underscore.js +++ b/underscore.js @@ -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);