From 59a4f49bfa5c611d1053d7e96dfcf9270be80f48 Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Wed, 17 Feb 2010 09:05:45 -0600 Subject: [PATCH 01/13] Make internal var each = _.each We use it everwhere, so this should be a slight speedup but make code more readable --- underscore.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/underscore.js b/underscore.js index faadaeb85..a115ed6d3 100644 --- a/underscore.js +++ b/underscore.js @@ -44,6 +44,7 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. + var each = _.each = function(obj, iterator, context) { var index = 0; try { @@ -66,7 +67,7 @@ _.map = function(obj, iterator, context) { if (obj && _.isFunction(obj.map)) return obj.map(iterator, context); var results = []; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { results.push(iterator.call(context, value, index, list)); }); return results; @@ -76,7 +77,7 @@ // inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. _.reduce = function(obj, memo, iterator, context) { if (obj && _.isFunction(obj.reduce)) return obj.reduce(_.bind(iterator, context), memo); - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { memo = iterator.call(context, memo, value, index, list); }); return memo; @@ -87,7 +88,7 @@ _.reduceRight = function(obj, memo, iterator, context) { if (obj && _.isFunction(obj.reduceRight)) return obj.reduceRight(_.bind(iterator, context), memo); var reversed = _.clone(_.toArray(obj)).reverse(); - _.each(reversed, function(value, index) { + each(reversed, function(value, index) { memo = iterator.call(context, memo, value, index, obj); }); return memo; @@ -96,7 +97,7 @@ // Return the first value which passes a truth test. _.detect = function(obj, iterator, context) { var result; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) { result = value; _.breakLoop(); @@ -110,7 +111,7 @@ _.select = function(obj, iterator, context) { if (obj && _.isFunction(obj.filter)) return obj.filter(iterator, context); var results = []; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { iterator.call(context, value, index, list) && results.push(value); }); return results; @@ -119,7 +120,7 @@ // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { var results = []; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { !iterator.call(context, value, index, list) && results.push(value); }); return results; @@ -131,7 +132,7 @@ iterator = iterator || _.identity; if (obj && _.isFunction(obj.every)) return obj.every(iterator, context); var result = true; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); }); return result; @@ -143,7 +144,7 @@ iterator = iterator || _.identity; if (obj && _.isFunction(obj.some)) return obj.some(iterator, context); var result = false; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { if (result = iterator.call(context, value, index, list)) _.breakLoop(); }); return result; @@ -154,7 +155,7 @@ _.include = function(obj, target) { if (obj && _.isFunction(obj.indexOf)) return _.indexOf(obj, target) != -1; var found = false; - _.each(obj, function(value) { + each(obj, function(value) { if (found = value === target) _.breakLoop(); }); return found; @@ -177,7 +178,7 @@ _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); var result = {computed : -Infinity}; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed >= result.computed && (result = {value : value, computed : computed}); }); @@ -188,7 +189,7 @@ _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); var result = {computed : Infinity}; - _.each(obj, function(value, index, list) { + each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); }); @@ -356,7 +357,7 @@ _.bindAll = function(obj) { var funcs = _.rest(arguments); if (funcs.length == 0) funcs = _.functions(obj); - _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; @@ -606,7 +607,7 @@ }; // Add all of the Underscore functions to the wrapper object. - _.each(_.functions(_), function(name) { + each(_.functions(_), function(name) { var method = _[name]; wrapper.prototype[name] = function() { var args = _.toArray(arguments); @@ -616,7 +617,7 @@ }); // Add all mutator Array functions to the wrapper. - _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = Array.prototype[name]; wrapper.prototype[name] = function() { method.apply(this._wrapped, arguments); @@ -625,7 +626,7 @@ }); // Add all accessor Array functions to the wrapper. - _.each(['concat', 'join', 'slice'], function(name) { + each(['concat', 'join', 'slice'], function(name) { var method = Array.prototype[name]; wrapper.prototype[name] = function() { return result(method.apply(this._wrapped, arguments), this._chain); From 7ec8d12d6c4644dcdade95c06125aca26f3e748f Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Wed, 17 Feb 2010 09:16:10 -0600 Subject: [PATCH 02/13] rename underscore methods after the native [] method names, aliases for ruby versions Even though the native methods have worse names (forEach, every, some), since this library is trying to smooth over the native language it makes more sense to use the native names, and provide aliases for more sensible names from other languages, not the other way around. Note this doesn't change any external usage, it just makes more sense. This should also be useful for abstraction purposes for building underscore functions, something like: addFn('some', {has_native: true, our_version: function () {...}}) this way we could feature detect on load for native versions and build a function, and also have the option to turn off native versions for testing our implementation. I Also standardized the comments to look like: Delegates to JavaScript 1.x's native y if available. --- underscore.js | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/underscore.js b/underscore.js index a115ed6d3..20b18eba6 100644 --- a/underscore.js +++ b/underscore.js @@ -44,8 +44,9 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. + // Delegates to JavaScript 1.6's native forEach if available. var each = - _.each = function(obj, iterator, context) { + _.forEach = function(obj, iterator, context) { var index = 0; try { if (obj.forEach) { @@ -62,8 +63,8 @@ return obj; }; - // Return the results of applying the iterator to each element. Use JavaScript - // 1.6's version of map, if possible. + // Return the results of applying the iterator to each element. + // Delegates to JavaScript 1.6's native map if available. _.map = function(obj, iterator, context) { if (obj && _.isFunction(obj.map)) return obj.map(iterator, context); var results = []; @@ -74,7 +75,8 @@ }; // Reduce builds up a single result from a list of values. Also known as - // inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. + // inject, or foldl. + // Delegates to JavaScript 1.8's native reduce if available. _.reduce = function(obj, memo, iterator, context) { if (obj && _.isFunction(obj.reduce)) return obj.reduce(_.bind(iterator, context), memo); each(obj, function(value, index, list) { @@ -84,7 +86,7 @@ }; // The right-associative version of reduce, also known as foldr. Uses - // JavaScript 1.8's version of reduceRight, if available. + // Delegates to JavaScript 1.8's native reduceRight if available. _.reduceRight = function(obj, memo, iterator, context) { if (obj && _.isFunction(obj.reduceRight)) return obj.reduceRight(_.bind(iterator, context), memo); var reversed = _.clone(_.toArray(obj)).reverse(); @@ -106,9 +108,9 @@ return result; }; - // Return all the elements that pass a truth test. Use JavaScript 1.6's - // filter(), if it exists. - _.select = function(obj, iterator, context) { + // Return all the elements that pass a truth test. + // Delegates to JavaScript 1.6's native filter if available. + _.filter = function(obj, iterator, context) { if (obj && _.isFunction(obj.filter)) return obj.filter(iterator, context); var results = []; each(obj, function(value, index, list) { @@ -126,9 +128,9 @@ return results; }; - // Determine whether all of the elements match a truth test. Delegate to - // JavaScript 1.6's every(), if it is present. - _.all = function(obj, iterator, context) { + // Determine whether all of the elements match a truth test. + // Delegates to JavaScript 1.6's native every if available. + _.every = function(obj, iterator, context) { iterator = iterator || _.identity; if (obj && _.isFunction(obj.every)) return obj.every(iterator, context); var result = true; @@ -138,9 +140,9 @@ return result; }; - // Determine if at least one element in the object matches a truth test. Use - // JavaScript 1.6's some(), if it exists. - _.any = function(obj, iterator, context) { + // Determine if at least one element in the object matches a truth test. + // Delegates to JavaScript 1.6's native some if available. + _.some = function(obj, iterator, context) { iterator = iterator || _.identity; if (obj && _.isFunction(obj.some)) return obj.some(iterator, context); var result = false; @@ -310,14 +312,15 @@ // If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), // we need this function. Return the position of the first occurence of an // item in an array, or -1 if the item is not included in the array. + // Delegates to JavaScript 1.8's native indexOf if available. _.indexOf = function(array, item) { if (array.indexOf) return array.indexOf(item); for (var i=0, l=array.length; i Date: Wed, 17 Feb 2010 09:26:34 -0600 Subject: [PATCH 03/13] delegate to native Object.keys in nightlies --- underscore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 20b18eba6..ac694195e 100644 --- a/underscore.js +++ b/underscore.js @@ -403,7 +403,8 @@ // ------------------------- Object Functions: ------------------------------ // Retrieve the names of an object's properties. - _.keys = function(obj) { + // ECMA5 has Object.keys(obj) in webkit nightlies + _.keys = Object.keys || function(obj) { if (_.isArray(obj)) return _.range(0, obj.length); var keys = []; for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); From c43de549bacd3f6def734317656ddae7fa52c4cf Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Wed, 17 Feb 2010 09:41:18 -0600 Subject: [PATCH 04/13] Create #alias method, callable on any object, _ by default. While I'm not a fan of making abstractions where a simpe solution exists, I think this is good as it makes a public API for extending underscore, therefore making it "ok" to make your own alias names. Also give us some future proofing in case we ever add in hooks on method definition. For example, right now if you add a method or make an alias in user code, it isn't added to the wrapper prototype. Added tests and inline docs, no external docs --- test/objects.js | 17 ++++++++++++++++- underscore.js | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/test/objects.js b/test/objects.js index c1c4aa327..8743eaed8 100644 --- a/test/objects.js +++ b/test/objects.js @@ -11,7 +11,7 @@ $(document).ready(function() { }); test("objects: functions", function() { - var expected = ["all", "any", "bind", "bindAll", "breakLoop", "clone", "compact", + var expected = ["alias", "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", "isArguments", "isArray", "isDate", "isElement", "isEmpty", "isEqual", @@ -183,4 +183,19 @@ $(document).ready(function() { value(); ok(returned == 6 && intercepted == 6, 'can use tapped objects in a chain'); }); + + + test("objects: alias", function() { + _.alias('isEqual', 'isTheSame'); + ok(_.isTheSame(9, 9), 'by default aliases methods on underscore'); + delete _.isTheSame; + // + var o = { hi: function () {return 9;} }; + _.alias(o, 'hi', 'ho'); + equals(o.ho(), 9, 'can add an alias on another object'); + _.alias(o, 'hi', 'there', 'sir'); + ok(o.hi==o.sir, 'can add multiple aliases'); + }); + + }); diff --git a/underscore.js b/underscore.js index ac694195e..538019fc8 100644 --- a/underscore.js +++ b/underscore.js @@ -440,6 +440,31 @@ return obj; }; + // Alias a method on an object to another name(s). + // If the first argument is NOT an object, then the object is assumed to + // be underscore. + // The first string argument is the existing method name, any following + // strings will be aliased to that name. + // Returns the object for chainability. + // + // Examples: + // + // Alias isEquals to isSame and eql on underscore: + // _.alias('isEqual', 'isSame', 'eql'); + // + // Alias `toString` to `to_s` on a given object: + // _.alias({}, 'toString', 'to_s') + // + // Implementation: explicitly cast arguments to array as calling mutating + // array methods on arguments object has strange behavior (at least in FF 3.6) + _.alias = function () { + var args = _.toArray(arguments), + obj = (typeof args[0] === 'string')? _ : args.shift(), + fn = obj[args.shift()]; + each(args, function (alias) { obj[alias] = fn; }); + return obj; + }; + // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { // Check object identity. @@ -593,15 +618,15 @@ // ------------------------------- Aliases ---------------------------------- - _.each = _.forEach; - _.foldl = _.inject = _.reduce; - _.foldr = _.reduceRight; - _.select = _.filter; - _.all = _.every; - _.any = _.some; - _.head = _.first; - _.tail = _.rest; - _.methods = _.functions; + _.alias('forEach', 'each'). + alias('reduce', 'foldl', 'inject'). + alias('reduceRight', 'foldr'). + alias('filter', 'select'). + alias('every', 'all'). + alias('some', 'any'). + alias('first', 'head'). + alias('rest', 'tail'). + alias('functions', 'methods'); // ------------------------ Setup the OOP Wrapper: -------------------------- From 386ee8ade99a1ec68378e4e0caba9c3ac28e91f8 Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Wed, 17 Feb 2010 09:45:24 -0600 Subject: [PATCH 05/13] add reference to underscore.js on docs page, since this is a good place to play around with it in the console --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index 8e26a9659..7332f4c88 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ Underscore.js +