diff --git a/index.html b/index.html index 256824685..540843faf 100644 --- a/index.html +++ b/index.html @@ -107,16 +107,55 @@

- + - +
Development Version (0.3.3)Development Version (0.4.0) 16kb, Uncompressed with Comments
Production Version (0.3.3)Production Version (0.4.0) 2kb, Packed and Gzipped

+

Object-Oriented and Functional Styles

+ +

+ You can use Underscore in either an object-oriented or a functional style, + depending on your preference. The following two lines of code are + identical ways to double a list of numbers. +

+ +
+_.map([1, 2, 3], function(n){ return n * 2; });
+_([1, 2, 3]).map(function(n){ return n * 2; });
+ +

+ Using the object-oriented style allows you to chain together methods. Calling + chain on a wrapped object will cause all future method calls to + return wrapped objects as well. When you've finished the computation, + use get to retrieve the final value. Here's an example of chaining + together a map/flatten/reduce, in order to get the word count of + every word in a song. +

+ +
+var lyrics = [
+  {line : 1, words : "I'm a lumberjack and I'm okay"},
+  {line : 2, words : "I sleep all night and I work all day"},
+  {line : 3, words : "He's a lumberjack and he's okay"},
+  {line : 4, words : "He sleeps all night and he works all day"}
+];
+
+_(lyrics).chain()
+  .map(function(line) { return line.words.split(' '); })
+  .flatten()
+  .reduce({}, function(counts, word) { 
+    counts[word] = (counts[word] || 0) + 1;
+    return counts;
+}).get();
+
+=> returns a hash containing the word counts...
+

Table of Contents

@@ -742,6 +781,17 @@ moe === _.identity(moe);

 _.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" ...
 

@@ -769,6 +819,16 @@ _.template(list, {people : ['moe', 'curly', 'larry']});

Change Log

+

+ 0.4.0
+ All Underscore functions can now be called in an object-oriented style, + like so: _([1, 2, 3]).map(...);. Original patch provided by + Marc-André Cournoyer. + Wrapped objects can be chained through multiple + method invocations. A functions method + was added, providing a sorted list of all the functions in Underscore. +

+

0.3.3
Added the JavaScript 1.8 function reduceRight. Aliased it @@ -787,7 +847,7 @@ _.template(list, {people : ['moe', 'curly', 'larry']}); All iterators are now passed in the original collection as their third argument, the same as JavaScript 1.6's forEach. Iterating over objects is now called with (value, key, collection), for details - see _.each. + see _.each.

