0.4.0 is out, with OOP-style and chaining

This commit is contained in:
Jeremy Ashkenas
2009-11-07 14:29:40 -05:00
parent 4f0afda61c
commit ed37b9df49
6 changed files with 135 additions and 13 deletions

View File

@@ -107,16 +107,55 @@
<p>
<table>
<tr>
<td><a href="underscore.js">Development Version (0.3.3)</a></td>
<td><a href="underscore.js">Development Version (0.4.0)</a></td>
<td><i>16kb, Uncompressed with Comments</i></td>
</tr>
<tr>
<td><a href="underscore-min.js">Production Version (0.3.3)</a></td>
<td><a href="underscore-min.js">Production Version (0.4.0)</a></td>
<td><i>2kb, Packed and Gzipped</i></td>
</tr>
</table>
</p>
<h2>Object-Oriented and Functional Styles</h2>
<p>
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.
</p>
<pre>
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });</pre>
<p>
Using the object-oriented style allows you to chain together methods. Calling
<tt>chain</tt> on a wrapped object will cause all future method calls to
return wrapped objects as well. When you've finished the computation,
use <tt>get</tt> to retrieve the final value. Here's an example of chaining
together a <b>map/flatten/reduce</b>, in order to get the word count of
every word in a song.
</p>
<pre>
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();
=&gt; returns a hash containing the word counts...</pre>
<h2>Table of Contents</h2>
<p>
@@ -742,6 +781,17 @@ moe === _.identity(moe);
<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">
@@ -769,6 +819,16 @@ _.template(list, {people : ['moe', 'curly', 'larry']});
<h2>Change Log</h2>
<p>
<b class="header">0.4.0</b><br />
All Underscore functions can now be called in an object-oriented style,
like so: <tt>_([1, 2, 3]).map(...);</tt>. Original patch provided by
<a href="http://macournoyer.com/">Marc-André Cournoyer</a>.
Wrapped objects can be chained through multiple
method invocations. A <a href="#functions"><tt>functions</tt></a> method
was added, providing a sorted list of all the functions in Underscore.
</p>
<p>
<b class="header">0.3.3</b><br />
Added the JavaScript 1.8 function <tt>reduceRight</tt>. 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 <b>forEach</b>. Iterating over
objects is now called with <tt>(value, key, collection)</tt>, for details
see <a href="#each">_.each</a>.
see <a href="#each"><tt>_.each</tt></a>.
</p>
<p>

35
test/chaining.js Normal file
View File

@@ -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");
});
});

View File

@@ -12,6 +12,7 @@
<script type="text/javascript" src="functions.js"></script>
<script type="text/javascript" src="objects.js"></script>
<script type="text/javascript" src="utility.js"></script>
<script type="text/javascript" src="chaining.js"></script>
<script type="text/javascript" src="speed.js"></script>
</head>
<body>

View File

@@ -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'});

2
underscore-min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -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;
};
})();