From 2d06e1d5262ca2e17aa71955c7780d15acc18d5d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 1 Dec 2010 11:08:34 -0500 Subject: [PATCH] Issue #70 -- implementing each, find, all, any, etc. without the use of a try/catch/throw. Minor speedup + avoids destroying the stack trace. --- test/collections.js | 10 +++------- test/utility.js | 9 --------- underscore.js | 45 ++++++++++++++++++++------------------------- 3 files changed, 23 insertions(+), 41 deletions(-) diff --git a/test/collections.js b/test/collections.js index b187af236..7cdf16587 100644 --- a/test/collections.js +++ b/test/collections.js @@ -7,10 +7,6 @@ $(document).ready(function() { equals(num, i + 1, 'each iterators provide value and iteration count'); }); - var answer = null; - _.each([1, 2, 3], function(num){ if ((answer = num) == 2) _.breakLoop(); }); - equals(answer, 2, 'the loop broke in the middle'); - var answers = []; _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); @@ -65,7 +61,7 @@ $(document).ready(function() { sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0); equals(sum, 6, 'OO-style reduce'); - + var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }); equals(sum, 6, 'default initial value'); }); @@ -73,10 +69,10 @@ $(document).ready(function() { test('collections: reduceRight', function() { var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, ''); equals(list, 'bazbarfoo', 'can perform right folds'); - + var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, ''); equals(list, 'bazbarfoo', 'aliased as "foldr"'); - + var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }); equals(list, 'bazbarfoo', 'default initial value'); }); diff --git a/test/utility.js b/test/utility.js index 0265c0328..94252a654 100644 --- a/test/utility.js +++ b/test/utility.js @@ -15,15 +15,6 @@ $(document).ready(function() { equals(_.identity(moe), moe, 'moe is the same as his identity'); }); - test('utility: breakLoop', function() { - var result = null; - _([1,2,3,4,5,6]).each(function(num) { - result = num; - if (num == 3) _.breakLoop(); - }); - equals(result, 3, 'broke out of a loop'); - }); - test("utility: uniqueId", function() { var ids = [], i = 0; while(i++ < 100) ids.push(_.uniqueId()); diff --git a/underscore.js b/underscore.js index 291b97617..424a87bdd 100644 --- a/underscore.js +++ b/underscore.js @@ -17,8 +17,8 @@ // Save the previous value of the `_` variable. var previousUnderscore = root._; - // Establish the object that gets thrown to break out of a loop iteration. - var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__'; + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype; @@ -67,20 +67,20 @@ // Handles objects implementing `forEach`, arrays, and raw objects. // Delegates to **ECMAScript 5**'s native `forEach` if available. var each = _.each = _.forEach = function(obj, iterator, context) { - try { - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (_.isNumber(obj.length)) { - for (var i = 0, l = obj.length; i < l; i++) iterator.call(context, obj[i], i, obj); - } else { - for (var key in obj) { - if (hasOwnProperty.call(obj, key)) iterator.call(context, obj[key], key, obj); + var value; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (_.isNumber(obj.length)) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; } } - } catch(e) { - if (e != breaker) throw e; } - return obj; }; // Return the results of applying the iterator to each element. @@ -126,10 +126,10 @@ // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, iterator, context) { var result; - each(obj, function(value, index, list) { + any(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) { result = value; - _.breakLoop(); + return true; } }); return result; @@ -164,7 +164,7 @@ if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); var result = true; each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); + if (!(result = result && iterator.call(context, value, index, list))) return breaker; }); return result; }; @@ -172,12 +172,12 @@ // Determine if at least one element in the object matches a truth test. // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. - _.some = _.any = function(obj, iterator, context) { + var any = _.some = _.any = function(obj, iterator, context) { iterator = iterator || _.identity; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); var result = false; each(obj, function(value, index, list) { - if (result = iterator.call(context, value, index, list)) _.breakLoop(); + if (result = iterator.call(context, value, index, list)) return breaker; }); return result; }; @@ -187,8 +187,8 @@ _.include = _.contains = function(obj, target) { if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; var found = false; - each(obj, function(value) { - if (found = value === target) _.breakLoop(); + any(obj, function(value) { + if (found = value === target) return true; }); return found; }; @@ -642,11 +642,6 @@ for (var i = 0; i < n; i++) iterator.call(context, i); }; - // Break out of the middle of an iteration. - _.breakLoop = function() { - throw breaker; - }; - // Add your own custom functions to the Underscore object, ensuring that // they're correctly added to the OOP wrapper as well. _.mixin = function(obj) {