diff --git a/test/chaining.js b/test/chaining.js new file mode 100644 index 000000000..0f3b10037 --- /dev/null +++ b/test/chaining.js @@ -0,0 +1,35 @@ +$(document).ready(function() { + + module("Underscore chaining."); + + test("chaining: map/flatten/reduce", function() { + var lyrics = [ + "I'm a lumberjack and I'm okay", + "I sleep all night and I work all day", + "He's a lumberjack and he's okay", + "He sleeps all night and he works all day" + ]; + var counts = _(lyrics).chain() + .map(function(line) { return line.split(''); }) + .flatten() + .reduce({}, function(hash, l) { + hash[l] = hash[l] || 0; + hash[l]++; + return hash; + }).get(); + ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song'); + }); + + test("chaining: select/reject/sortBy", function() { + var numbers = [1,2,3,4,5,6,7,8,9,10]; + numbers = _(numbers).chain().select(function(n) { + return n % 2 == 0; + }).reject(function(n) { + return n % 4 == 0; + }).sortBy(function(n) { + return -n; + }).get(); + equals(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers"); + }); + +}); diff --git a/test/test.html b/test/test.html index 1e001b739..30ac9098f 100644 --- a/test/test.html +++ b/test/test.html @@ -12,6 +12,7 @@ + diff --git a/test/utility.js b/test/utility.js index b58844294..4022410ea 100644 --- a/test/utility.js +++ b/test/utility.js @@ -21,6 +21,18 @@ $(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", "clone", "compact", "compose", + "defer", "delay", "detect", "each", "every", "extend", "filter", "first", + "flatten", "foldl", "foldr", "forEach", "functions", "identity", "include", + "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEqual", + "isFunction", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", + "methods", "min", "pluck", "reduce", "reduceRight", "reject", "select", + "size", "some", "sortBy", "sortedIndex", "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-min.js b/underscore-min.js index ec7f39785..25ad77f4c 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var a=this;var c=a._;var b=a._={};if(typeof exports!=="undefined"){b=exports}b.VERSION="0.3.3";b.each=function(n,k,j){var f=0;try{if(n.forEach){n.forEach(k,j)}else{if(n.length){for(var h=0,d=n.length;h=d.computed&&(d={value:m,computed:j})});return d.value};b.min=function(g,f,e){if(!f&&b.isArray(g)){return Math.min.apply(Math,g)}var d={computed:Infinity};b.each(g,function(m,h,k){var j=f?f.call(e,m,h,k):m;jg?1:0}),"value")};b.sortedIndex=function(j,h,f){f=f||b.identity;var d=0,g=j.length;while(d>1;f(j[e])=0})})};b.zip=function(){var d=b.toArray(arguments);var g=b.max(b.pluck(d,"length"));var f=new Array(g);for(var e=0;e=0;e--){arguments=[d[e].apply(this,arguments)]}return arguments[0]}};b.keys=function(d){return b.map(d,function(f,e){return e})};b.values=function(d){return b.map(d,b.identity)};b.extend=function(d,f){for(var e in f){d[e]=f[e]}return d};b.clone=function(d){if(b.isArray(d)){return d.slice(0)}return b.extend({},d)};b.isEqual=function(e,d){if(e===d){return true}var h=typeof(e),k=typeof(d);if(h!=k){return false}if(e==d){return true}if(e.isEqual){return e.isEqual(d)}if(h!=="object"){return false}var f=b.keys(e),j=b.keys(d);if(f.length!=j.length){return false}for(var g in e){if(!b.isEqual(e[g],d[g])){return false}}return true};b.isElement=function(d){return !!(d&&d.nodeType==1)};b.isArray=function(d){return Object.prototype.toString.call(d)=="[object Array]"};b.isFunction=function(d){return Object.prototype.toString.call(d)=="[object Function]"};b.isUndefined=function(d){return typeof d=="undefined"};b.noConflict=function(){a._=c;return this};b.identity=function(d){return d};b.uniqueId=function(d){var e=this._idCounter=(this._idCounter||0)+1;return d?d+e:e};b.template=function(f,e){var d=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+f.replace(/[\r\t\n]/g," ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return e?d(e):d};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any})(); \ No newline at end of file +(function(){var b=this;var d=b._;var e=function(f){this._wrapped=f};var c=b._=function(f){return new e(f)};if(typeof exports!=="undefined"){c=exports}c.VERSION="0.4.0";c.each=function(o,m,k){var g=0;try{if(o.forEach){o.forEach(m,k)}else{if(o.length){for(var j=0,f=o.length;j=f.computed&&(f={value:o,computed:m})});return f.value};c.min=function(j,h,g){if(!h&&c.isArray(j)){return Math.min.apply(Math,j)}var f={computed:Infinity};c.each(j,function(o,k,n){var m=h?h.call(g,o,k,n):o;mj?1:0}),"value")};c.sortedIndex=function(m,k,h){h=h||c.identity;var f=0,j=m.length;while(f>1;h(m[g])=0})})};c.zip=function(){var f=c.toArray(arguments);var j=c.max(c.pluck(f,"length"));var h=new Array(j);for(var g=0;g=0;g--){arguments=[f[g].apply(this,arguments)]}return arguments[0]}};c.keys=function(f){return c.map(f,function(h,g){return g})};c.values=function(f){return c.map(f,c.identity)};c.extend=function(f,h){for(var g in h){f[g]=h[g]}return f};c.clone=function(f){if(c.isArray(f)){return f.slice(0)}return c.extend({},f)};c.isEqual=function(g,f){if(g===f){return true}var k=typeof(g),n=typeof(f);if(k!=n){return false}if(g==f){return true}if(g.isEqual){return g.isEqual(f)}if(k!=="object"){return false}var h=c.keys(g),m=c.keys(f);if(h.length!=m.length){return false}for(var j in g){if(!c.isEqual(g[j],f[j])){return false}}return true};c.isElement=function(f){return !!(f&&f.nodeType==1)};c.isArray=function(f){return Object.prototype.toString.call(f)=="[object Array]"};c.isFunction=function(f){return Object.prototype.toString.call(f)=="[object Function]"};c.isUndefined=function(f){return typeof f=="undefined"};c.noConflict=function(){b._=d;return this};c.identity=function(f){return f};var a=0;c.uniqueId=function(f){var g=a++;return f?f+g:g};c.functions=function(){var g=[];for(var f in c){if(Object.prototype.hasOwnProperty.call(c,f)){g.push(f)}}return c.without(g,"VERSION","prototype","noConflict").sort()};c.template=function(h,g){var f=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+h.replace(/[\r\t\n]/g," ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return g?f(g):f};c.forEach=c.each;c.foldl=c.inject=c.reduce;c.foldr=c.reduceRight;c.filter=c.select;c.every=c.all;c.some=c.any;c.methods=c.functions;c.each(c.functions(),function(f){e.prototype[f]=function(){Array.prototype.unshift.call(arguments,this._wrapped);var g=c[f].apply(c,arguments);return this._chain?c(g).chain():g}});e.prototype.chain=function(){this._chain=true;return this};e.prototype.get=function(){return this._wrapped}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 8f045b9ab..318268e2c 100644 --- a/underscore.js +++ b/underscore.js @@ -18,8 +18,8 @@ // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. - var wrapper = function(obj) { this.wrapped = obj; }; + // underscore functions. Wrapped objects may be chained. + var wrapper = function(obj) { this._wrapped = obj; }; // Create a safe reference to the Underscore object for reference below. var _ = root._ = function(obj) { return new wrapper(obj); }; @@ -28,7 +28,7 @@ if (typeof exports !== 'undefined') _ = exports; // Current version. - _.VERSION = '0.3.3'; + _.VERSION = '0.4.0'; /*------------------------ Collection Functions: ---------------------------*/ @@ -447,8 +447,9 @@ // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. + var idCounter = 0; _.uniqueId = function(prefix) { - var id = this._idCounter = (this._idCounter || 0) + 1; + var id = idCounter++; return prefix ? prefix + id : id; }; @@ -456,7 +457,7 @@ _.functions = function() { var functions = []; for (var key in _) if (Object.prototype.hasOwnProperty.call(_, key)) functions.push(key); - return _.without(functions, 'VERSION', 'prototype', 'noConflict'); + return _.without(functions, 'VERSION', 'prototype', 'noConflict').sort(); }; // JavaScript templating a-la ERB, pilfered from John Resig's @@ -487,13 +488,26 @@ _.some = _.any; _.methods = _.functions; - /*------------------- Add all Functions to the Wrapper: --------------------*/ - + /*------------------------ Setup the OOP Wrapper: --------------------------*/ + + // Add all of the Underscore functions to the wrapper object. _.each(_.functions(), function(name) { wrapper.prototype[name] = function() { - Array.prototype.unshift.call(arguments, this.wrapped); - return _[name].apply(_, arguments); + Array.prototype.unshift.call(arguments, this._wrapped); + var result = _[name].apply(_, arguments); + return this._chain ? _(result).chain() : result; }; }); + + // Start chaining a wrapped Underscore object. + wrapper.prototype.chain = function() { + this._chain = true; + return this; + }; + + // Extracts the result from a wrapped and chained object. + wrapper.prototype.get = function() { + return this._wrapped; + }; })();