From 02ede85b539a89a44a71ce098f09a9553a3a6890 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 25 Oct 2009 14:36:12 -0400 Subject: [PATCH 001/533] first underscore.js commit -- pulled out from documentcloud's core.js --- underscore.js | 368 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 underscore.js diff --git a/underscore.js b/underscore.js new file mode 100644 index 000000000..74d1d9385 --- /dev/null +++ b/underscore.js @@ -0,0 +1,368 @@ +// Javascript can be so much more pleasant when it's functional -- re-implement +// a bunch of utility methods from Prototype and Steele's Functional... +window._ = { + + // The cornerstone, an each implementation. + // Handles objects implementing forEach, _each, arrays, and raw objects. + each : function(obj, iterator, context) { + var index = 0; + try { + if (obj.forEach) { + obj.forEach(iterator, context); + } else if (obj.length) { + for (var i=0; i= result) result = value; + }); + return result; + }, + + // Return the minimum element (or element-based computation). + min : function(obj, iterator, context) { + var result; + _.each(obj, function(value, index) { + value = iterator ? iterator.call(context, value, index) : value; + if (result == null || value < result) result = value; + }); + return result; + }, + + // Optimized version of a common use case of map: fetching a property. + pluck : function(obj, key) { + var results = []; + _.each(obj, function(value){ results.push(value[key]); }); + return results; + }, + + // Return all the elements for which a truth test fails. + reject : function(obj, iterator, context) { + var results = []; + _.each(obj, function(value, index) { + if (!iterator.call(context, value, index)) results.push(value); + }); + return results; + }, + + // Sort the object's values by a criteria produced by an iterator. + sortBy : function(obj, iterator, context) { + return _.pluck(_.map(obj, function(value, index) { + return { + value : value, + criteria : iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }), 'value'); + }, + + // Use a comparator function to figure out at what index an object should + // be inserted so as to maintain order. Uses binary search. + sortedIndex : function(array, comparator, obj) { + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >> 1; + comparator(array[mid], obj) < 0 ? low = mid + 1 : high = mid; + } + return low; + }, + + // Convert anything iterable into a real, live array. + toArray : function(iterable) { + if (!iterable) return []; + if (_.isArray(iterable)) return iterable; + return _.map(iterable, function(val){ return val; }); + }, + + // Return the number of elements in an object. + size : function(obj) { + _.toArray(obj).length; + }, + + //------------- The following methods only apply to arrays. ----------------- + + // Get the first element of an array. + first : function(array) { + return array[0]; + }, + + // Get the last element of an array. + last : function(array) { + return array[array.length - 1]; + }, + + // Trim out all falsy values from an array. + compact : function(array) { + return _.select(array, function(value){ return !!value; }); + }, + + // Return a completely flattened version of an array. + flatten : function(array) { + return _.inject(array, [], function(memo, value) { + if (_.isArray(value)) return memo.concat(_.flatten(value)); + memo.push(value); + return memo; + }); + }, + + // Return a version of the array that does not contain the specified value. + without : function(array) { + var values = array.slice.call(arguments, 0); + return _.select(function(value){ return !_.include(values, value); }); + }, + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + uniq : function(array, sorted) { + return _.inject(array, [], function(memo, el, i) { + if (0 == i || (sorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); + return memo; + }); + }, + + // Produce an array that contains every item shared between two given arrays. + intersect : function(array1, array2) { + return _.select(_.uniq(array1), function(item1) { + return _.detect(array2, function(item2) { return item1 === item2; }); + }); + }, + + // If the browser doesn't supply us with indexOf, we might 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. + // indexOf : function(array, item) { + // var length = array.length; + // for (i=0; i)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + return data ? fn(data) : fn; + } + +}; From fe7156e4ea6d26894f1270563c8bf290364e1bed Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 25 Oct 2009 20:36:28 -0400 Subject: [PATCH 002/533] first round of tests... --- test/example.js | 29 ++ test/test.html | 18 + test/test.js | 59 +++ test/vendor/jquery.js | 19 + test/vendor/qunit.css | 17 + test/vendor/qunit.js | 997 ++++++++++++++++++++++++++++++++++++++++++ underscore.js | 2 + 7 files changed, 1141 insertions(+) create mode 100644 test/example.js create mode 100644 test/test.html create mode 100644 test/test.js create mode 100644 test/vendor/jquery.js create mode 100644 test/vendor/qunit.css create mode 100644 test/vendor/qunit.js diff --git a/test/example.js b/test/example.js new file mode 100644 index 000000000..48ed383fe --- /dev/null +++ b/test/example.js @@ -0,0 +1,29 @@ +$(document).ready(function(){ + + test("a basic test example", function() { + ok( true, "this test is fine" ); + var value = "hello"; + equals( "hello", value, "We expect value to be hello" ); + }); + + module("Module A"); + + test("first test within module", function() { + ok( true, "all pass" ); + }); + + test("second test within module", function() { + ok( true, "all pass" ); + }); + + module("Module B"); + + test("some other test", function() { + expect(2); + equals( true, false, "failing test" ); + equals( true, true, "passing test" ); + }); + +}); + + diff --git a/test/test.html b/test/test.html new file mode 100644 index 000000000..962718958 --- /dev/null +++ b/test/test.html @@ -0,0 +1,18 @@ + + + + + + + + + + + +

Underscore Test Suite

+

+

+
    + + \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 000000000..569cbe455 --- /dev/null +++ b/test/test.js @@ -0,0 +1,59 @@ +$(document).ready(function() { + + module("Collection functions (each, any, select, and so on...)"); + + test("collections: _.each", function() { + _.each(numbers, function(num, i){ + equals(num, i + 1, 'each iterators provide value and iteration count'); + }); + }); + + test('collections: _.each and throwing "__break__"', function() { + var answer = null; + _.each(numbers, function(num){ + if ((answer = num) == 2) throw '__break__'; + }); + equals(2, answer, 'the loop broke in the middle'); + }); + + test('collections: _.each can take a context object', function() { + var answers = []; + _.each(numbers, function(num) { + answers.push(num * this.multiplier); + }, {multiplier : 5}); + equals('5, 10, 15', answers.join(', '), 'context object property accessed'); + }); + + test('collections: _.all', function() { + ok(_.all([]), 'the empty set'); + ok(_.all([true, true, true]), 'all true values'); + ok(!_.all([true, false, true]), 'one false value'); + ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); + ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); + }); + + test('collections: _.any', function() { + ok(!_.any([]), 'the empty set'); + ok(!_.any([false, false, false]), 'all false values'); + ok(_.any([false, false, true]), 'one true value'); + ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); + ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); + }); + + test('collections: _.map', function() { + var doubled = _.map(numbers, function(num){ return num * 2; }); + equals('2, 4, 6', doubled.join(', '), 'doubled numbers'); + var tripled = _.map(numbers, function(num){ return num * this.multiplier; }, {multiplier : 3}); + equals('3, 6, 9', tripled.join(', '), 'tripled numbers with context'); + }); + + test('collections: _.detect', function() { + var result = _.detect(numbers, function(num){ return num * 2 == 4; }); + equals(2, result, 'found the first "2" and broke the loop'); + }); + + test('collections: _.select', function() { + var evens = _.select(function([1,2,3,4,5,6], )) + }); + +}); \ No newline at end of file diff --git a/test/vendor/jquery.js b/test/vendor/jquery.js new file mode 100644 index 000000000..b1ae21d8b --- /dev/null +++ b/test/vendor/jquery.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

    ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
    ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
    ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file diff --git a/test/vendor/qunit.css b/test/vendor/qunit.css new file mode 100644 index 000000000..660cd3c1a --- /dev/null +++ b/test/vendor/qunit.css @@ -0,0 +1,17 @@ +h1#qunit-header { padding: 15px; font-size: large; background-color: #06b; color: white; font-family: 'trebuchet ms', verdana, arial; margin: 0; } +h1#qunit-header a { color: white; } + +h2#qunit-banner { height: 2em; border-bottom: 1px solid white; background-color: #eee; margin: 0; font-family: 'trebuchet ms', verdana, arial; } +h2#qunit-banner.pass { background-color: green; } +h2#qunit-banner.fail { background-color: red; } + +h2#qunit-userAgent { padding: 10px; background-color: #eee; color: black; margin: 0; font-size: small; font-weight: normal; font-family: 'trebuchet ms', verdana, arial; font-size: 10pt; } + +div#qunit-testrunner-toolbar { background: #eee; border-top: 1px solid black; padding: 10px; font-family: 'trebuchet ms', verdana, arial; margin: 0; font-size: 10pt; } + +ol#qunit-tests { font-family: 'trebuchet ms', verdana, arial; font-size: 10pt; } +ol#qunit-tests li strong { cursor:pointer; } +ol#qunit-tests .pass { color: green; } +ol#qunit-tests .fail { color: red; } + +p#qunit-testresult { margin-left: 1em; font-size: 10pt; font-family: 'trebuchet ms', verdana, arial; } \ No newline at end of file diff --git a/test/vendor/qunit.js b/test/vendor/qunit.js new file mode 100644 index 000000000..1fc2fe13a --- /dev/null +++ b/test/vendor/qunit.js @@ -0,0 +1,997 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // Initialize the configuration options + init: function init() { + config = { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + blocking: false, + autorun: false, + assertions: [], + filters: [], + queue: [] + }; + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + // call on start of module test to prepend name to all tests + module: function module(name, testEnvironment) { + + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name, testEnvironment ); + }); + }, + + asyncTest: function asyncTest(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function test(testName, expected, callback, async) { + var name = testName, testEnvironment = {}; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + if ( config.currentModule ) { + name = config.currentModule + " module: " + name; + } + + if ( !validTest(name) ) { + return; + } + + synchronize(function() { + QUnit.testStart( testName ); + + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + + config.assertions = []; + config.expected = null; + + if ( arguments.length >= 3 ) { + config.expected = callback; + callback = arguments[2]; + } + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + + if ( async ) { + QUnit.stop(); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }); + + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); + } + + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + ol.style.display = "none"; + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || "(no message)"; + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + var b = document.createElement("strong"); + b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = (e || window.event).target; + if ( target.nodeName.toLowerCase() === "strong" ) { + var text = "", node = target.firstChild; + + while ( node.nodeType === 3 ) { + text += node.nodeValue; + node = node.nextSibling; + } + + text = text.replace(/(^\s*|\s*$)/g, ""); + + if ( window.location ) { + window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); + } + } + }); + + var li = document.createElement("li"); + li.className = bad ? "fail" : "pass"; + li.appendChild( b ); + li.appendChild( ol ); + tests.appendChild( li ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function expect(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function ok(a, msg) { + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equals( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equals: function equals(actual, expected, message) { + push(expected == actual, actual, expected, message); + }, + + same: function(a, b, message) { + push(QUnit.equiv(a, b), a, b, message); + }, + + start: function start() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function stop(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + */ + reset: function reset() { + if ( window.jQuery ) { + jQuery("#main").html( config.fixture ); + jQuery.event.global = {}; + jQuery.ajaxSettings = extend({}, config.ajaxSettings); + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function triggerEvent( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Logging callbacks + done: function done(failures, total) {}, + log: function log(result, message) {}, + testStart: function testStart(name) {}, + testDone: function testDone(name, failures, total) {}, + moduleStart: function moduleStart(name, testEnvironment) {}, + moduleDone: function moduleDone(name, failures, total) {} +}; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if ( window.jQuery ) { + config.ajaxSettings = window.jQuery.ajaxSettings; + } + + QUnit.start(); +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.
    ', + '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); + + if ( banner ) { + banner.className += " " + (config.stats.bad ? "fail" : "pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function push(result, actual, expected, message) { + message = message || (result ? "okay" : "failed"); + QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + while ( config.queue.length && !config.blocking ) { + config.queue.shift()(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + + + // Determine what is o. + function hoozit(o) { + if (o.constructor === String) { + return "string"; + + } else if (o.constructor === Boolean) { + return "boolean"; + + } else if (o.constructor === Number) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + } else if (typeof o === "undefined") { + return "undefined"; + + // consider: typeof null === object + } else if (o === null) { + return "null"; + + // consider: typeof [] === object + } else if (o instanceof Array) { + return "array"; + + // consider: typeof new Date() === object + } else if (o instanceof Date) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (o instanceof RegExp) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (o instanceof Function) { + return "function"; + } else { + return undefined; + } + } + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = hoozit(o); + if (prop) { + if (hoozit(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return hoozit(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i; + var len; + + // b could be an object literal here + if ( ! (hoozit(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + for (i = 0; i < len; i++) { + if ( ! innerEquiv(a[i], b[i])) { + return false; + } + } + return true; + }, + + "object": function (b, a) { + var i; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of strings + + // comparing constructors is more strict than using instanceof + if ( a.constructor !== b.constructor) { + return false; + } + + // stack constructor before traversing properties + callers.push(a.constructor); + + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep + + aProperties.push(i); // collect a's properties + + if ( ! innerEquiv(a[i], b[i])) { + eq = false; + } + } + + callers.pop(); // unstack, we are done + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv(aProperties.sort(), bProperties.sort()); + } + }; + }(); + + innerEquiv = function () { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function (a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [b, a]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); + }; + + return innerEquiv; + +}(); + +/** + * jsDump + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 5/15/2008 + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return '"' + str.toString().replace(/"/g, '\\"') + '"'; + }; + function literal( o ) { + return o + ''; + }; + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) + arr = arr.join( ',' + s + inner ); + if ( !arr ) + return pre + post; + return [ pre, inner + arr, base + post ].join(s); + }; + function array( arr ) { + var i = arr.length, ret = Array(i); + this.up(); + while ( i-- ) + ret[i] = this.parse( arr[i] ); + this.down(); + return join( '[', ret, ']' ); + }; + + var reName = /^function (\w+)/; + + var jsDump = { + parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance + var parser = this.parsers[ type || this.typeOf(obj) ]; + type = typeof parser; + + return type == 'function' ? parser.call( this, obj ) : + type == 'string' ? parser : + this.parsers.error; + }, + typeOf:function( obj ) { + var type = typeof obj, + f = 'function';//we'll use it 3 times, save it + return type != 'object' && type != f ? type : + !obj ? 'null' : + obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions + obj.getHours ? 'date' : + obj.scrollBy ? 'window' : + obj.nodeName == '#document' ? 'document' : + obj.nodeName ? 'node' : + obj.item ? 'nodelist' : // Safari reports nodelists as functions + obj.callee ? 'arguments' : + obj.call || obj.constructor != Array && //an array would also fall on this hack + (obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects + 'length' in obj ? 'array' : + type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +})(this); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 74d1d9385..6659c9c81 100644 --- a/underscore.js +++ b/underscore.js @@ -35,6 +35,7 @@ window._ = { // 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) { + iterator = iterator || function(v){ return v; }; if (obj.every) return obj.every(iterator, context); var result = true; _.each(obj, function(value, index) { @@ -47,6 +48,7 @@ window._ = { // 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) { + iterator = iterator || function(v) { return v; }; if (obj.some) return obj.some(iterator, context); var result = false; _.each(obj, function(value, index) { From 5c46c60b06a210d702b75aa3493655eb39c152ce Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 26 Oct 2009 08:40:14 -0400 Subject: [PATCH 003/533] finished off the complete test suite for underscore -- let's polish off the corners and provide some docs --- test/arrays.js | 45 ++++++++++ test/collections.js | 123 +++++++++++++++++++++++++++ test/example.js | 29 ------- test/functions.js | 25 ++++++ test/objects.js | 64 ++++++++++++++ test/test.html | 6 +- test/test.js | 59 ------------- test/utility.js | 20 +++++ underscore.js | 202 ++++++++++++++++++++++---------------------- 9 files changed, 385 insertions(+), 188 deletions(-) create mode 100644 test/arrays.js create mode 100644 test/collections.js delete mode 100644 test/example.js create mode 100644 test/functions.js create mode 100644 test/objects.js delete mode 100644 test/test.js create mode 100644 test/utility.js diff --git a/test/arrays.js b/test/arrays.js new file mode 100644 index 000000000..90a1e2e25 --- /dev/null +++ b/test/arrays.js @@ -0,0 +1,45 @@ +$(document).ready(function() { + + module("Array-only functions (last, compact, uniq, and so on...)"); + + test("arrays: first", function() { + equals(_.first([1,2,3]), 1, 'can pull out the first element of an array'); + }); + + test("arrays: last", function() { + equals(_.last([1,2,3]), 3, 'can pull out the last element of an array'); + }); + + test("arrays: compact", function() { + equals(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values'); + }); + + test("arrays: flatten", function() { + var list = [1, [2], [3, [[[4]]]]]; + equals(_.flatten(list).join(', '), '1, 2, 3, 4', 'can flatten nested arrays'); + }); + + test("arrays: without", function() { + var list = [1, 2, 1, 0, 3, 1, 4]; + equals(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object'); + }); + + test("arrays: uniq", function() { + var list = [1, 2, 1, 3, 1, 4]; + equals(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array'); + var list = [1, 1, 1, 2, 2, 3]; + equals(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster'); + }); + + test("arrays: intersect", function() { + var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; + equals(_.intersect(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays'); + }); + + test("arrays: indexOf", function() { + var numbers = [1, 2, 3]; + numbers.indexOf = null; + equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); + }); + +}); diff --git a/test/collections.js b/test/collections.js new file mode 100644 index 000000000..1cb3f9e5f --- /dev/null +++ b/test/collections.js @@ -0,0 +1,123 @@ +$(document).ready(function() { + + module("Collection functions (each, any, select, and so on...)"); + + test("collections: each", function() { + _.each([1, 2, 3], function(num, i){ + equals(num, i + 1, 'each iterators provide value and iteration count'); + }); + }); + + test('collections: each and throwing "__break__"', function() { + var answer = null; + _.each([1, 2, 3], function(num){ + if ((answer = num) == 2) throw '__break__'; + }); + equals(answer, 2, 'the loop broke in the middle'); + }); + + test('collections: each can take a context object', function() { + 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'); + }); + + test('collections: all', function() { + ok(_.all([]), 'the empty set'); + ok(_.all([true, true, true]), 'all true values'); + ok(!_.all([true, false, true]), 'one false value'); + ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); + ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); + }); + + test('collections: any', function() { + ok(!_.any([]), 'the empty set'); + ok(!_.any([false, false, false]), 'all false values'); + ok(_.any([false, false, true]), 'one true value'); + ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); + ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); + }); + + test('collections: map', function() { + var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); + equals(doubled.join(', '), '2, 4, 6', 'doubled numbers'); + var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3}); + equals(tripled.join(', '), '3, 6, 9', 'tripled numbers with context'); + }); + + test('collections: inject', function() { + var sum = _.inject([1,2,3], 0, function(sum, num){ return sum + num; }); + equals(sum, 6, 'can sum up an array'); + }); + + test('collections: detect', function() { + var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; }); + equals(result, 2, 'found the first "2" and broke the loop'); + }); + + test('collections: select', function() { + var evens = _.select([1,2,3,4,5,6], function(num){ return num % 2 == 0; }); + equals(evens.join(', '), '2, 4, 6', 'selected each even number'); + }); + + test('collections: reject', function() { + var odds = _.reject([1,2,3,4,5,6], function(num){ return num % 2 == 0; }); + equals(odds.join(', '), '1, 3, 5', 'rejected each even number'); + }); + + test('collections: include', function() { + ok(_.include([1,2,3], 2), 'two is in the array'); + ok(!_.include([1,3,9], 2), 'two is not in the array'); + ok(_.include({moe:1, larry:3, curly:9}, 3), '_.include on objects checks their values'); + }); + + test('collections: invoke', function() { + var list = [[5, 1, 7], [3, 2, 1]]; + var result = _.invoke(list, 'sort'); + equals(result[0].join(', '), '1, 5, 7', 'first array sorted'); + equals(result[1].join(', '), '1, 2, 3', 'second array sorted'); + }); + + test('collections: pluck', function() { + var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; + equals(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects'); + }); + + test('collections: max', function() { + equals(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); + var neg = _.max([1, 2, 3], function(num){ return -num; }); + equals(neg, 1, 'can perform a computation-based max'); + }); + + test('collections: min', function() { + equals(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); + var neg = _.min([1, 2, 3], function(num){ return -num; }); + equals(neg, 3, 'can perform a computation-based min'); + }); + + test('collections: sortBy', function() { + var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}]; + people = _.sortBy(people, function(person){ return person.age; }); + equals(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age'); + }); + + test('collections: sortedIndex', function() { + var numbers = [10, 20, 30, 40, 50], num = 35; + var index = _.sortedIndex(numbers, function(a, b) { return a < b ? -1 : a > b ? 1 : 0; }, num); + equals(index, 3, '35 should be inserted at index 3'); + }); + + test('collections: toArray', function() { + ok(!_.isArray(arguments), 'arguments object is not an array'); + ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); + var numbers = _.toArray({one : 1, two : 2, three : 3}); + equals(_.pluck(numbers, '0').join(', '), 'one, two, three', 'object flattened into array'); + }); + + test('collections: size', function() { + equals(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object'); + }); + +}); diff --git a/test/example.js b/test/example.js deleted file mode 100644 index 48ed383fe..000000000 --- a/test/example.js +++ /dev/null @@ -1,29 +0,0 @@ -$(document).ready(function(){ - - test("a basic test example", function() { - ok( true, "this test is fine" ); - var value = "hello"; - equals( "hello", value, "We expect value to be hello" ); - }); - - module("Module A"); - - test("first test within module", function() { - ok( true, "all pass" ); - }); - - test("second test within module", function() { - ok( true, "all pass" ); - }); - - module("Module B"); - - test("some other test", function() { - expect(2); - equals( true, false, "failing test" ); - equals( true, true, "passing test" ); - }); - -}); - - diff --git a/test/functions.js b/test/functions.js new file mode 100644 index 000000000..0f9362def --- /dev/null +++ b/test/functions.js @@ -0,0 +1,25 @@ +$(document).ready(function() { + + module("Function functions (bind, bindAll, and so on...)"); + + test("functions: bind", function() { + var context = {name : 'moe'}; + var func = function() { return "name: " + this.name; }; + var bound = _.bind(func, context); + equals(bound(), 'name: moe', 'can bind a function to a context'); + }); + + test("functions: bindAll", function() { + var curly = {name : 'curly'}, moe = { + name : 'moe', + getName : function() { return 'name: ' + this.name; }, + sayHi : function() { return 'hi: ' + this.name; } + }; + curly.getName = moe.getName; + _.bindAll('getName', 'sayHi', moe); + 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'); + }); + +}); diff --git a/test/objects.js b/test/objects.js new file mode 100644 index 000000000..347230b59 --- /dev/null +++ b/test/objects.js @@ -0,0 +1,64 @@ +$(document).ready(function() { + + module("Object functions (values, extend, isEqual, and so on...)"); + + test("objects: keys", function() { + equals(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object'); + }); + + test("objects: values", function() { + equals(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object'); + }); + + test("objects: extend", function() { + var source = {name : 'moe'}, dest = {age : 50}; + _.extend(dest, source); + equals(dest.name, 'moe', 'can extend an object with the attributes of another'); + }); + + test("objects: clone", function() { + var moe = {name : 'moe', lucky : [13, 27, 34]}; + var clone = _.clone(moe); + equals(clone.name, 'moe', 'the clone as the attributes of the original'); + clone.name = 'curly'; + ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original'); + clone.lucky.push(101); + equals(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original'); + }); + + test("objects: isEqual", function() { + var moe = {name : 'moe', lucky : [13, 27, 34]}; + var clone = {name : 'moe', lucky : [13, 27, 34]}; + ok(moe != clone, 'basic equality between objects is false'); + ok(_.isEqual(moe, clone), 'deep equality is true'); + }); + + test("objects: isElement", function() { + ok(!_.isElement('div'), 'strings are not dom elements'); + ok(_.isElement($('html')[0]), 'the html tag is a DOM element'); + }); + + test("objects: isArray", function() { + ok(!_.isArray(arguments), 'the arguments object is not an array'); + ok(_.isArray([1, 2, 3]), 'but arrays are'); + }); + + test("objects: isFunction", function() { + ok(!_.isFunction([1, 2, 3]), 'arrays are not functions'); + ok(!_.isFunction('moe'), 'strings are not functions'); + ok(_.isFunction(_.isFunction), 'but functions are'); + }); + + test("objects: isUndefined", function() { + ok(!_.isUndefined(1), 'numbers are defined'); + ok(!_.isUndefined(null), 'null is defined'); + ok(!_.isUndefined(false), 'false is defined'); + ok(_.isUndefined(), 'nothing is undefined'); + ok(_.isUndefined(undefined), 'undefined is undefined'); + }); + + test("objects: toString", function() { + equals(_.toString([1, 2, 3]), '1,2,3', 'object can be converted to printable strings'); + }); + +}); diff --git a/test/test.html b/test/test.html index 962718958..9c8220344 100644 --- a/test/test.html +++ b/test/test.html @@ -6,7 +6,11 @@ - + + + + + diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 569cbe455..000000000 --- a/test/test.js +++ /dev/null @@ -1,59 +0,0 @@ -$(document).ready(function() { - - module("Collection functions (each, any, select, and so on...)"); - - test("collections: _.each", function() { - _.each(numbers, function(num, i){ - equals(num, i + 1, 'each iterators provide value and iteration count'); - }); - }); - - test('collections: _.each and throwing "__break__"', function() { - var answer = null; - _.each(numbers, function(num){ - if ((answer = num) == 2) throw '__break__'; - }); - equals(2, answer, 'the loop broke in the middle'); - }); - - test('collections: _.each can take a context object', function() { - var answers = []; - _.each(numbers, function(num) { - answers.push(num * this.multiplier); - }, {multiplier : 5}); - equals('5, 10, 15', answers.join(', '), 'context object property accessed'); - }); - - test('collections: _.all', function() { - ok(_.all([]), 'the empty set'); - ok(_.all([true, true, true]), 'all true values'); - ok(!_.all([true, false, true]), 'one false value'); - ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); - ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); - }); - - test('collections: _.any', function() { - ok(!_.any([]), 'the empty set'); - ok(!_.any([false, false, false]), 'all false values'); - ok(_.any([false, false, true]), 'one true value'); - ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); - ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); - }); - - test('collections: _.map', function() { - var doubled = _.map(numbers, function(num){ return num * 2; }); - equals('2, 4, 6', doubled.join(', '), 'doubled numbers'); - var tripled = _.map(numbers, function(num){ return num * this.multiplier; }, {multiplier : 3}); - equals('3, 6, 9', tripled.join(', '), 'tripled numbers with context'); - }); - - test('collections: _.detect', function() { - var result = _.detect(numbers, function(num){ return num * 2 == 4; }); - equals(2, result, 'found the first "2" and broke the loop'); - }); - - test('collections: _.select', function() { - var evens = _.select(function([1,2,3,4,5,6], )) - }); - -}); \ No newline at end of file diff --git a/test/utility.js b/test/utility.js new file mode 100644 index 000000000..cfaaaace3 --- /dev/null +++ b/test/utility.js @@ -0,0 +1,20 @@ +$(document).ready(function() { + + module("Utility functions (uniqueId, template)"); + + test("utility: uniqueId", function() { + var ids = [], i = 0; + while(i++ < 100) ids.push(_.uniqueId()); + equals(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids'); + }); + + test("utility: template", function() { + var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); + var result = basicTemplate({thing : 'This'}); + equals(result, "This is gettin' on my noives!", 'can do basic attribute interpolation'); + var fancyTemplate = _.template("<% for (key in people) { %>
  1. <%= people[key] %>
  2. <% } %>"); + result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}}); + equals(result, "
  3. Moe
  4. Larry
  5. Curly
  6. ", 'can run arbitrary javascript in templates'); + }); + +}); diff --git a/underscore.js b/underscore.js index 6659c9c81..c32e87a2d 100644 --- a/underscore.js +++ b/underscore.js @@ -10,13 +10,9 @@ window._ = { if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length) { - for (var i=0; i= result) result = value; - }); - return result; - }, - - // Return the minimum element (or element-based computation). - min : function(obj, iterator, context) { - var result; - _.each(obj, function(value, index) { - value = iterator ? iterator.call(context, value, index) : value; - if (result == null || value < result) result = value; - }); - return result; - }, - // Optimized version of a common use case of map: fetching a property. pluck : function(obj, key) { var results = []; @@ -147,13 +133,26 @@ window._ = { return results; }, - // Return all the elements for which a truth test fails. - reject : function(obj, iterator, context) { - var results = []; + // Return the maximum item or (item-based computation). + max : function(obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); + var result; _.each(obj, function(value, index) { - if (!iterator.call(context, value, index)) results.push(value); + var computed = iterator ? iterator.call(context, value, index) : value; + if (result == null || computed >= result.computed) result = {value : value, computed : computed}; }); - return results; + return result.value; + }, + + // Return the minimum element (or element-based computation). + min : function(obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); + var result; + _.each(obj, function(value, index) { + var computed = iterator ? iterator.call(context, value, index) : value; + if (result == null || computed < result.computed) result = {value : value, computed : computed}; + }); + return result.value; }, // Sort the object's values by a criteria produced by an iterator. @@ -189,7 +188,7 @@ window._ = { // Return the number of elements in an object. size : function(obj) { - _.toArray(obj).length; + return _.toArray(obj).length; }, //------------- The following methods only apply to arrays. ----------------- @@ -218,10 +217,10 @@ window._ = { }); }, - // Return a version of the array that does not contain the specified value. + // Return a version of the array that does not contain the specified value(s). without : function(array) { var values = array.slice.call(arguments, 0); - return _.select(function(value){ return !_.include(values, value); }); + return _.select(array, function(value){ return !_.include(values, value); }); }, // Produce a duplicate-free version of the array. If the array has already @@ -240,14 +239,38 @@ window._ = { }); }, - // If the browser doesn't supply us with indexOf, we might 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. - // indexOf : function(array, item) { - // var length = array.length; - // for (i=0; i Date: Mon, 26 Oct 2009 08:43:10 -0400 Subject: [PATCH 004/533] merged some tests --- test/arrays.js | 1 + test/collections.js | 22 +++++++++------------- test/objects.js | 2 ++ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 90a1e2e25..97ff295a9 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -27,6 +27,7 @@ $(document).ready(function() { test("arrays: uniq", function() { var list = [1, 2, 1, 3, 1, 4]; equals(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array'); + var list = [1, 1, 1, 2, 2, 3]; equals(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster'); }); diff --git a/test/collections.js b/test/collections.js index 1cb3f9e5f..4aebedba4 100644 --- a/test/collections.js +++ b/test/collections.js @@ -3,24 +3,16 @@ $(document).ready(function() { module("Collection functions (each, any, select, and so on...)"); test("collections: each", function() { - _.each([1, 2, 3], function(num, i){ + _.each([1, 2, 3], function(num, i) { equals(num, i + 1, 'each iterators provide value and iteration count'); }); - }); - - test('collections: each and throwing "__break__"', function() { + var answer = null; - _.each([1, 2, 3], function(num){ - if ((answer = num) == 2) throw '__break__'; - }); + _.each([1, 2, 3], function(num){ if ((answer = num) == 2) throw '__break__'; }); equals(answer, 2, 'the loop broke in the middle'); - }); - - test('collections: each can take a context object', function() { + var answers = []; - _.each([1, 2, 3], function(num) { - answers.push(num * this.multiplier); - }, {multiplier : 5}); + _.each([1, 2, 3], function(num) { answers.push(num * this.multiplier);}, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); }); @@ -43,6 +35,7 @@ $(document).ready(function() { test('collections: map', function() { var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); equals(doubled.join(', '), '2, 4, 6', 'doubled numbers'); + var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3}); equals(tripled.join(', '), '3, 6, 9', 'tripled numbers with context'); }); @@ -87,12 +80,14 @@ $(document).ready(function() { test('collections: max', function() { equals(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); + var neg = _.max([1, 2, 3], function(num){ return -num; }); equals(neg, 1, 'can perform a computation-based max'); }); test('collections: min', function() { equals(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); + var neg = _.min([1, 2, 3], function(num){ return -num; }); equals(neg, 3, 'can perform a computation-based min'); }); @@ -112,6 +107,7 @@ $(document).ready(function() { test('collections: toArray', function() { ok(!_.isArray(arguments), 'arguments object is not an array'); ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); + var numbers = _.toArray({one : 1, two : 2, three : 3}); equals(_.pluck(numbers, '0').join(', '), 'one, two, three', 'object flattened into array'); }); diff --git a/test/objects.js b/test/objects.js index 347230b59..624d67bfe 100644 --- a/test/objects.js +++ b/test/objects.js @@ -20,8 +20,10 @@ $(document).ready(function() { var moe = {name : 'moe', lucky : [13, 27, 34]}; var clone = _.clone(moe); equals(clone.name, 'moe', 'the clone as the attributes of the original'); + clone.name = 'curly'; ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original'); + clone.lucky.push(101); equals(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original'); }); From d229e822e73e797b820bc7a5df05c65154fd903a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 26 Oct 2009 09:15:14 -0400 Subject: [PATCH 005/533] added an _.zip() --- test/arrays.js | 6 ++++ test/collections.js | 32 +++++++++++----------- test/functions.js | 7 +++++ underscore.js | 67 ++++++++++++++++++++++++++------------------- 4 files changed, 68 insertions(+), 44 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 97ff295a9..7b4e45692 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -37,6 +37,12 @@ $(document).ready(function() { equals(_.intersect(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays'); }); + test('arrays: zip', function() { + var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true]; + var stooges = _.zip(names, ages, leaders); + equals(_.toString(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths'); + }); + test("arrays: indexOf", function() { var numbers = [1, 2, 3]; numbers.indexOf = null; diff --git a/test/collections.js b/test/collections.js index 4aebedba4..1ba3512b7 100644 --- a/test/collections.js +++ b/test/collections.js @@ -16,22 +16,6 @@ $(document).ready(function() { equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); }); - test('collections: all', function() { - ok(_.all([]), 'the empty set'); - ok(_.all([true, true, true]), 'all true values'); - ok(!_.all([true, false, true]), 'one false value'); - ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); - ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); - }); - - test('collections: any', function() { - ok(!_.any([]), 'the empty set'); - ok(!_.any([false, false, false]), 'all false values'); - ok(_.any([false, false, true]), 'one true value'); - ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); - ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); - }); - test('collections: map', function() { var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); equals(doubled.join(', '), '2, 4, 6', 'doubled numbers'); @@ -60,6 +44,22 @@ $(document).ready(function() { equals(odds.join(', '), '1, 3, 5', 'rejected each even number'); }); + test('collections: all', function() { + ok(_.all([]), 'the empty set'); + ok(_.all([true, true, true]), 'all true values'); + ok(!_.all([true, false, true]), 'one false value'); + ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); + ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); + }); + + test('collections: any', function() { + ok(!_.any([]), 'the empty set'); + ok(!_.any([false, false, false]), 'all false values'); + ok(_.any([false, false, true]), 'one true value'); + ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); + ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); + }); + test('collections: include', function() { ok(_.include([1,2,3], 2), 'two is in the array'); ok(!_.include([1,3,9], 2), 'two is not in the array'); diff --git a/test/functions.js b/test/functions.js index 0f9362def..47de34699 100644 --- a/test/functions.js +++ b/test/functions.js @@ -7,6 +7,13 @@ $(document).ready(function() { var func = function() { return "name: " + this.name; }; var bound = _.bind(func, context); equals(bound(), 'name: moe', 'can bind a function to a context'); + + var func = function(salutation, name) { return salutation + ': ' + name; }; + func = _.bind(func, this, 'hello'); + equals(func('moe'), 'hello: moe', 'the function was partially applied in advance'); + + func = _.bind(func, this, 'curly'); + equals(func(), 'hello: curly', 'the function was completely applied in advance'); }); test("functions: bindAll", function() { diff --git a/underscore.js b/underscore.js index c32e87a2d..8f4ba6ee4 100644 --- a/underscore.js +++ b/underscore.js @@ -28,35 +28,10 @@ window._ = { return obj; }, - // 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) { - iterator = iterator || function(v){ return v; }; - if (obj.every) return obj.every(iterator, context); - var result = true; - _.each(obj, function(value, index) { - result = result && !!iterator.call(context, value, index); - if (!result) throw '__break__'; - }); - 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) { - iterator = iterator || function(v) { return v; }; - if (obj.some) return obj.some(iterator, context); - var result = false; - _.each(obj, function(value, index) { - if (result = !!iterator.call(context, value, index)) throw '__break__'; - }); - return result; - }, - // Return the results of applying the iterator to each element. Use Javascript // 1.6's version of map, if possible. map : function(obj, iterator, context) { - if (obj.map) return obj.map(iterator, context); + if (obj && obj.map) return obj.map(iterator, context); var results = []; _.each(obj, function(value, index) { results.push(iterator.call(context, value, index)); @@ -64,7 +39,8 @@ window._ = { return results; }, - // Aka reduce. Inject builds up a single result from a list of values. + // Inject builds up a single result from a list of values. Also known as + // reduce, and foldl. inject : function(obj, memo, iterator, context) { _.each(obj, function(value, index) { memo = iterator.call(context, memo, value, index); @@ -104,6 +80,31 @@ window._ = { 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) { + iterator = iterator || function(v){ return v; }; + if (obj.every) return obj.every(iterator, context); + var result = true; + _.each(obj, function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw '__break__'; + }); + 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) { + iterator = iterator || function(v) { return v; }; + if (obj.some) return obj.some(iterator, context); + var result = false; + _.each(obj, function(value, index) { + if (result = !!iterator.call(context, value, index)) throw '__break__'; + }); + return result; + }, + // Determine if a given value is included in the array or object, // based on '=='. include : function(obj, target) { @@ -239,6 +240,16 @@ window._ = { }); }, + // Zip together multiple lists into a single array -- elements that share + // an index go together. + zip : function() { + var args = _.toArray(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i=0; i Date: Mon, 26 Oct 2009 09:55:56 -0400 Subject: [PATCH 006/533] added a bunch of speed tests using jslitmus --- test/speed.js | 34 ++ test/test.html | 17 +- test/vendor/jslitmus.js | 670 ++++++++++++++++++++++++++++++++++++++++ test/vendor/qunit.css | 4 +- 4 files changed, 717 insertions(+), 8 deletions(-) create mode 100644 test/speed.js create mode 100644 test/vendor/jslitmus.js diff --git a/test/speed.js b/test/speed.js new file mode 100644 index 000000000..63cde52bb --- /dev/null +++ b/test/speed.js @@ -0,0 +1,34 @@ +(function() { + + var numbers = []; + for (var i=0; i<1000; i++) numbers.push(i); + var objects = _.map(numbers, function(n){ return {num : n}; }); + var randomized = _.sortBy(numbers, function(){ return Math.random(); }); + + JSLitmus.test('_.each()', function() { + var timesTwo = []; + _.each(numbers, function(num){ timesTwo.push(num * 2); }); + return timesTwo; + }); + + JSLitmus.test('_.map()', function() { + return _.map(objects, function(obj){ return obj.num; }); + }); + + JSLitmus.test('_.pluck()', function() { + return _.pluck(objects, 'num'); + }); + + JSLitmus.test('_.uniq()', function() { + return _.uniq(randomized); + }); + + JSLitmus.test('_.uniq() (sorted)', function() { + return _.uniq(numbers, true); + }); + + JSLitmus.test('_.isEqual()', function() { + return _.isEqual(numbers, randomized); + }); + +})(); \ No newline at end of file diff --git a/test/test.html b/test/test.html index 9c8220344..67415345d 100644 --- a/test/test.html +++ b/test/test.html @@ -1,22 +1,27 @@ - + + Underscore Test Suite + - +

    Underscore Test Suite

    -

    -

    -
      +

      +

      +
        +
        +

        JSLitmus Speed Suite

        +

        Each iteration runs on an array of 1000 elements.

        +
        \ No newline at end of file diff --git a/test/vendor/jslitmus.js b/test/vendor/jslitmus.js new file mode 100644 index 000000000..a0e9f806f --- /dev/null +++ b/test/vendor/jslitmus.js @@ -0,0 +1,670 @@ +// JSLitmus.js +// +// History: +// 2008-10-27: Initial release +// 2008-11-09: Account for iteration loop overhead +// 2008-11-13: Added OS detection +// 2009-02-25: Create tinyURL automatically, shift-click runs tests in reverse +// +// Copyright (c) 2008-2009, Robert Kieffer +// All Rights Reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the +// Software), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +(function() { + // Private methods and state + + // Get platform info but don't go crazy trying to recognize everything + // that's out there. This is just for the major platforms and OSes. + var platform = 'unknown platform', ua = navigator.userAgent; + + // Detect OS + var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|'); + var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null; + if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null; + + // Detect browser + var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null; + + // Detect version + var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)'); + var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null; + var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform'; + + /** + * A smattering of methods that are needed to implement the JSLitmus testbed. + */ + var jsl = { + /** + * Enhanced version of escape() + */ + escape: function(s) { + s = s.replace(/,/g, '\\,'); + s = escape(s); + s = s.replace(/\+/g, '%2b'); + s = s.replace(/ /g, '+'); + return s; + }, + + /** + * Get an element by ID. + */ + $: function(id) { + return document.getElementById(id); + }, + + /** + * Null function + */ + F: function() {}, + + /** + * Set the status shown in the UI + */ + status: function(msg) { + var el = jsl.$('jsl_status'); + if (el) el.innerHTML = msg || ''; + }, + + /** + * Convert a number to an abbreviated string like, "15K" or "10M" + */ + toLabel: function(n) { + if (n == Infinity) { + return 'Infinity'; + } else if (n > 1e9) { + n = Math.round(n/1e8); + return n/10 + 'B'; + } else if (n > 1e6) { + n = Math.round(n/1e5); + return n/10 + 'M'; + } else if (n > 1e3) { + n = Math.round(n/1e2); + return n/10 + 'K'; + } + return n; + }, + + /** + * Copy properties from src to dst + */ + extend: function(dst, src) { + for (var k in src) dst[k] = src[k]; return dst; + }, + + /** + * Like Array.join(), but for the key-value pairs in an object + */ + join: function(o, delimit1, delimit2) { + if (o.join) return o.join(delimit1); // If it's an array + var pairs = []; + for (var k in o) pairs.push(k + delimit1 + o[k]); + return pairs.join(delimit2); + }, + + /** + * Array#indexOf isn't supported in IE, so we use this as a cross-browser solution + */ + indexOf: function(arr, o) { + if (arr.indexOf) return arr.indexOf(o); + for (var i = 0; i < this.length; i++) if (arr[i] === o) return i; + return -1; + } + }; + + /** + * Test manages a single test (created with + * JSLitmus.test()) + * + * @private + */ + var Test = function (name, f) { + if (!f) throw new Error('Undefined test function'); + if (!(/function[^\(]*\(([^,\)]*)/).test(f.toString())) { + throw new Error('"' + name + '" test: Test is not a valid Function object'); + } + this.loopArg = RegExp.$1; + this.name = name; + this.f = f; + }; + + jsl.extend(Test, /** @lends Test */ { + /** Calibration tests for establishing iteration loop overhead */ + CALIBRATIONS: [ + new Test('calibrating loop', function(count) {while (count--);}), + new Test('calibrating function', jsl.F) + ], + + /** + * Run calibration tests. Returns true if calibrations are not yet + * complete (in which case calling code should run the tests yet again). + * onCalibrated - Callback to invoke when calibrations have finished + */ + calibrate: function(onCalibrated) { + for (var i = 0; i < Test.CALIBRATIONS.length; i++) { + var cal = Test.CALIBRATIONS[i]; + if (cal.running) return true; + if (!cal.count) { + cal.isCalibration = true; + cal.onStop = onCalibrated; + //cal.MIN_TIME = .1; // Do calibrations quickly + cal.run(2e4); + return true; + } + } + return false; + } + }); + + jsl.extend(Test.prototype, {/** @lends Test.prototype */ + /** Initial number of iterations */ + INIT_COUNT: 10, + /** Max iterations allowed (i.e. used to detect bad looping functions) */ + MAX_COUNT: 1e9, + /** Minimum time a test should take to get valid results (secs) */ + MIN_TIME: .5, + + /** Callback invoked when test state changes */ + onChange: jsl.F, + + /** Callback invoked when test is finished */ + onStop: jsl.F, + + /** + * Reset test state + */ + reset: function() { + delete this.count; + delete this.time; + delete this.running; + delete this.error; + }, + + /** + * Run the test (in a timeout). We use a timeout to make sure the browser + * has a chance to finish rendering any UI changes we've made, like + * updating the status message. + */ + run: function(count) { + count = count || this.INIT_COUNT; + jsl.status(this.name + ' x ' + count); + this.running = true; + var me = this; + setTimeout(function() {me._run(count);}, 200); + }, + + /** + * The nuts and bolts code that actually runs a test + */ + _run: function(count) { + var me = this; + + // Make sure calibration tests have run + if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return; + this.error = null; + + try { + var start, f = this.f, now, i = count; + + // Start the timer + start = new Date(); + + // Now for the money shot. If this is a looping function ... + if (this.loopArg) { + // ... let it do the iteration itself + f(count); + } else { + // ... otherwise do the iteration for it + while (i--) f(); + } + + // Get time test took (in secs) + this.time = Math.max(1,new Date() - start)/1000; + + // Store iteration count and per-operation time taken + this.count = count; + this.period = this.time/count; + + // Do we need to do another run? + this.running = this.time <= this.MIN_TIME; + + // ... if so, compute how many times we should iterate + if (this.running) { + // Bump the count to the nearest power of 2 + var x = this.MIN_TIME/this.time; + var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2)))); + count *= pow; + if (count > this.MAX_COUNT) { + throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.'); + } + } + } catch (e) { + // Exceptions are caught and displayed in the test UI + this.reset(); + this.error = e; + } + + // Figure out what to do next + if (this.running) { + me.run(count); + } else { + jsl.status(''); + me.onStop(me); + } + + // Finish up + this.onChange(this); + }, + + /** + * Get the number of operations per second for this test. + * + * @param normalize if true, iteration loop overhead taken into account + */ + getHz: function(/**Boolean*/ normalize) { + var p = this.period; + + // Adjust period based on the calibration test time + if (normalize && !this.isCalibration) { + var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1]; + + // If the period is within 20% of the calibration time, then zero the + // it out + p = p < cal.period*1.2 ? 0 : p - cal.period; + } + + return Math.round(1/p); + }, + + /** + * Get a friendly string describing the test + */ + toString: function() { + return this.name + ' - ' + this.time/this.count + ' secs'; + } + }); + + // CSS we need for the UI + var STYLESHEET = ''; + + // HTML markup for the UI + var MARKUP = '
        \ + \ + \ +
        \ +
        \ + Normalize results \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
        ' + platform + '
        TestOps/sec
        \ +
        \ + \ + Powered by JSLitmus \ +
        '; + + /** + * The public API for creating and running tests + */ + window.JSLitmus = { + /** The list of all tests that have been registered with JSLitmus.test */ + _tests: [], + /** The queue of tests that need to be run */ + _queue: [], + + /** + * The parsed query parameters the current page URL. This is provided as a + * convenience for test functions - it's not used by JSLitmus proper + */ + params: {}, + + /** + * Initialize + */ + _init: function() { + // Parse query params into JSLitmus.params[] hash + var match = (location + '').match(/([^?#]*)(#.*)?$/); + if (match) { + var pairs = match[1].split('&'); + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split('='); + if (pair.length > 1) { + var key = pair.shift(); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + this.params[key] = value; + } + } + } + + // Write out the stylesheet. We have to do this here because IE + // doesn't honor sheets written after the document has loaded. + document.write(STYLESHEET); + + // Setup the rest of the UI once the document is loaded + if (window.addEventListener) { + window.addEventListener('load', this._setup, false); + } else if (document.addEventListener) { + document.addEventListener('load', this._setup, false); + } else if (window.attachEvent) { + window.attachEvent('onload', this._setup); + } + + return this; + }, + + /** + * Set up the UI + */ + _setup: function() { + var el = jsl.$('jslitmus_container'); + if (!el) document.body.appendChild(el = document.createElement('div')); + + el.innerHTML = MARKUP; + + // Render the UI for all our tests + for (var i=0; i < JSLitmus._tests.length; i++) + JSLitmus.renderTest(JSLitmus._tests[i]); + }, + + /** + * (Re)render all the test results + */ + renderAll: function() { + for (var i = 0; i < JSLitmus._tests.length; i++) + JSLitmus.renderTest(JSLitmus._tests[i]); + JSLitmus.renderChart(); + }, + + /** + * (Re)render the chart graphics + */ + renderChart: function() { + var url = JSLitmus.chartUrl(); + jsl.$('chart_link').href = url; + jsl.$('chart_image').src = url; + jsl.$('chart').style.display = ''; + + // Update the tiny URL + jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url); + }, + + /** + * (Re)render the results for a specific test + */ + renderTest: function(test) { + // Make a new row if needed + if (!test._row) { + var trow = jsl.$('test_row_template'); + if (!trow) return; + + test._row = trow.cloneNode(true); + test._row.style.display = ''; + test._row.id = ''; + test._row.onclick = function() {JSLitmus._queueTest(test);}; + test._row.title = 'Run ' + test.name + ' test'; + trow.parentNode.appendChild(test._row); + test._row.cells[0].innerHTML = test.name; + } + + var cell = test._row.cells[1]; + var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping']; + + if (test.error) { + cns.push('test_error'); + cell.innerHTML = + '
        ' + test.error + '
        ' + + '
        • ' + + jsl.join(test.error, ': ', '
        • ') + + '
        '; + } else { + if (test.running) { + cns.push('test_running'); + cell.innerHTML = 'running'; + } else if (jsl.indexOf(JSLitmus._queue, test) >= 0) { + cns.push('test_pending'); + cell.innerHTML = 'pending'; + } else if (test.count) { + cns.push('test_done'); + var hz = test.getHz(jsl.$('test_normalize').checked); + cell.innerHTML = hz != Infinity ? hz : '∞'; + } else { + cell.innerHTML = 'ready'; + } + } + cell.className = cns.join(' '); + }, + + /** + * Create a new test + */ + test: function(name, f) { + // Create the Test object + var test = new Test(name, f); + JSLitmus._tests.push(test); + + // Re-render if the test state changes + test.onChange = JSLitmus.renderTest; + + // Run the next test if this one finished + test.onStop = function(test) { + if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test); + JSLitmus.currentTest = null; + JSLitmus._nextTest(); + }; + + // Render the new test + this.renderTest(test); + }, + + /** + * Add all tests to the run queue + */ + runAll: function(e) { + e = e || window.event; + var reverse = e && e.shiftKey, len = JSLitmus._tests.length; + for (var i = 0; i < len; i++) { + JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]); + } + }, + + /** + * Remove all tests from the run queue. The current test has to finish on + * it's own though + */ + stop: function() { + while (JSLitmus._queue.length) { + var test = JSLitmus._queue.shift(); + JSLitmus.renderTest(test); + } + }, + + /** + * Run the next test in the run queue + */ + _nextTest: function() { + if (!JSLitmus.currentTest) { + var test = JSLitmus._queue.shift(); + if (test) { + jsl.$('stop_button').disabled = false; + JSLitmus.currentTest = test; + test.run(); + JSLitmus.renderTest(test); + if (JSLitmus.onTestStart) JSLitmus.onTestStart(test); + } else { + jsl.$('stop_button').disabled = true; + JSLitmus.renderChart(); + } + } + }, + + /** + * Add a test to the run queue + */ + _queueTest: function(test) { + if (jsl.indexOf(JSLitmus._queue, test) >= 0) return; + JSLitmus._queue.push(test); + JSLitmus.renderTest(test); + JSLitmus._nextTest(); + }, + + /** + * Generate a Google Chart URL that shows the data for all tests + */ + chartUrl: function() { + var n = JSLitmus._tests.length, markers = [], data = []; + var d, min = 0, max = -1e10; + var normalize = jsl.$('test_normalize').checked; + + // Gather test data + for (var i=0; i < JSLitmus._tests.length; i++) { + var test = JSLitmus._tests[i]; + if (test.count) { + var hz = test.getHz(normalize); + var v = hz != Infinity ? hz : 0; + data.push(v); + markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' + + markers.length + ',10'); + max = Math.max(v, max); + } + } + if (markers.length <= 0) return null; + + // Build chart title + var title = document.getElementsByTagName('title'); + title = (title && title.length) ? title[0].innerHTML : null; + var chart_title = []; + if (title) chart_title.push(title); + chart_title.push('Ops/sec (' + platform + ')'); + + // Build labels + var labels = [jsl.toLabel(min), jsl.toLabel(max)]; + + var w = 250, bw = 15; + var bs = 5; + var h = markers.length*(bw + bs) + 30 + chart_title.length*20; + + var params = { + chtt: escape(chart_title.join('|')), + chts: '000000,10', + cht: 'bhg', // chart type + chd: 't:' + data.join(','), // data set + chds: min + ',' + max, // max/min of data + chxt: 'x', // label axes + chxl: '0:|' + labels.join('|'), // labels + chsp: '0,1', + chm: markers.join('|'), // test names + chbh: [bw, 0, bs].join(','), // bar widths + // chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient + chs: w + 'x' + h + }; + return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&'); + } + }; + + JSLitmus._init(); +})(); \ No newline at end of file diff --git a/test/vendor/qunit.css b/test/vendor/qunit.css index 660cd3c1a..c12b16d8e 100644 --- a/test/vendor/qunit.css +++ b/test/vendor/qunit.css @@ -1,11 +1,11 @@ -h1#qunit-header { padding: 15px; font-size: large; background-color: #06b; color: white; font-family: 'trebuchet ms', verdana, arial; margin: 0; } +h1#qunit-header, h1.qunit-header { padding: 15px; font-size: large; background-color: #06b; color: white; font-family: 'trebuchet ms', verdana, arial; margin: 0; } h1#qunit-header a { color: white; } h2#qunit-banner { height: 2em; border-bottom: 1px solid white; background-color: #eee; margin: 0; font-family: 'trebuchet ms', verdana, arial; } h2#qunit-banner.pass { background-color: green; } h2#qunit-banner.fail { background-color: red; } -h2#qunit-userAgent { padding: 10px; background-color: #eee; color: black; margin: 0; font-size: small; font-weight: normal; font-family: 'trebuchet ms', verdana, arial; font-size: 10pt; } +h2#qunit-userAgent, h2.qunit-userAgent { padding: 10px; background-color: #eee; color: black; margin: 0; font-size: small; font-weight: normal; font-family: 'trebuchet ms', verdana, arial; font-size: 10pt; } div#qunit-testrunner-toolbar { background: #eee; border-top: 1px solid black; padding: 10px; font-family: 'trebuchet ms', verdana, arial; margin: 0; font-size: 10pt; } From 4c41af41e665cd41396783635a905d1743ce9596 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 26 Oct 2009 16:07:09 -0400 Subject: [PATCH 007/533] first batch of documentation --- index.html | 308 ++++++++++++++++++++++++++++++++++++++++++++++ test/functions.js | 19 +++ test/test.html | 2 +- underscore.js | 23 ++++ 4 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 000000000..aae21514d --- /dev/null +++ b/index.html @@ -0,0 +1,308 @@ + + + + Underscore.js + + + + +
        + +

        Underscore.js

        + +

        + Underscore is a + utility-belt library for Javascript that provides a lot of the + functional programming support that you would expect in + Prototype.js + (or Ruby), + but without extending any of the built-in Javascript objects. It's the + tie to go along with jQuery's tux. +

        + +

        + Underscore provides 43-odd functions that support both the usual + functional suspects: map, select, invoke — + as well as more specialized helpers: function binding, javascript + templating, deep equality testing, and so on. It delegates to the built-in + functions, if present, so + Javascript 1.6 + compliant browsers will use the + native implementations of forEach, map, filter, + every and some. +

        + +

        + Underscore includes a complete Test & Benchmark Suite + for your perusal. +

        + +

        Table of Contents

        + +

        + Collections +
        + each, map, + inject, detect, select, reject, all, + any, include, invoke, pluck, max, + min, sortBy, sortedIndex, toArray, + size +

        + +

        + Arrays +
        + first, last, + compact, flatten, without, uniq, + intersect, zip, indexOf +

        + +

        + Objects +
        + keys, values, + extend, clone, isEqual, isElement, + isArray, isFunction, isUndefined, toString + +

        + +

        + Functions +
        + bind, bindAll, delay, + defer, wrap +

        + +

        + Utility +
        + uniqueId, template +

        + +
        +

        Collections

        + +

        + each_.each(list, iterator, [context]) +
        + Iterates over a list of elements, yielding each in turn to an iterator + function. The iterator is bound to the context object, if one is + passed. If list is a Javascript object, a pair with key + and value properties will be yielded. Delegates to the native + forEach method if it exists. +

        +
        +_.each([1, 2, 3], function(num){ alert(num); });
        +=> alerts each number in turn...
        + +

        + map_.map(list, iterator, [context]) +
        + Produces a new array of values by mapping each value in list + through a transformation function (iterator). If the native + map method exists, it will be used instead. +

        +
        +_.map([1, 2, 3], function(num){ return num * 3 });
        +=> [3, 6, 9]
        + +

        + inject_.inject(list, memo, iterator, [context]) +
        + Also known as reduce and foldl, inject reduces a + list of values into a single value. Memo is the initial state + of the reduction, and each successive step of it should be returned by + iterator. +

        +
        +var sum = _.inject([1, 2, 3], 0, function(memo, num){ return memo + num });
        +=> 6
        +
        + +

        + detect_.detect(list, iterator, [context]) +
        + Looks through each value in the list, returning the first one that + passes a truth test (iterator). The function returns as + soon as it finds the true element, and doesn't continue to traverse + the list. +

        +
        +var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
        +=> 2
        +
        + +

        + select_.select(list, iterator, [context]) +
        + Looks through each value in the list, returning an array of all + the values that pass a truth test (iterator). Delegates to the + native filter method, if it exists. +

        +
        +var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
        +=> [2, 4, 6]
        +
        + +

        + reject_.reject(list, iterator, [context]) +
        + Returns the values in list without the elements that the truth + test (iterator) passes. The opposite of select. +

        +
        +var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
        +=> [1, 3, 5]
        +
        + +

        + all_.(list, [iterator], [context]) +
        + Returns true if all of the values in the list pass the iterator + truth test. If an iterator is not provided, the truthy value of + the element will be used instead. Delegates to the native method every, if + present. +

        +
        +_.all([true, 1, null, 'yes']);
        +=> false
        +
        + +

        + any_.(list, iterator, [context]) +
        + Returns true if any of the values in the list pass the + iterator truth test. Short-circuits and stops traversing the list + if a true element is found. Delegates to the native method some, + if present. +

        +
        +_.any([null, 0, 'yes', false]);
        +=> true
        +
        + +

        + include_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + invoke_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + pluck_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + max_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + min_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + sortBy_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + sortedIndex_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + toArray_.(list, iterator, [context]) +
        + +

        +
        +
        + +

        + size_.(list, iterator, [context]) +
        + +

        +
        +
        + +
        + +
        + + + diff --git a/test/functions.js b/test/functions.js index 47de34699..039f7a746 100644 --- a/test/functions.js +++ b/test/functions.js @@ -29,4 +29,23 @@ $(document).ready(function() { equals(curly.sayHi(), 'hi: moe', 'bound function is still bound to original 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'); + }); + }); diff --git a/test/test.html b/test/test.html index 67415345d..aa2ca7a12 100644 --- a/test/test.html +++ b/test/test.html @@ -9,8 +9,8 @@ - + diff --git a/underscore.js b/underscore.js index 8f4ba6ee4..7c5ee29cb 100644 --- a/underscore.js +++ b/underscore.js @@ -283,6 +283,29 @@ window._ = { }); }, + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + delay : function(func, wait) { + var args = _.toArray(arguments).slice(2); + return window.setTimeout(function(){ return func.apply(func, args); }, wait); + }, + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + defer : function(func) { + return _.delay.apply(_, [func, 1].concat(_.toArray(arguments).slice(1))); + }, + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + wrap : function(func, wrapper) { + return function() { + var args = [func].concat(_.toArray(arguments)); + return wrapper.apply(wrapper, args); + }; + }, + /* ---------------- The following methods apply to objects ---------------- */ // Retrieve the names of an object's properties. From 25d3177bd733d26f307cbb80cbb80971a8c4bd93 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 26 Oct 2009 21:11:19 -0400 Subject: [PATCH 008/533] further along with the HTML documentation --- index.html | 80 +++++++++++++++++++++++++++++++-------------- test/collections.js | 2 +- underscore.js | 5 +-- 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/index.html b/index.html index aae21514d..fef1bc7eb 100644 --- a/index.html +++ b/index.html @@ -71,12 +71,12 @@ Underscore provides 43-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript - templating, deep equality testing, and so on. It delegates to the built-in + templating, deep equality testing, and so on. It delegates to built-in functions, if present, so Javascript 1.6 compliant browsers will use the native implementations of forEach, map, filter, - every and some. + every, some and indexOf.

        @@ -203,7 +203,7 @@ var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

        - all_.(list, [iterator], [context]) + all_.all(list, [iterator], [context])
        Returns true if all of the values in the list pass the iterator truth test. If an iterator is not provided, the truthy value of @@ -216,7 +216,7 @@ _.all([true, 1, null, 'yes']);

        - any_.(list, iterator, [context]) + any_.any(list, iterator, [context])
        Returns true if any of the values in the list pass the iterator truth test. Short-circuits and stops traversing the list @@ -228,60 +228,90 @@ _.any([null, 0, 'yes', false]); => true -

        - include_.(list, iterator, [context]) +

        + include_.include(list, value)
        - + Returns true if the value is present in the list, using + == to test equality. Uses indexOf internally, if list + is an Array.

        +_.include([1, 2, 3], 3);
        +=> true
         
        -

        - invoke_.(list, iterator, [context]) +

        + invoke_.invoke(list, methodName)
        - + Calls the method named by methodName on each value in the list. + Any extra arguments passed to invoke will be forwarded on to the + method invocation.

        +_.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
        +=> [[1, 5, 7], [1, 2, 3]]
         
        -

        - pluck_.(list, iterator, [context]) +

        + pluck_.pluck(list, propertyName)
        - + An optimized version of what is perhaps the most common use-case for + map: returning a list of property values.

        +var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
        +_.pluck(stooges, 'name');
        +=> ["moe", "larry", "curly"]
         
        -

        - max_.(list, iterator, [context]) +

        + max_.max(list, [iterator], [context])
        - + Returns the maximum value in list. If iterator is passed, + it will be used on each value to generate the criterion by which the + value is ranked.

        +var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
        +_.max(stooges, function(stooge){ return stooge.age; });
        +=> {name : 'curly', age : 60};
         
        -

        - min_.(list, iterator, [context]) +

        + min_.min(list, [iterator], [context])
        - + Returns the minimum value in list. If iterator is passed, + it will be used on each value to generate the criterion by which the + value is ranked.

        +var numbers = [10, 5, 100, 2, 1000];
        +_.min(numbers);
        +=> 2
         
        -

        - sortBy_.(list, iterator, [context]) +

        + sortBy_.sortBy(list, iterator, [context])
        - + Returns a sorted list, ranked by the results of running each + value through iterator.

        +_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
        +=> [5, 4, 6, 3, 1, 2]
         
        -

        - sortedIndex_.(list, iterator, [context]) +

        + sortedIndex_.sortedIndex(list, value, [iterator])
        - + Uses a binary search to determine the index at which the value + should be inserted into the list in order to maintain the list's + sorted order. If an iterator is passed, it will be used to compute + the sort ranking of each value.

        +_.sortedIndex([10, 20, 30, 40, 50], 35);
        +=> 3
         

        diff --git a/test/collections.js b/test/collections.js index 1ba3512b7..bed210ac2 100644 --- a/test/collections.js +++ b/test/collections.js @@ -100,7 +100,7 @@ $(document).ready(function() { test('collections: sortedIndex', function() { var numbers = [10, 20, 30, 40, 50], num = 35; - var index = _.sortedIndex(numbers, function(a, b) { return a < b ? -1 : a > b ? 1 : 0; }, num); + var index = _.sortedIndex(numbers, num); equals(index, 3, '35 should be inserted at index 3'); }); diff --git a/underscore.js b/underscore.js index 7c5ee29cb..2f7a58d30 100644 --- a/underscore.js +++ b/underscore.js @@ -171,11 +171,12 @@ window._ = { // Use a comparator function to figure out at what index an object should // be inserted so as to maintain order. Uses binary search. - sortedIndex : function(array, comparator, obj) { + sortedIndex : function(array, obj, iterator) { + iterator = iterator || function(val) { return val; }; var low = 0, high = array.length; while (low < high) { var mid = (low + high) >> 1; - comparator(array[mid], obj) < 0 ? low = mid + 1 : high = mid; + iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; } return low; }, From 9a881c70cbcd95b26b6af9dac075384fa236f2c7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 26 Oct 2009 23:09:56 -0400 Subject: [PATCH 009/533] more and more and more docs ... almost there --- index.html | 337 ++++++++++++++++++++++++++++++++++++++++++++++--- test/test.html | 2 +- underscore.js | 16 ++- 3 files changed, 329 insertions(+), 26 deletions(-) diff --git a/index.html b/index.html index fef1bc7eb..4fa43925c 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,7 @@ width: 550px; } #documentation p { - margin-bottom: 8px; + margin-bottom: 4px; } a, a:visited { padding: 0 2px; @@ -34,7 +34,7 @@ h1, h2, h3, h4, h5, h6 { margin-top: 35px; } - code, pre { + code, pre, tt { font-family: Monaco, Consolas, "Lucida Console", monospace; font-size: 12px; line-height: 18px; @@ -45,7 +45,7 @@ } pre { font-size: 12px; - padding-left: 12px; + padding: 2px 0 2px 12px; border-left: 6px solid #aaaa99; margin: 0px 0 35px; } @@ -104,6 +104,13 @@ intersect, zip, indexOf

        +

        + Functions +
        + bind, bindAll, delay, + defer, wrap +

        +

        Objects
        @@ -113,13 +120,6 @@

        -

        - Functions -
        - bind, bindAll, delay, - defer, wrap -

        -

        Utility
        @@ -127,7 +127,8 @@

        -

        Collections

        + +

        Collection Functions (Arrays or Objects)

        each_.each(list, iterator, [context]) @@ -135,8 +136,9 @@ Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is passed. If list is a Javascript object, a pair with key - and value properties will be yielded. Delegates to the native - forEach method if it exists. + and value properties will be yielded. If the list has an each + method of its own defined, it will be used. Delegates to the native + forEach function if it exists.

         _.each([1, 2, 3], function(num){ alert(num); });
        @@ -216,7 +218,7 @@ _.all([true, 1, null, 'yes']);
         

        - any_.any(list, iterator, [context]) + any_.any(list, [iterator], [context])
        Returns true if any of the values in the list pass the iterator truth test. Short-circuits and stops traversing the list @@ -241,7 +243,7 @@ _.include([1, 2, 3], 3);

        - invoke_.invoke(list, methodName) + invoke_.invoke(list, methodName, [*arguments])
        Calls the method named by methodName on each value in the list. Any extra arguments passed to invoke will be forwarded on to the @@ -314,21 +316,318 @@ _.sortedIndex([10, 20, 30, 40, 50], 35); => 3 -

        - toArray_.(list, iterator, [context]) +

        + toArray_.toArray(list)
        + Converts the list (anything that can be iterated over), into a + real Array. Useful for transmuting the arguments object. +

        +
        +(function(){ return _.toArray(arguments).slice(0); })(1, 2, 3);
        +=> [1, 2, 3]
        +
        +

        + size_.size(list) +
        + Return the number of values in the list. +

        +
        +_.size({one : 1, two : 2, three : 3});
        +=> 3
        +
        + +

        Array Functions

        + +

        + first_.first(array) +
        + Convenience to return the first element of an array (identical to array[0]). +

        +
        +_.first([3, 2, 1]);
        +=> 3
        +
        + +

        + last_.last(array) +
        + Returns the last element of an array. +

        +
        +_.last([3, 2, 1]);
        +=> 1
        +
        + +

        + compact_.compact(array) +
        + Returns a copy of the array with all falsy values removed. + In Javascript, false, null, 0, "", + undefined and NaN are all falsy. +

        +
        +_.compact([0, 1, false, 2, '', 3]);
        +=> [1, 2, 3]
        +
        + +

        + flatten_.flatten(array) +
        + Flattens a nested array (the nesting can be to any depth). +

        +
        +_.flatten([1, [2], [3, [[[4]]]]]);
        +=> [1, 2, 3, 4];
        +
        + +

        + without_.without(array, [*values]) +
        + Returns a copy of the array with all instances of the values + removed. == is used for the equality test. +

        +
        +_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
        +=> [2, 3, 4]
        +
        + +

        + uniq_.uniq(array, [isSorted]) +
        + Produces a duplicate-free version of the array, using == to test + object equality. If you know in advance that the array is sorted, + passing true for isSorted will run a much faster algorithm. +

        +
        +_.uniq([1, 2, 1, 3, 1, 4]);
        +=> [1, 2, 3, 4]
        +
        + +

        + intersect_.intersect(*arrays) +
        + Computes the list of values that are the intersection of all the arrays. + Each value in the result is present in each of the arrays. +

        +
        +_.intersect([1, 2, 3], [101, 2, 1, 10], [2, 1]);
        +=> [1, 2]
        +
        + +

        + zip_.zip(*arrays) +
        + Merges together the values of each of the arrays with the + values at the corresponding position. Useful when you have separate + data sources that are coordinated through matching array indexes. +

        +
        +_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
        +=> [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
        +
        + +

        + indexOf_.indexOf(array, value) +
        + Returns the index at which value can be found in the array, + or -1 if value is not present in the array. Uses the native + indexOf function unless it's missing. +

        +
        +_.indexOf([1, 2, 3], 2);
        +=> 1
        +
        + +

        Function (uh, ahem) Functions

        + +

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

        +
        +var func = function(greeting){ return greeting + ': ' + this.name };
        +func = _.bind(func, {name : 'moe'}, 'hi');
        +func();
        +=> 'hi: moe'
        +
        + +

        + bindAll_.bindAll(*methodNames, context) +
        + Binds a number of methods on the context 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. +

        +
        +var buttonView = {
        +  label   : 'underscore', 
        +  onClick : function(){ alert('clicked: ' + this.label); },
        +  onHover : function(){ console.log('hovering: ' + this.label); }
        +};
        +_.bindAll('onClick', 'onHover', buttonView);
        +jQuery('#underscore_button').bind('click', buttonView.onClick);
        +=> When the button is clicked, this.label will have the correct value...
        +
        + +

        + delay_.delay(function, wait, [*arguments]) +
        + Much like setTimeout, invokes function after wait + milliseconds. If you pass the optional arguments, they will be + forwarded on to the function when it is invoked. +

        +
        +var log = _.bind(console.log, console);
        +_.delay(log, 1000, 'logged later');
        +=> 'logged later' // Appears after one second.
        +
        + +

        + defer_.defer(function) +
        + Defers invoking the function until the current call stack has cleared, + similar to using setTimeout with a delay of 0. Useful for performing + expensive computations or HTML rendering in chunks without blocking the UI thread + from updating. +

        +
        +_.defer(function(){ alert('deferred'); });
        +// Returns from the function before the alert runs.
        +
        + +

        + wrap_.wrap(function, wrapper) +
        + Wraps the first function inside of the wrapper function, + passing it as the first argument. This allows the wrapper to + execute code before and after the function runs, adjust the arguments, + and execute it conditionally. +

        +
        +var hello = function(name) { return "hello: " + name; };
        +hello = _.wrap(hello, function(func) {
        +  return "before, " + func("moe") + ", after";
        +});
        +hello();
        +=> before, hello: moe, after
        +
        + +

        Object Functions

        + +

        + keys_.keys(object) +
        + Retrieve all the names of the object's properties. +

        +
        +_.keys({one : 1, two : 2, three : 3});
        +=> ["one", "two", "three"]
        +
        + +

        + values_.values(object) +
        + Return all of the values of the object's properties. +

        +
        +_.values({one : 1, two : 2, three : 3});
        +=> [1, 2, 3]
        +
        + +

        + extend_.extend(destination, source) +
        + Copy all of the properties in the source object over to the + destination object. +

        +
        +_.extend({name : 'moe'}, {age : 50});
        +=> {name : 'moe', age : 50}
        +
        + +

        + clone_.clone(object) +
        + Create a shallow-copied clone of the object. Any nested objects + or arrays will be copied by reference, not duplicated. +

        +
        +_.clone({name : 'moe'});
        +=> {name : 'moe'};
        +
        + +

        + isEqual_.isEqual(object, other) +
        + Performs an optimized deep comparison between the two objects, to determine + if they should be considered equal. +

        +
        +var moe   = {name : 'moe', luckyNumbers : [13, 27, 34]};
        +var clone = {name : 'moe', luckyNumbers : [13, 27, 34]};
        +moe == clone;
        +=> false
        +_.isEqual(moe, clone);
        +=> true
        +
        + +

        + isElement_.() +

         

        - size_.(list, iterator, [context]) + isArray_.()
        -

         
        + +

        + isFunction_.() +
        +

        +
        +
        + +

        + isUndefined_.() +
        +

        +
        +
        + +

        + toString_.() +
        +

        +
        +
        + +

        Utility Functions

        + +

        + uniqueId_.() +
        +

        +
        +
        + +

        + template_.() +
        +

        +
        +
        diff --git a/test/test.html b/test/test.html index aa2ca7a12..67415345d 100644 --- a/test/test.html +++ b/test/test.html @@ -9,8 +9,8 @@ - + diff --git a/underscore.js b/underscore.js index 2f7a58d30..7a42cbb12 100644 --- a/underscore.js +++ b/underscore.js @@ -227,17 +227,21 @@ window._ = { // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. - uniq : function(array, sorted) { + uniq : function(array, isSorted) { return _.inject(array, [], function(memo, el, i) { - if (0 == i || (sorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); + if (0 == i || (isSorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); return memo; }); }, - // Produce an array that contains every item shared between two given arrays. - intersect : function(array1, array2) { - return _.select(_.uniq(array1), function(item1) { - return _.detect(array2, function(item2) { return item1 === item2; }); + // Produce an array that contains every item shared between all the + // passed-in arrays. + intersect : function(array) { + var rest = _.toArray(arguments).slice(1); + return _.select(_.uniq(array), function(item1) { + return _.all(rest, function(other) { + return _.detect(other, function(item2){ return item1 === item2; }); + }); }); }, From 5e3f783a231ced7d41188499614678b59015e242 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 12:29:24 -0400 Subject: [PATCH 010/533] docs done -- going back to code comments --- index.html | 137 ++++++++++++++++++++++++++++-------------------- test/arrays.js | 2 +- test/objects.js | 4 -- underscore.js | 26 +++++---- 4 files changed, 94 insertions(+), 75 deletions(-) diff --git a/index.html b/index.html index 4fa43925c..535df71bb 100644 --- a/index.html +++ b/index.html @@ -116,7 +116,7 @@
        keys, values, extend, clone, isEqual, isElement, - isArray, isFunction, isUndefined, toString + isArray, isFunction, isUndefined

        @@ -142,7 +142,7 @@

         _.each([1, 2, 3], function(num){ alert(num); });
        -=> alerts each number in turn...
        +=> alerts each number in turn...

        map_.map(list, iterator, [context]) @@ -153,7 +153,7 @@ _.each([1, 2, 3], function(num){ alert(num); });

         _.map([1, 2, 3], function(num){ return num * 3 });
        -=> [3, 6, 9]
        +=> [3, 6, 9]

        inject_.inject(list, memo, iterator, [context]) @@ -165,7 +165,7 @@ _.map([1, 2, 3], function(num){ return num * 3 });

         var sum = _.inject([1, 2, 3], 0, function(memo, num){ return memo + num });
        -=> 6
        +=> 6
         

        @@ -178,7 +178,7 @@ var sum = _.inject([1, 2, 3], 0, function(memo, num){ return memo + num });

         var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
        -=> 2
        +=> 2
         

        @@ -190,7 +190,7 @@ var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

         var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
        -=> [2, 4, 6]
        +=> [2, 4, 6]
         

        @@ -201,7 +201,7 @@ var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

         var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
        -=> [1, 3, 5]
        +=> [1, 3, 5]
         

        @@ -214,7 +214,7 @@ var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

         _.all([true, 1, null, 'yes']);
        -=> false
        +=> false
         

        @@ -227,7 +227,7 @@ _.all([true, 1, null, 'yes']);

         _.any([null, 0, 'yes', false]);
        -=> true
        +=> true
         

        @@ -239,7 +239,7 @@ _.any([null, 0, 'yes', false]);

         _.include([1, 2, 3], 3);
        -=> true
        +=> true
         

        @@ -251,7 +251,7 @@ _.include([1, 2, 3], 3);

         _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
        -=> [[1, 5, 7], [1, 2, 3]]
        +=> [[1, 5, 7], [1, 2, 3]]
         

        @@ -263,7 +263,7 @@ _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');

         var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
         _.pluck(stooges, 'name');
        -=> ["moe", "larry", "curly"]
        +=> ["moe", "larry", "curly"]
         

        @@ -276,7 +276,7 @@ _.pluck(stooges, 'name');

         var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
         _.max(stooges, function(stooge){ return stooge.age; });
        -=> {name : 'curly', age : 60};
        +=> {name : 'curly', age : 60};
         

        @@ -289,7 +289,7 @@ _.max(stooges, function(stooge){ return stooge.age; });

         var numbers = [10, 5, 100, 2, 1000];
         _.min(numbers);
        -=> 2
        +=> 2
         

        @@ -300,7 +300,7 @@ _.min(numbers);

         _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
        -=> [5, 4, 6, 3, 1, 2]
        +=> [5, 4, 6, 3, 1, 2]
         

        @@ -313,7 +313,7 @@ _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });

         _.sortedIndex([10, 20, 30, 40, 50], 35);
        -=> 3
        +=> 3
         

        @@ -324,7 +324,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

         (function(){ return _.toArray(arguments).slice(0); })(1, 2, 3);
        -=> [1, 2, 3]
        +=> [1, 2, 3]
         

        @@ -334,7 +334,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

         _.size({one : 1, two : 2, three : 3});
        -=> 3
        +=> 3
         

        Array Functions

        @@ -346,7 +346,7 @@ _.size({one : 1, two : 2, three : 3});

         _.first([3, 2, 1]);
        -=> 3
        +=> 3
         

        @@ -356,7 +356,7 @@ _.first([3, 2, 1]);

         _.last([3, 2, 1]);
        -=> 1
        +=> 1
         

        @@ -368,7 +368,7 @@ _.last([3, 2, 1]);

         _.compact([0, 1, false, 2, '', 3]);
        -=> [1, 2, 3]
        +=> [1, 2, 3]
         

        @@ -378,7 +378,7 @@ _.compact([0, 1, false, 2, '', 3]);

         _.flatten([1, [2], [3, [[[4]]]]]);
        -=> [1, 2, 3, 4];
        +=> [1, 2, 3, 4];
         

        @@ -389,7 +389,7 @@ _.flatten([1, [2], [3, [[[4]]]]]);

         _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
        -=> [2, 3, 4]
        +=> [2, 3, 4]
         

        @@ -401,7 +401,7 @@ _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);

         _.uniq([1, 2, 1, 3, 1, 4]);
        -=> [1, 2, 3, 4]
        +=> [1, 2, 3, 4]
         

        @@ -412,7 +412,7 @@ _.uniq([1, 2, 1, 3, 1, 4]);

         _.intersect([1, 2, 3], [101, 2, 1, 10], [2, 1]);
        -=> [1, 2]
        +=> [1, 2]
         

        @@ -424,7 +424,7 @@ _.intersect([1, 2, 3], [101, 2, 1, 10], [2, 1]);

         _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
        -=> [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
        +=> [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
         

        @@ -436,7 +436,7 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

         _.indexOf([1, 2, 3], 2);
        -=> 1
        +=> 1
         

        Function (uh, ahem) Functions

        @@ -453,7 +453,7 @@ _.indexOf([1, 2, 3], 2); var func = function(greeting){ return greeting + ': ' + this.name }; func = _.bind(func, {name : 'moe'}, 'hi'); func(); -=> 'hi: moe' +=> 'hi: moe'

        @@ -473,7 +473,7 @@ var buttonView = { }; _.bindAll('onClick', 'onHover', buttonView); jQuery('#underscore_button').bind('click', buttonView.onClick); -=> When the button is clicked, this.label will have the correct value... +=> When the button is clicked, this.label will have the correct value...

        @@ -486,7 +486,7 @@ jQuery('#underscore_button').bind('click', buttonView.onClick);

         var log = _.bind(console.log, console);
         _.delay(log, 1000, 'logged later');
        -=> 'logged later' // Appears after one second.
        +=> 'logged later' // Appears after one second.
         

        @@ -516,7 +516,7 @@ hello = _.wrap(hello, function(func) { return "before, " + func("moe") + ", after"; }); hello(); -=> before, hello: moe, after +=> before, hello: moe, after

        Object Functions

        @@ -528,7 +528,7 @@ hello();

         _.keys({one : 1, two : 2, three : 3});
        -=> ["one", "two", "three"]
        +=> ["one", "two", "three"]
         

        @@ -538,7 +538,7 @@ _.keys({one : 1, two : 2, three : 3});

         _.values({one : 1, two : 2, three : 3});
        -=> [1, 2, 3]
        +=> [1, 2, 3]
         

        @@ -549,7 +549,7 @@ _.values({one : 1, two : 2, three : 3});

         _.extend({name : 'moe'}, {age : 50});
        -=> {name : 'moe', age : 50}
        +=> {name : 'moe', age : 50}
         

        @@ -560,7 +560,7 @@ _.extend({name : 'moe'}, {age : 50});

         _.clone({name : 'moe'});
        -=> {name : 'moe'};
        +=> {name : 'moe'};
         

        @@ -573,60 +573,85 @@ _.clone({name : 'moe'}); var moe = {name : 'moe', luckyNumbers : [13, 27, 34]}; var clone = {name : 'moe', luckyNumbers : [13, 27, 34]}; moe == clone; -=> false +=> false _.isEqual(moe, clone); -=> true +=> true -

        - isElement_.() +

        + isElement_.isElement(object)
        + Returns true if object is a DOM element.

        +_.isElement(jQuery('body')[0]);
        +=> true
         
        -

        - isArray_.() +

        + isArray_.isArray(object)
        + Returns true if object is an Array.

        +(function(){ return _.isArray(arguments); })();
        +=> false
        +_.isArray([1,2,3]);
        +=> true
         
        -

        - isFunction_.() +

        + isFunction_.isFunction(object)
        + Returns true if object is a Function.

        +_.isFunction(alert);
        +=> true
         
        -

        - isUndefined_.() -
        -

        -
        -
        - -

        - toString_.() +

        + isUndefined_.isUndefined(variable)
        + Returns true if variable is undefined.

        +_.isUndefined(window.missingVariable);
        +=> true
         

        Utility Functions

        -

        - uniqueId_.() +

        + uniqueId_.uniqueId([prefix])
        + Generate a globally-unique id for client-side models or DOM elements + that need one. If prefix is passed, the id will be appended to it.

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

        - template_.() +

        + template_.template(templateString)
        + Compiles Javascript templates into functions that can be evaluated + for rendering. Useful for rendering complicated bits of HTML from JSON + data sources. Template functions can both interpolate variables, using
        + <%= … %>, as well as execute arbitrary Javascript code, with + <% … %>. When you evaluate a template, pass in a + context object that has properties corresponding to the template's free + variables.

        +var compiled = _.template("hello: <%= name %>");
        +compiled({name : 'moe'});
        +=> "hello: moe"
        +
        +var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>";
        +_.template(list, {people : ['moe', 'curly', 'larry']});
        +=> "<li>moe</li><li>curly</li><li>larry</li>"
         
        diff --git a/test/arrays.js b/test/arrays.js index 7b4e45692..e59397996 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -40,7 +40,7 @@ $(document).ready(function() { test('arrays: zip', function() { var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true]; var stooges = _.zip(names, ages, leaders); - equals(_.toString(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths'); + equals(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths'); }); test("arrays: indexOf", function() { diff --git a/test/objects.js b/test/objects.js index 624d67bfe..eb73245d8 100644 --- a/test/objects.js +++ b/test/objects.js @@ -58,9 +58,5 @@ $(document).ready(function() { ok(_.isUndefined(), 'nothing is undefined'); ok(_.isUndefined(undefined), 'undefined is undefined'); }); - - test("objects: toString", function() { - equals(_.toString([1, 2, 3]), '1,2,3', 'object can be converted to printable strings'); - }); }); diff --git a/underscore.js b/underscore.js index 7a42cbb12..9f2f403e8 100644 --- a/underscore.js +++ b/underscore.js @@ -1,6 +1,11 @@ -// Javascript can be so much more pleasant when it's functional -- re-implement -// a bunch of utility methods from Prototype and Steele's Functional... +// Underscore.js +// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the terms of the MIT license. +// For all details and documentation: +// http://fdjklsafjsdalk window._ = { + + VERSION : '0.1.0', // The cornerstone, an each implementation. // Handles objects implementing forEach, _each, arrays, and raw objects. @@ -345,17 +350,15 @@ window._ = { if (a == b) return true; // One of them implements an isEqual()? if (a.isEqual) return a.isEqual(b); + // If a is not an object by this point, we can't handle it. + if (atype !== 'object') return false; // Nothing else worked, deep compare the contents. - return atype === 'object' && _._isEqualContents(a, b); - }, - - // Objects have equal contents if they have the same keys, and all the values - // are equal (as defined by _.isEqual). - _isEqualContents : function(a, b) { var aKeys = _.keys(a), bKeys = _.keys(b); + // Different object sizes? if (aKeys.length != bKeys.length) return false; + // Recursive comparison of contents. for (var key in a) if (!_.isEqual(a[key], b[key])) return false; - return true; + return true; }, // Is a given value a DOM element? @@ -378,11 +381,6 @@ window._ = { return typeof obj == 'undefined'; }, - // Convert any value into printable string form. - toString : function(obj) { - return obj == null ? '' : String(obj); - }, - /* -------------- The following methods are utility methods --------------- */ // Generate a unique integer id (unique within the entire client session). From ef10906b5f82f873960786578565d42d14163771 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 13:26:36 -0400 Subject: [PATCH 011/533] getting ready for 0.1 --- LICENSE | 22 +++++++++++ README | 10 +++++ index.html | 102 +++++++++++++++++++++++++------------------------ test/speed.js | 16 ++++++++ test/test.html | 10 ++++- underscore.js | 10 +++-- 6 files changed, 115 insertions(+), 55 deletions(-) create mode 100644 LICENSE create mode 100644 README diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..9644b34c3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2009 Jeremy Ashkenas, DocumentCloud + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 000000000..037e284fe --- /dev/null +++ b/README @@ -0,0 +1,10 @@ + __ + /\_\ ____ + \/\ \ /',__\ + __ \ \ \/\__, `\ + /\_\_\ \ \/\____/ + _______\/_/\ \_\ \/___/ +/\______\ \ \____/ +\/______/ \/___/ + +See: documentcloud.github.com/underscore \ No newline at end of file diff --git a/index.html b/index.html index 535df71bb..9bec576ee 100644 --- a/index.html +++ b/index.html @@ -12,8 +12,7 @@ } div.container { width: 720px; - margin: 0 auto; - margin-top: 50px; + margin: 50px 0 50px 50px; } p { width: 550px; @@ -32,7 +31,10 @@ background: #f0c095; } h1, h2, h3, h4, h5, h6 { - margin-top: 35px; + margin-top: 40px; + } + b.method_name { + font-size: 18px; } code, pre, tt { font-family: Monaco, Consolas, "Lucida Console", monospace; @@ -47,7 +49,7 @@ font-size: 12px; padding: 2px 0 2px 12px; border-left: 6px solid #aaaa99; - margin: 0px 0 35px; + margin: 0px 0 30px; } @@ -131,7 +133,7 @@

        Collection Functions (Arrays or Objects)

        - each_.each(list, iterator, [context]) + each_.each(list, iterator, [context])
        Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is @@ -145,7 +147,7 @@ _.each([1, 2, 3], function(num){ alert(num); }); => alerts each number in turn...

        - map_.map(list, iterator, [context]) + map_.map(list, iterator, [context])
        Produces a new array of values by mapping each value in list through a transformation function (iterator). If the native @@ -156,7 +158,7 @@ _.map([1, 2, 3], function(num){ return num * 3 }); => [3, 6, 9]

        - inject_.inject(list, memo, iterator, [context]) + inject_.inject(list, memo, iterator, [context])
        Also known as reduce and foldl, inject reduces a list of values into a single value. Memo is the initial state @@ -169,7 +171,7 @@ var sum = _.inject([1, 2, 3], 0, function(memo, num){ return memo + num });

        - detect_.detect(list, iterator, [context]) + detect_.detect(list, iterator, [context])
        Looks through each value in the list, returning the first one that passes a truth test (iterator). The function returns as @@ -182,7 +184,7 @@ var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

        - select_.select(list, iterator, [context]) + select_.select(list, iterator, [context])
        Looks through each value in the list, returning an array of all the values that pass a truth test (iterator). Delegates to the @@ -194,7 +196,7 @@ var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

        - reject_.reject(list, iterator, [context]) + reject_.reject(list, iterator, [context])
        Returns the values in list without the elements that the truth test (iterator) passes. The opposite of select. @@ -205,7 +207,7 @@ var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

        - all_.all(list, [iterator], [context]) + all_.all(list, [iterator], [context])
        Returns true if all of the values in the list pass the iterator truth test. If an iterator is not provided, the truthy value of @@ -218,7 +220,7 @@ _.all([true, 1, null, 'yes']);

        - any_.any(list, [iterator], [context]) + any_.any(list, [iterator], [context])
        Returns true if any of the values in the list pass the iterator truth test. Short-circuits and stops traversing the list @@ -231,7 +233,7 @@ _.any([null, 0, 'yes', false]);

        - include_.include(list, value) + include_.include(list, value)
        Returns true if the value is present in the list, using == to test equality. Uses indexOf internally, if list @@ -243,7 +245,7 @@ _.include([1, 2, 3], 3);

        - invoke_.invoke(list, methodName, [*arguments]) + invoke_.invoke(list, methodName, [*arguments])
        Calls the method named by methodName on each value in the list. Any extra arguments passed to invoke will be forwarded on to the @@ -255,7 +257,7 @@ _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');

        - pluck_.pluck(list, propertyName) + pluck_.pluck(list, propertyName)
        An optimized version of what is perhaps the most common use-case for map: returning a list of property values. @@ -267,7 +269,7 @@ _.pluck(stooges, 'name');

        - max_.max(list, [iterator], [context]) + max_.max(list, [iterator], [context])
        Returns the maximum value in list. If iterator is passed, it will be used on each value to generate the criterion by which the @@ -280,7 +282,7 @@ _.max(stooges, function(stooge){ return stooge.age; });

        - min_.min(list, [iterator], [context]) + min_.min(list, [iterator], [context])
        Returns the minimum value in list. If iterator is passed, it will be used on each value to generate the criterion by which the @@ -293,7 +295,7 @@ _.min(numbers);

        - sortBy_.sortBy(list, iterator, [context]) + sortBy_.sortBy(list, iterator, [context])
        Returns a sorted list, ranked by the results of running each value through iterator. @@ -304,7 +306,7 @@ _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });

        - sortedIndex_.sortedIndex(list, value, [iterator]) + sortedIndex_.sortedIndex(list, value, [iterator])
        Uses a binary search to determine the index at which the value should be inserted into the list in order to maintain the list's @@ -317,7 +319,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

        - toArray_.toArray(list) + toArray_.toArray(list)
        Converts the list (anything that can be iterated over), into a real Array. Useful for transmuting the arguments object. @@ -328,7 +330,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

        - size_.size(list) + size_.size(list)
        Return the number of values in the list.

        @@ -340,7 +342,7 @@ _.size({one : 1, two : 2, three : 3});

        Array Functions

        - first_.first(array) + first_.first(array)
        Convenience to return the first element of an array (identical to array[0]).

        @@ -350,7 +352,7 @@ _.first([3, 2, 1]);

        - last_.last(array) + last_.last(array)
        Returns the last element of an array.

        @@ -360,7 +362,7 @@ _.last([3, 2, 1]);

        - compact_.compact(array) + compact_.compact(array)
        Returns a copy of the array with all falsy values removed. In Javascript, false, null, 0, "", @@ -372,7 +374,7 @@ _.compact([0, 1, false, 2, '', 3]);

        - flatten_.flatten(array) + flatten_.flatten(array)
        Flattens a nested array (the nesting can be to any depth).

        @@ -382,7 +384,7 @@ _.flatten([1, [2], [3, [[[4]]]]]);

        - without_.without(array, [*values]) + without_.without(array, [*values])
        Returns a copy of the array with all instances of the values removed. == is used for the equality test. @@ -393,7 +395,7 @@ _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);

        - uniq_.uniq(array, [isSorted]) + uniq_.uniq(array, [isSorted])
        Produces a duplicate-free version of the array, using == to test object equality. If you know in advance that the array is sorted, @@ -405,7 +407,7 @@ _.uniq([1, 2, 1, 3, 1, 4]);

        - intersect_.intersect(*arrays) + intersect_.intersect(*arrays)
        Computes the list of values that are the intersection of all the arrays. Each value in the result is present in each of the arrays. @@ -416,7 +418,7 @@ _.intersect([1, 2, 3], [101, 2, 1, 10], [2, 1]);

        - zip_.zip(*arrays) + zip_.zip(*arrays)
        Merges together the values of each of the arrays with the values at the corresponding position. Useful when you have separate @@ -428,7 +430,7 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

        - indexOf_.indexOf(array, value) + indexOf_.indexOf(array, value)
        Returns the index at which value can be found in the array, or -1 if value is not present in the array. Uses the native @@ -442,7 +444,7 @@ _.indexOf([1, 2, 3], 2);

        Function (uh, ahem) Functions

        - bind_.bind(function, context, [*arguments]) + bind_.bind(function, context, [*arguments])
        Bind a function to a context object, meaning that whenever the function is called, the value of this will be the context. @@ -457,7 +459,7 @@ func();

        - bindAll_.bindAll(*methodNames, context) + bindAll_.bindAll(*methodNames, context)
        Binds a number of methods on the context object, specified by methodNames, to be run in the context of that object whenever they @@ -477,7 +479,7 @@ jQuery('#underscore_button').bind('click', buttonView.onClick);

        - delay_.delay(function, wait, [*arguments]) + delay_.delay(function, wait, [*arguments])
        Much like setTimeout, invokes function after wait milliseconds. If you pass the optional arguments, they will be @@ -490,7 +492,7 @@ _.delay(log, 1000, 'logged later');

        - defer_.defer(function) + defer_.defer(function)
        Defers invoking the function until the current call stack has cleared, similar to using setTimeout with a delay of 0. Useful for performing @@ -503,7 +505,7 @@ _.defer(function(){ alert('deferred'); });

        - wrap_.wrap(function, wrapper) + wrap_.wrap(function, wrapper)
        Wraps the first function inside of the wrapper function, passing it as the first argument. This allows the wrapper to @@ -522,7 +524,7 @@ hello();

        Object Functions

        - keys_.keys(object) + keys_.keys(object)
        Retrieve all the names of the object's properties.

        @@ -532,7 +534,7 @@ _.keys({one : 1, two : 2, three : 3});

        - values_.values(object) + values_.values(object)
        Return all of the values of the object's properties.

        @@ -542,7 +544,7 @@ _.values({one : 1, two : 2, three : 3});

        - extend_.extend(destination, source) + extend_.extend(destination, source)
        Copy all of the properties in the source object over to the destination object. @@ -553,7 +555,7 @@ _.extend({name : 'moe'}, {age : 50});

        - clone_.clone(object) + clone_.clone(object)
        Create a shallow-copied clone of the object. Any nested objects or arrays will be copied by reference, not duplicated. @@ -564,7 +566,7 @@ _.clone({name : 'moe'});

        - isEqual_.isEqual(object, other) + isEqual_.isEqual(object, other)
        Performs an optimized deep comparison between the two objects, to determine if they should be considered equal. @@ -579,7 +581,7 @@ _.isEqual(moe, clone);

        - isElement_.isElement(object) + isElement_.isElement(object)
        Returns true if object is a DOM element.

        @@ -589,7 +591,7 @@ _.isElement(jQuery('body')[0]);

        - isArray_.isArray(object) + isArray_.isArray(object)
        Returns true if object is an Array.

        @@ -601,7 +603,7 @@ _.isArray([1,2,3]);

        - isFunction_.isFunction(object) + isFunction_.isFunction(object)
        Returns true if object is a Function.

        @@ -611,7 +613,7 @@ _.isFunction(alert);

        - isUndefined_.isUndefined(variable) + isUndefined_.isUndefined(variable)
        Returns true if variable is undefined.

        @@ -623,7 +625,7 @@ _.isUndefined(window.missingVariable);

        Utility Functions

        - uniqueId_.uniqueId([prefix]) + uniqueId_.uniqueId([prefix])
        Generate a globally-unique id for client-side models or DOM elements that need one. If prefix is passed, the id will be appended to it. @@ -634,15 +636,17 @@ _.uniqueId('contact_');

        - template_.template(templateString) + template_.template(templateString, [context])
        Compiles Javascript templates into functions that can be evaluated for rendering. Useful for rendering complicated bits of HTML from JSON data sources. Template functions can both interpolate variables, using
        <%= … %>, as well as execute arbitrary Javascript code, with - <% … %>. When you evaluate a template, pass in a - context object that has properties corresponding to the template's free - variables. + <% … %>. When you evaluate a template function, pass in a + context object that has properties corresponding to the template's free + variables. If you're writing a one-off, you can pass the context + object as the second parameter to template in order to render + immediately instead of returning a template function.

         var compiled = _.template("hello: <%= name %>");
        diff --git a/test/speed.js b/test/speed.js
        index 63cde52bb..5258fb58f 100644
        --- a/test/speed.js
        +++ b/test/speed.js
        @@ -27,8 +27,24 @@
             return _.uniq(numbers, true);
           });
           
        +  JSLitmus.test('_.sortBy()', function() {
        +    return _.sortBy(numbers, function(num){ return -num; });
        +  });
        +  
           JSLitmus.test('_.isEqual()', function() {
             return _.isEqual(numbers, randomized);
           });
        +  
        +  JSLitmus.test('_.keys()', function() {
        +    return _.keys(objects);
        +  });
        +  
        +  JSLitmus.test('_.values()', function() {
        +    return _.values(objects);
        +  });
        +  
        +  JSLitmus.test('_.intersect()', function() {
        +    return _.intersect(numbers, randomized);
        +  });
         
         })();
        \ No newline at end of file
        diff --git a/test/test.html b/test/test.html
        index 67415345d..1e001b739 100644
        --- a/test/test.html
        +++ b/test/test.html
        @@ -20,8 +20,14 @@
           


          -

          JSLitmus Speed Suite

          -

          Each iteration runs on an array of 1000 elements.

          +

          Underscore Speed Suite

          +

          + A representative sample of the functions are benchmarked here, to provide + a sense of how fast they might run in different browsers. + Each iteration runs on an array of 1000 elements.

          + For example, the 'intersect' test measures the number of times you can + find the intersection of two thousand-element arrays in one second. +


          \ No newline at end of file diff --git a/underscore.js b/underscore.js index 9f2f403e8..d70ea072d 100644 --- a/underscore.js +++ b/underscore.js @@ -1,12 +1,14 @@ // Underscore.js // (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the terms of the MIT license. +// Portions of Underscore are inspired by or borrowed from Prototype.js, +// Oliver Steele's Functional, And John Resig's Micro-Templating. // For all details and documentation: -// http://fdjklsafjsdalk +// http://documentcloud.github.com/underscore/ window._ = { VERSION : '0.1.0', - + // The cornerstone, an each implementation. // Handles objects implementing forEach, _each, arrays, and raw objects. each : function(obj, iterator, context) { @@ -243,9 +245,9 @@ window._ = { // passed-in arrays. intersect : function(array) { var rest = _.toArray(arguments).slice(1); - return _.select(_.uniq(array), function(item1) { + return _.select(_.uniq(array), function(item) { return _.all(rest, function(other) { - return _.detect(other, function(item2){ return item1 === item2; }); + return _.indexOf(other, item) >= 0; }); }); }, From 15bb8c6410bc16bd9df1c7b67d90c177dc1cf2b2 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 13:29:58 -0400 Subject: [PATCH 012/533] read me --- README | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/README b/README index 037e284fe..51327ad24 100644 --- a/README +++ b/README @@ -1,10 +1,17 @@ - __ - /\_\ ____ - \/\ \ /',__\ - __ \ \ \/\__, `\ - /\_\_\ \ \/\____/ - _______\/_/\ \_\ \/___/ -/\______\ \ \____/ -\/______/ \/___/ + __ + /\ \ __ + __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____ +/\ \/\ \ /' _ `\ /'_` \ /'__`\/\`'__\/',__\ /'___\ / __`\/\`'__\/'__`\ \/\ \ /',__\ +\ \ \_\ \/\ \/\ \/\ \L\ \/\ __/\ \ \//\__, `\/\ \__//\ \L\ \ \ \//\ __/ __ \ \ \/\__, `\ + \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/ + \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/ + \ \____/ + \/___/ -See: documentcloud.github.com/underscore \ No newline at end of file +Underscore is a utility-belt library for Javascript that provides +a lot of the functional programming support that you would expect +in Prototype.js (or Ruby), but without extending any of the built- +in Javascript objects. It's the tie to go along with jQuery's tux. + +For Docs, License, Tests, and pre-packed downloads, see: +http://documentcloud.github.com/underscore/ \ No newline at end of file From dc8d4e0b588c9ceb9826493d05205a4c75314489 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 13:30:49 -0400 Subject: [PATCH 013/533] removing the ells from the heart of the ascii art --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 51327ad24..a36288fc1 100644 --- a/README +++ b/README @@ -2,7 +2,7 @@ /\ \ __ __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____ /\ \/\ \ /' _ `\ /'_` \ /'__`\/\`'__\/',__\ /'___\ / __`\/\`'__\/'__`\ \/\ \ /',__\ -\ \ \_\ \/\ \/\ \/\ \L\ \/\ __/\ \ \//\__, `\/\ \__//\ \L\ \ \ \//\ __/ __ \ \ \/\__, `\ +\ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\ \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/ \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/ \ \____/ From 26a9175419294d50945c3469ea4b5705df69a48c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 13:50:02 -0400 Subject: [PATCH 014/533] almost 0.1 --- README | 4 ++-- index.html | 21 +++++++++++++++++++++ underscore-min.js | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 underscore-min.js diff --git a/README b/README index a36288fc1..1bf4faf29 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ __ /\ \ __ __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____ -/\ \/\ \ /' _ `\ /'_` \ /'__`\/\`'__\/',__\ /'___\ / __`\/\`'__\/'__`\ \/\ \ /',__\ +/\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\ \ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\ \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/ \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/ @@ -14,4 +14,4 @@ in Prototype.js (or Ruby), but without extending any of the built- in Javascript objects. It's the tie to go along with jQuery's tux. For Docs, License, Tests, and pre-packed downloads, see: -http://documentcloud.github.com/underscore/ \ No newline at end of file +http://documentcloud.github.com/underscore/ diff --git a/index.html b/index.html index 9bec576ee..e6a4fe669 100644 --- a/index.html +++ b/index.html @@ -36,6 +36,12 @@ b.method_name { font-size: 18px; } + table, tr, td { + margin: 0; padding: 0; + } + td { + padding: 2px 12px 2px 0; + } code, pre, tt { font-family: Monaco, Consolas, "Lucida Console", monospace; font-size: 12px; @@ -86,6 +92,21 @@ for your perusal.

          +

          Downloads

          + +

          + + + + + + + + + +
          Development Version(16kb, Uncompressed Code)
          Production Version(4kb, Packed and Gzipped)
          +

          +

          Table of Contents

          diff --git a/underscore-min.js b/underscore-min.js new file mode 100644 index 000000000..56b63e7bf --- /dev/null +++ b/underscore-min.js @@ -0,0 +1 @@ +window._={VERSION:"0.1.0",each:function(c,f,a){var g=0;try{if(c.forEach){c.forEach(f,a)}else{if(c.length){for(var d=0;d=a.computed){a={value:g,computed:f}}});return a.value},min:function(d,c,b){if(!c&&_.isArray(d)){return Math.min.apply(Math,d)}var a;_.each(d,function(g,e){var f=c?c.call(b,g,e):g;if(a==null||fd?1:0}),"value")},sortedIndex:function(f,e,c){c=c||function(g){return g};var a=0,d=f.length;while(a>1;c(f[b])=0})})},zip:function(){var a=_.toArray(arguments);var d=_.max(_.pluck(a,"length"));var c=new Array(d);for(var b=0;b)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return b?a(b):a}}; \ No newline at end of file From 90e34e1a74490863c34a8b68183d0ed700b1e316 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 14:45:05 -0400 Subject: [PATCH 015/533] comment edits --- index.html | 18 +++++++++++------- underscore.js | 8 ++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index e6a4fe669..633721834 100644 --- a/index.html +++ b/index.html @@ -160,7 +160,7 @@ function. The iterator is bound to the context object, if one is passed. If list is a Javascript object, a pair with key and value properties will be yielded. If the list has an each - method of its own defined, it will be used. Delegates to the native + method of its own, it will be used instead. Delegates to the native forEach function if it exists.

          @@ -196,8 +196,8 @@ var sum = _.inject([1, 2, 3], 0, function(memo, num){ return memo + num });
                   
          Looks through each value in the list, returning the first one that passes a truth test (iterator). The function returns as - soon as it finds the true element, and doesn't continue to traverse - the list. + soon as it finds the first acceptable element, and doesn't continue to + traverse the list.

           var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
          @@ -257,7 +257,7 @@ _.any([null, 0, 'yes', false]);
                   include_.include(list, value)
                   
          Returns true if the value is present in the list, using - == to test equality. Uses indexOf internally, if list + === to test equality. Uses indexOf internally, if list is an Array.

          @@ -408,7 +408,7 @@ _.flatten([1, [2], [3, [[[4]]]]]);
                   without_.without(array, [*values])
                   
          Returns a copy of the array with all instances of the values - removed. == is used for the equality test. + removed. === is used for the equality test.

           _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
          @@ -418,7 +418,7 @@ _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
                 

          uniq_.uniq(array, [isSorted])
          - Produces a duplicate-free version of the array, using == to test + Produces a duplicate-free version of the array, using === to test object equality. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm.

          @@ -677,7 +677,11 @@ compiled({name : 'moe'}); var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>"; _.template(list, {people : ['moe', 'curly', 'larry']}); => "<li>moe</li><li>curly</li><li>larry</li>" -
          +
          + +

          + Underscore is a DocumentCloud production. +

          diff --git a/underscore.js b/underscore.js index d70ea072d..22f5b2286 100644 --- a/underscore.js +++ b/underscore.js @@ -10,7 +10,7 @@ window._ = { VERSION : '0.1.0', // The cornerstone, an each implementation. - // Handles objects implementing forEach, _each, arrays, and raw objects. + // Handles objects implementing forEach, each, arrays, and raw objects. each : function(obj, iterator, context) { var index = 0; try { @@ -47,7 +47,7 @@ window._ = { }, // Inject builds up a single result from a list of values. Also known as - // reduce, and foldl. + // reduce, or foldl. inject : function(obj, memo, iterator, context) { _.each(obj, function(value, index) { memo = iterator.call(context, memo, value, index); @@ -113,12 +113,12 @@ window._ = { }, // Determine if a given value is included in the array or object, - // based on '=='. + // based on '==='. include : function(obj, target) { if (_.isArray(obj)) return _.indexOf(obj, target) != -1; var found = false; _.each(obj, function(pair) { - if (pair.value == target) { + if (pair.value === target) { found = true; throw '__break__'; } From 5ef845a663e7016dc949032aa918602ad2cd52d4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 15:01:34 -0400 Subject: [PATCH 016/533] changing include to use === instead of == --- underscore-min.js | 2 +- underscore.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/underscore-min.js b/underscore-min.js index 56b63e7bf..6aae0d7e7 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -window._={VERSION:"0.1.0",each:function(c,f,a){var g=0;try{if(c.forEach){c.forEach(f,a)}else{if(c.length){for(var d=0;d=a.computed){a={value:g,computed:f}}});return a.value},min:function(d,c,b){if(!c&&_.isArray(d)){return Math.min.apply(Math,d)}var a;_.each(d,function(g,e){var f=c?c.call(b,g,e):g;if(a==null||fd?1:0}),"value")},sortedIndex:function(f,e,c){c=c||function(g){return g};var a=0,d=f.length;while(a>1;c(f[b])=0})})},zip:function(){var a=_.toArray(arguments);var d=_.max(_.pluck(a,"length"));var c=new Array(d);for(var b=0;b)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return b?a(b):a}}; \ No newline at end of file +window._={VERSION:"0.1.0",each:function(c,f,a){var g=0;try{if(c.forEach){c.forEach(f,a)}else{if(c.length){for(var d=0;d=a.computed){a={value:g,computed:f}}});return a.value},min:function(d,c,b){if(!c&&_.isArray(d)){return Math.min.apply(Math,d)}var a;_.each(d,function(g,e){var f=c?c.call(b,g,e):g;if(a==null||fd?1:0}),"value")},sortedIndex:function(f,e,c){c=c||function(g){return g};var a=0,d=f.length;while(a>1;c(f[b])=0})})},zip:function(){var a=_.toArray(arguments);var d=_.max(_.pluck(a,"length"));var c=new Array(d);for(var b=0;b)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return b?a(b):a}}; \ No newline at end of file diff --git a/underscore.js b/underscore.js index 22f5b2286..abec6fd9e 100644 --- a/underscore.js +++ b/underscore.js @@ -8,6 +8,8 @@ window._ = { VERSION : '0.1.0', + + /*------------------------ Collection Functions: ---------------------------*/ // The cornerstone, an each implementation. // Handles objects implementing forEach, each, arrays, and raw objects. @@ -200,7 +202,7 @@ window._ = { return _.toArray(obj).length; }, - //------------- The following methods only apply to arrays. ----------------- + /*-------------------------- Array Functions: ------------------------------*/ // Get the first element of an array. first : function(array) { @@ -272,7 +274,7 @@ window._ = { return -1; }, - /* -------------- The following methods apply to functions -----------------*/ + /* ----------------------- Function Functions: -----------------------------*/ // Create a function bound to a given object (assigning 'this', and arguments, // optionally). Binding with arguments is also known as 'curry'. @@ -318,7 +320,7 @@ window._ = { }; }, - /* ---------------- The following methods apply to objects ---------------- */ + /* ------------------------- Object Functions: ---------------------------- */ // Retrieve the names of an object's properties. keys : function(obj) { @@ -383,7 +385,7 @@ window._ = { return typeof obj == 'undefined'; }, - /* -------------- The following methods are utility methods --------------- */ + /* -------------------------- Utility Functions: -------------------------- */ // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. From 6cff16308ddca3807a36ad6dab0281b6341e03d9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 27 Oct 2009 15:28:36 -0400 Subject: [PATCH 017/533] that's 42 functions, not 43 --- index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 633721834..8febc3d60 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@

          - Underscore provides 43-odd functions that support both the usual + Underscore provides 42-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in @@ -92,17 +92,17 @@ for your perusal.

          -

          Downloads

          +

          Downloads (Right-click, and use "Save As")

          - + - +
          Development Version(16kb, Uncompressed Code)16kb, Uncompressed with Comments
          Production Version(4kb, Packed and Gzipped)4kb, Packed and Gzipped

          From f4299d7427abc59bca5b9c69904f25d0bf0f6791 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 28 Oct 2009 08:58:00 -0400 Subject: [PATCH 018/533] adding a documentcloud project pic to the bottom of underscore --- index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 8febc3d60..118e3a2ce 100644 --- a/index.html +++ b/index.html @@ -680,7 +680,9 @@ _.template(list, {people : ['moe', 'curly', 'larry']});

          - Underscore is a DocumentCloud production. + + A DocumentCloud Project +

          From 195a0f0908e5860105fa63d7539c8b17c21f9a49 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 28 Oct 2009 09:22:50 -0400 Subject: [PATCH 019/533] adding github link --- index.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.html b/index.html index 118e3a2ce..9d3ee6e18 100644 --- a/index.html +++ b/index.html @@ -92,6 +92,11 @@ for your perusal.

          +

          + The unabridged source code is + available on GitHub. +

          +

          Downloads (Right-click, and use "Save As")

          From 6d52832a73dcedffd8838ebe0d12fc1d4841588b Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 28 Oct 2009 12:37:55 -0400 Subject: [PATCH 020/533] going to version 0.1.1 with noConflict --- index.html | 16 +++++++++++++--- test/utility.js | 6 ++++++ underscore-min.js | 2 +- underscore.js | 15 +++++++++++++-- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 9d3ee6e18..db3ee81b0 100644 --- a/index.html +++ b/index.html @@ -102,11 +102,11 @@

          - + - +
          Development VersionDevelopment Version (0.1.1) 16kb, Uncompressed with Comments
          Production VersionProduction Version (0.1.1) 4kb, Packed and Gzipped
          @@ -151,7 +151,8 @@

          Utility
          - uniqueId, template + noConflict, + uniqueId, template

          @@ -649,6 +650,15 @@ _.isUndefined(window.missingVariable);

          Utility Functions

          + +

          + noConflict_.noConflict() +
          + Give control of the "_" variable back to its previous owner. Returns + a reference to the Underscore object. +

          +
          +var underscore = _.noConflict();

          uniqueId_.uniqueId([prefix]) diff --git a/test/utility.js b/test/utility.js index cfaaaace3..9a8eb516f 100644 --- a/test/utility.js +++ b/test/utility.js @@ -1,6 +1,12 @@ $(document).ready(function() { module("Utility functions (uniqueId, template)"); + + test("utility: noConflict", function() { + var underscore = _.noConflict(); + ok(underscore.isUndefined(_), "The '_' variable has been returned to its previous state."); + window._ = underscore; + }); test("utility: uniqueId", function() { var ids = [], i = 0; diff --git a/underscore-min.js b/underscore-min.js index 6aae0d7e7..096ff0aac 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -window._={VERSION:"0.1.0",each:function(c,f,a){var g=0;try{if(c.forEach){c.forEach(f,a)}else{if(c.length){for(var d=0;d=a.computed){a={value:g,computed:f}}});return a.value},min:function(d,c,b){if(!c&&_.isArray(d)){return Math.min.apply(Math,d)}var a;_.each(d,function(g,e){var f=c?c.call(b,g,e):g;if(a==null||fd?1:0}),"value")},sortedIndex:function(f,e,c){c=c||function(g){return g};var a=0,d=f.length;while(a>1;c(f[b])=0})})},zip:function(){var a=_.toArray(arguments);var d=_.max(_.pluck(a,"length"));var c=new Array(d);for(var b=0;b)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return b?a(b):a}}; \ No newline at end of file +window.Underscore={VERSION:"0.1.1",PREVIOUS_UNDERSCORE:window._,each:function(c,f,a){var g=0;try{if(c.forEach){c.forEach(f,a)}else{if(c.length){for(var d=0;d=a.computed){a={value:g,computed:f}}});return a.value},min:function(d,c,b){if(!c&&_.isArray(d)){return Math.min.apply(Math,d)}var a;_.each(d,function(g,e){var f=c?c.call(b,g,e):g;if(a==null||fd?1:0}),"value")},sortedIndex:function(f,e,c){c=c||function(g){return g};var a=0,d=f.length;while(a>1;c(f[b])=0})})},zip:function(){var a=_.toArray(arguments);var d=_.max(_.pluck(a,"length"));var c=new Array(d);for(var b=0;b)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return b?a(b):a}};window._=Underscore; \ No newline at end of file diff --git a/underscore.js b/underscore.js index abec6fd9e..0091abd90 100644 --- a/underscore.js +++ b/underscore.js @@ -5,9 +5,11 @@ // Oliver Steele's Functional, And John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore/ -window._ = { +window.Underscore = { - VERSION : '0.1.0', + VERSION : '0.1.1', + + PREVIOUS_UNDERSCORE : window._, /*------------------------ Collection Functions: ---------------------------*/ @@ -387,6 +389,13 @@ window._ = { /* -------------------------- Utility Functions: -------------------------- */ + // Run Underscore.js in noConflict mode, returning the '_' variable to its + // previous owner. Returns a reference to the Underscore object. + noConflict : function() { + window._ = Underscore.PREVIOUS_UNDERSCORE; + return this; + }, + // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. uniqueId : function(prefix) { @@ -413,3 +422,5 @@ window._ = { } }; + +window._ = Underscore; From 4a83fcdd260f61f253f3349740ee304074867aff Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 28 Oct 2009 18:49:50 -0400 Subject: [PATCH 021/533] version 0.2.0 is out, with inject -> reduce, JS standard methodname aliases, a compose(), and a lastIndexOf() --- index.html | 167 ++++++++++++++++++++----------- package.json | 12 +++ test/arrays.js | 7 ++ test/collections.js | 20 +++- test/functions.js | 10 ++ test/utility.js | 2 + underscore-min.js | 2 +- underscore.js | 232 +++++++++++++++++++++++++------------------- 8 files changed, 294 insertions(+), 158 deletions(-) create mode 100644 package.json diff --git a/index.html b/index.html index db3ee81b0..11f706290 100644 --- a/index.html +++ b/index.html @@ -33,9 +33,14 @@ h1, h2, h3, h4, h5, h6 { margin-top: 40px; } - b.method_name { + b.header { font-size: 18px; } + span.alias { + font-size: 14px; + font-style: italic; + margin-left: 20px; + } table, tr, td { margin: 0; padding: 0; } @@ -76,7 +81,7 @@

          - Underscore provides 42-odd functions that support both the usual + Underscore provides 44-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in @@ -102,11 +107,11 @@

          - + - +
          Development Version (0.1.1)Development Version (0.2.0) 16kb, Uncompressed with Comments
          Production Version (0.1.1)Production Version (0.2.0) 4kb, Packed and Gzipped
          @@ -118,7 +123,7 @@ Collections
          each, map, - inject, detect, select, reject, all, + reduce, detect, select, reject, all, any, include, invoke, pluck, max, min, sortBy, sortedIndex, toArray, size @@ -129,14 +134,15 @@
          first, last, compact, flatten, without, uniq, - intersect, zip, indexOf + intersect, zip, indexOf, + lastIndexOf

          Functions
          bind, bindAll, delay, - defer, wrap + defer, wrap, compose

          @@ -160,7 +166,8 @@

          Collection Functions (Arrays or Objects)

          - each_.each(list, iterator, [context]) + each_.each(list, iterator, [context]) + Alias: forEach
          Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is @@ -174,7 +181,7 @@ _.each([1, 2, 3], function(num){ alert(num); }); => alerts each number in turn...

          - map_.map(list, iterator, [context]) + map_.map(list, iterator, [context])
          Produces a new array of values by mapping each value in list through a transformation function (iterator). If the native @@ -184,21 +191,22 @@ _.each([1, 2, 3], function(num){ alert(num); }); _.map([1, 2, 3], function(num){ return num * 3 }); => [3, 6, 9] -

          - inject_.inject(list, memo, iterator, [context]) +

          + reduce_.reduce(list, memo, iterator, [context]) + Alias: inject
          - Also known as reduce and foldl, inject reduces a + Also known as inject and foldl, reduce boils down a list of values into a single value. Memo is the initial state of the reduction, and each successive step of it should be returned by iterator.

          -var sum = _.inject([1, 2, 3], 0, function(memo, num){ return memo + num });
          +var sum = _.reduce([1, 2, 3], 0, function(memo, num){ return memo + num });
           => 6
           

          - detect_.detect(list, iterator, [context]) + detect_.detect(list, iterator, [context])
          Looks through each value in the list, returning the first one that passes a truth test (iterator). The function returns as @@ -211,7 +219,8 @@ var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

          - select_.select(list, iterator, [context]) + select_.select(list, iterator, [context]) + Alias: filter
          Looks through each value in the list, returning an array of all the values that pass a truth test (iterator). Delegates to the @@ -223,7 +232,7 @@ var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

          - reject_.reject(list, iterator, [context]) + reject_.reject(list, iterator, [context])
          Returns the values in list without the elements that the truth test (iterator) passes. The opposite of select. @@ -234,7 +243,8 @@ var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

          - all_.all(list, [iterator], [context]) + all_.all(list, [iterator], [context]) + Alias: every
          Returns true if all of the values in the list pass the iterator truth test. If an iterator is not provided, the truthy value of @@ -247,7 +257,8 @@ _.all([true, 1, null, 'yes']);

          - any_.any(list, [iterator], [context]) + any_.any(list, [iterator], [context]) + Alias: some
          Returns true if any of the values in the list pass the iterator truth test. Short-circuits and stops traversing the list @@ -260,7 +271,7 @@ _.any([null, 0, 'yes', false]);

          - include_.include(list, value) + include_.include(list, value)
          Returns true if the value is present in the list, using === to test equality. Uses indexOf internally, if list @@ -272,7 +283,7 @@ _.include([1, 2, 3], 3);

          - invoke_.invoke(list, methodName, [*arguments]) + invoke_.invoke(list, methodName, [*arguments])
          Calls the method named by methodName on each value in the list. Any extra arguments passed to invoke will be forwarded on to the @@ -284,7 +295,7 @@ _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');

          - pluck_.pluck(list, propertyName) + pluck_.pluck(list, propertyName)
          An optimized version of what is perhaps the most common use-case for map: returning a list of property values. @@ -296,7 +307,7 @@ _.pluck(stooges, 'name');

          - max_.max(list, [iterator], [context]) + max_.max(list, [iterator], [context])
          Returns the maximum value in list. If iterator is passed, it will be used on each value to generate the criterion by which the @@ -309,7 +320,7 @@ _.max(stooges, function(stooge){ return stooge.age; });

          - min_.min(list, [iterator], [context]) + min_.min(list, [iterator], [context])
          Returns the minimum value in list. If iterator is passed, it will be used on each value to generate the criterion by which the @@ -322,7 +333,7 @@ _.min(numbers);

          - sortBy_.sortBy(list, iterator, [context]) + sortBy_.sortBy(list, iterator, [context])
          Returns a sorted list, ranked by the results of running each value through iterator. @@ -333,7 +344,7 @@ _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });

          - sortedIndex_.sortedIndex(list, value, [iterator]) + sortedIndex_.sortedIndex(list, value, [iterator])
          Uses a binary search to determine the index at which the value should be inserted into the list in order to maintain the list's @@ -346,7 +357,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

          - toArray_.toArray(list) + toArray_.toArray(list)
          Converts the list (anything that can be iterated over), into a real Array. Useful for transmuting the arguments object. @@ -357,7 +368,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

          - size_.size(list) + size_.size(list)
          Return the number of values in the list.

          @@ -369,7 +380,7 @@ _.size({one : 1, two : 2, three : 3});

          Array Functions

          - first_.first(array) + first_.first(array)
          Convenience to return the first element of an array (identical to array[0]).

          @@ -379,7 +390,7 @@ _.first([3, 2, 1]);

          - last_.last(array) + last_.last(array)
          Returns the last element of an array.

          @@ -389,7 +400,7 @@ _.last([3, 2, 1]);

          - compact_.compact(array) + compact_.compact(array)
          Returns a copy of the array with all falsy values removed. In Javascript, false, null, 0, "", @@ -401,7 +412,7 @@ _.compact([0, 1, false, 2, '', 3]);

          - flatten_.flatten(array) + flatten_.flatten(array)
          Flattens a nested array (the nesting can be to any depth).

          @@ -411,7 +422,7 @@ _.flatten([1, [2], [3, [[[4]]]]]);

          - without_.without(array, [*values]) + without_.without(array, [*values])
          Returns a copy of the array with all instances of the values removed. === is used for the equality test. @@ -422,7 +433,7 @@ _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);

          - uniq_.uniq(array, [isSorted]) + uniq_.uniq(array, [isSorted])
          Produces a duplicate-free version of the array, using === to test object equality. If you know in advance that the array is sorted, @@ -434,7 +445,7 @@ _.uniq([1, 2, 1, 3, 1, 4]);

          - intersect_.intersect(*arrays) + intersect_.intersect(*arrays)
          Computes the list of values that are the intersection of all the arrays. Each value in the result is present in each of the arrays. @@ -445,7 +456,7 @@ _.intersect([1, 2, 3], [101, 2, 1, 10], [2, 1]);

          - zip_.zip(*arrays) + zip_.zip(*arrays)
          Merges together the values of each of the arrays with the values at the corresponding position. Useful when you have separate @@ -457,7 +468,7 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

          - indexOf_.indexOf(array, value) + indexOf_.indexOf(array, value)
          Returns the index at which value can be found in the array, or -1 if value is not present in the array. Uses the native @@ -466,12 +477,24 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

           _.indexOf([1, 2, 3], 2);
           => 1
          +
          + +

          + lastIndexOf_.lastIndexOf(array, value) +
          + Returns the index of the last occurrence of value in the array, + or -1 if value is not present. Uses the native lastIndexOf + function if possible. +

          +
          +_.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
          +=> 4
           

          Function (uh, ahem) Functions

          - bind_.bind(function, context, [*arguments]) + bind_.bind(function, context, [*arguments])
          Bind a function to a context object, meaning that whenever the function is called, the value of this will be the context. @@ -486,7 +509,7 @@ func();

          - bindAll_.bindAll(*methodNames, context) + bindAll_.bindAll(*methodNames, context)
          Binds a number of methods on the context object, specified by methodNames, to be run in the context of that object whenever they @@ -506,7 +529,7 @@ jQuery('#underscore_button').bind('click', buttonView.onClick);

          - delay_.delay(function, wait, [*arguments]) + delay_.delay(function, wait, [*arguments])
          Much like setTimeout, invokes function after wait milliseconds. If you pass the optional arguments, they will be @@ -519,7 +542,7 @@ _.delay(log, 1000, 'logged later');

          - defer_.defer(function) + defer_.defer(function)
          Defers invoking the function until the current call stack has cleared, similar to using setTimeout with a delay of 0. Useful for performing @@ -532,7 +555,7 @@ _.defer(function(){ alert('deferred'); });

          - wrap_.wrap(function, wrapper) + wrap_.wrap(function, wrapper)
          Wraps the first function inside of the wrapper function, passing it as the first argument. This allows the wrapper to @@ -545,13 +568,29 @@ hello = _.wrap(hello, function(func) { return "before, " + func("moe") + ", after"; }); hello(); -=> before, hello: moe, after +=> 'before, hello: moe, after' + + +

          + compose_.compose(*functions) +
          + Returns the composition of a list of functions, where each function + consumes the return value of the function that follows. In math terms, + composing the functions f(), g(), and h() produces + f(g(h())). +

          +
          +var greet    = function(name){ return "hi: " + name; };
          +var exclaim  = function(statement){ return statement + "!"; };
          +var welcome = _.compose(greet, exclaim);
          +welcome('moe');
          +=> 'hi: moe!'
           

          Object Functions

          - keys_.keys(object) + keys_.keys(object)
          Retrieve all the names of the object's properties.

          @@ -561,7 +600,7 @@ _.keys({one : 1, two : 2, three : 3});

          - values_.values(object) + values_.values(object)
          Return all of the values of the object's properties.

          @@ -571,7 +610,7 @@ _.values({one : 1, two : 2, three : 3});

          - extend_.extend(destination, source) + extend_.extend(destination, source)
          Copy all of the properties in the source object over to the destination object. @@ -582,7 +621,7 @@ _.extend({name : 'moe'}, {age : 50});

          - clone_.clone(object) + clone_.clone(object)
          Create a shallow-copied clone of the object. Any nested objects or arrays will be copied by reference, not duplicated. @@ -593,7 +632,7 @@ _.clone({name : 'moe'});

          - isEqual_.isEqual(object, other) + isEqual_.isEqual(object, other)
          Performs an optimized deep comparison between the two objects, to determine if they should be considered equal. @@ -608,7 +647,7 @@ _.isEqual(moe, clone);

          - isElement_.isElement(object) + isElement_.isElement(object)
          Returns true if object is a DOM element.

          @@ -618,7 +657,7 @@ _.isElement(jQuery('body')[0]);

          - isArray_.isArray(object) + isArray_.isArray(object)
          Returns true if object is an Array.

          @@ -630,7 +669,7 @@ _.isArray([1,2,3]);

          - isFunction_.isFunction(object) + isFunction_.isFunction(object)
          Returns true if object is a Function.

          @@ -640,7 +679,7 @@ _.isFunction(alert);

          - isUndefined_.isUndefined(variable) + isUndefined_.isUndefined(variable)
          Returns true if variable is undefined.

          @@ -652,7 +691,7 @@ _.isUndefined(window.missingVariable);

          Utility Functions

          - noConflict_.noConflict() + noConflict_.noConflict()
          Give control of the "_" variable back to its previous owner. Returns a reference to the Underscore object. @@ -661,7 +700,7 @@ _.isUndefined(window.missingVariable); var underscore = _.noConflict();

          - uniqueId_.uniqueId([prefix]) + uniqueId_.uniqueId([prefix])
          Generate a globally-unique id for client-side models or DOM elements that need one. If prefix is passed, the id will be appended to it. @@ -672,7 +711,7 @@ _.uniqueId('contact_');

          - template_.template(templateString, [context]) + template_.template(templateString, [context])
          Compiles Javascript templates into functions that can be evaluated for rendering. Useful for rendering complicated bits of HTML from JSON @@ -694,6 +733,26 @@ _.template(list, {people : ['moe', 'curly', 'larry']}); => "<li>moe</li><li>curly</li><li>larry</li>" +

          Change Log

          + +

          + 0.2.0
          + Added compose and lastIndexOf, renamed inject to + reduce, added aliases for inject, filter, + every, some, and forEach. +

          + +

          + 0.1.1
          + Added noConflict, so that the "Underscore" object can be assigned to + other variables. +

          + +

          + 0.1.0
          + Initial release of Underscore.js. +

          +

          A DocumentCloud Project diff --git a/package.json b/package.json new file mode 100644 index 000000000..0f55aaa87 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +// ServerJS package specification. +{ + "name": "underscore", + "description": "Functional programming aid for Javascript. Works well with jQuery.", + "url": "http://documentcloud.github.com/underscore/", + "keywords": ["util", "functional", "server", "client", "browser"], + "author": "Jeremy Ashkenas ", + "maintainer": "Kris Kowal ", + "contributors": [], + "dependencies": [], + "lib", "." +} diff --git a/test/arrays.js b/test/arrays.js index e59397996..dd54c7266 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -49,4 +49,11 @@ $(document).ready(function() { equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); }); + test("arrays: lastIndexOf", function() { + var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; + numbers.lastIndexOf = null; + equals(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); + equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); + }); + }); diff --git a/test/collections.js b/test/collections.js index bed210ac2..f97e6e520 100644 --- a/test/collections.js +++ b/test/collections.js @@ -14,6 +14,10 @@ $(document).ready(function() { 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'); + + answers = []; + _.forEach([1, 2, 3], function(num){ answers.push(num); }); + equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); }); test('collections: map', function() { @@ -24,9 +28,12 @@ $(document).ready(function() { equals(tripled.join(', '), '3, 6, 9', 'tripled numbers with context'); }); - test('collections: inject', function() { - var sum = _.inject([1,2,3], 0, function(sum, num){ return sum + num; }); + test('collections: reduce', function() { + var sum = _.reduce([1, 2, 3], 0, function(sum, num){ return sum + num; }); equals(sum, 6, 'can sum up an array'); + + sum = _.inject([1, 2, 3], 0, function(sum, num){ return sum + num; }); + equals(sum, 6, 'aliased as "inject"'); }); test('collections: detect', function() { @@ -35,12 +42,15 @@ $(document).ready(function() { }); test('collections: select', function() { - var evens = _.select([1,2,3,4,5,6], function(num){ return num % 2 == 0; }); + var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equals(evens.join(', '), '2, 4, 6', 'selected each even number'); + + evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); + equals(evens.join(', '), '2, 4, 6', 'aliased as "filter"'); }); test('collections: reject', function() { - var odds = _.reject([1,2,3,4,5,6], function(num){ return num % 2 == 0; }); + var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equals(odds.join(', '), '1, 3, 5', 'rejected each even number'); }); @@ -50,6 +60,7 @@ $(document).ready(function() { ok(!_.all([true, false, true]), 'one false value'); ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); + ok(_.every([true, true, true]), 'aliased as "every"'); }); test('collections: any', function() { @@ -58,6 +69,7 @@ $(document).ready(function() { ok(_.any([false, false, true]), 'one true value'); ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); + ok(_.some([false, false, true]), 'aliased as "some"'); }); test('collections: include', function() { diff --git a/test/functions.js b/test/functions.js index 039f7a746..9e401e4c7 100644 --- a/test/functions.js +++ b/test/functions.js @@ -48,4 +48,14 @@ $(document).ready(function() { 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/utility.js b/test/utility.js index 9a8eb516f..af9680bb5 100644 --- a/test/utility.js +++ b/test/utility.js @@ -5,6 +5,8 @@ $(document).ready(function() { test("utility: noConflict", function() { var underscore = _.noConflict(); ok(underscore.isUndefined(_), "The '_' variable has been returned to its previous state."); + var intersection = underscore.intersect([-1, 0, 1, 2], [1, 2, 3, 4]); + equals(intersection.join(', '), '1, 2', 'but the intersection function still works'); window._ = underscore; }); diff --git a/underscore-min.js b/underscore-min.js index 096ff0aac..98d0437b5 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -window.Underscore={VERSION:"0.1.1",PREVIOUS_UNDERSCORE:window._,each:function(c,f,a){var g=0;try{if(c.forEach){c.forEach(f,a)}else{if(c.length){for(var d=0;d=a.computed){a={value:g,computed:f}}});return a.value},min:function(d,c,b){if(!c&&_.isArray(d)){return Math.min.apply(Math,d)}var a;_.each(d,function(g,e){var f=c?c.call(b,g,e):g;if(a==null||fd?1:0}),"value")},sortedIndex:function(f,e,c){c=c||function(g){return g};var a=0,d=f.length;while(a>1;c(f[b])=0})})},zip:function(){var a=_.toArray(arguments);var d=_.max(_.pluck(a,"length"));var c=new Array(d);for(var b=0;b)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return b?a(b):a}};window._=Underscore; \ No newline at end of file +(function(){var a=(typeof window!="undefined")?window:exports;var c=a._;var b=a._={};b.VERSION="0.2.0";b.each=function(g,j,d){var k=0;try{if(g.forEach){g.forEach(j,d)}else{if(g.length){for(var h=0;h=d.computed){d={value:k,computed:j}}});return d.value};b.min=function(g,f,e){if(!f&&b.isArray(g)){return Math.min.apply(Math,g)}var d;b.each(g,function(k,h){var j=f?f.call(e,k,h):k;if(d==null||jg?1:0}),"value")};b.sortedIndex=function(j,h,f){f=f||function(k){return k};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;i--){if(e[i]===d){return i}}return -1};b.bind=function(f,e){if(!e){return f}var d=b.toArray(arguments).slice(2);return function(){var g=d.concat(b.toArray(arguments));return f.apply(e,g)}};b.bindAll=function(){var d=b.toArray(arguments);var e=d.pop();b.each(d,function(f){e[f]=b.bind(e[f],e)})};b.delay=function(e,f){var d=b.toArray(arguments).slice(2);return setTimeout(function(){return e.apply(e,d)},f)};b.defer=function(d){return b.delay.apply(b,[d,1].concat(b.toArray(arguments).slice(1)))};b.wrap=function(d,e){return function(){var f=[d].concat(b.toArray(arguments));return e.apply(e,f)}};b.compose=function(){var d=b.toArray(arguments);return function(){for(var e=d.length-1;e>=0;e--){arguments=[d[e].apply(this,arguments)]}return arguments[0]}};b.keys=function(d){return b.pluck(d,"key")};b.values=function(d){return b.pluck(d,"value")};b.extend=function(d,f){for(var e in f){d[e]=f[e]}return d};b.clone=function(d){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 typeof d=="function"};b.isUndefined=function(d){return typeof d=="undefined"};b.noConflict=function(){a._=c;return this};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.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any;if(!b.isUndefined(exports)){exports=b}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 0091abd90..7b48ab101 100644 --- a/underscore.js +++ b/underscore.js @@ -5,17 +5,22 @@ // Oliver Steele's Functional, And John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore/ -window.Underscore = { + +(function() { - VERSION : '0.1.1', + var root = (typeof window != 'undefined') ? window : exports; - PREVIOUS_UNDERSCORE : window._, + var previousUnderscore = root._; + var _ = root._ = {}; + + _.VERSION = '0.2.0'; + /*------------------------ Collection Functions: ---------------------------*/ // The cornerstone, an each implementation. // Handles objects implementing forEach, each, arrays, and raw objects. - each : function(obj, iterator, context) { + _.each = function(obj, iterator, context) { var index = 0; try { if (obj.forEach) { @@ -37,30 +42,30 @@ window.Underscore = { if (e != '__break__') throw e; } return obj; - }, + }; // Return the results of applying the iterator to each element. Use Javascript // 1.6's version of map, if possible. - map : function(obj, iterator, context) { + _.map = function(obj, iterator, context) { if (obj && obj.map) return obj.map(iterator, context); var results = []; _.each(obj, function(value, index) { results.push(iterator.call(context, value, index)); }); return results; - }, + }; - // Inject builds up a single result from a list of values. Also known as - // reduce, or foldl. - inject : function(obj, memo, iterator, context) { + // Reduce builds up a single result from a list of values. Also known as + // inject, or foldl. + _.reduce = function(obj, memo, iterator, context) { _.each(obj, function(value, index) { memo = iterator.call(context, memo, value, index); }); return memo; - }, + }; // Return the first value which passes a truth test. - detect : function(obj, iterator, context) { + _.detect = function(obj, iterator, context) { var result; _.each(obj, function(value, index) { if (iterator.call(context, value, index)) { @@ -69,31 +74,31 @@ window.Underscore = { } }); 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) { + _.select = function(obj, iterator, context) { if (obj.filter) return obj.filter(iterator, context); var results = []; _.each(obj, function(value, index) { if (iterator.call(context, value, index)) results.push(value); }); return results; - }, + }; // Return all the elements for which a truth test fails. - reject : function(obj, iterator, context) { + _.reject = function(obj, iterator, context) { var results = []; _.each(obj, function(value, index) { if (!iterator.call(context, value, index)) results.push(value); }); 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) { + _.all = function(obj, iterator, context) { iterator = iterator || function(v){ return v; }; if (obj.every) return obj.every(iterator, context); var result = true; @@ -102,11 +107,11 @@ window.Underscore = { if (!result) throw '__break__'; }); 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) { + _.any = function(obj, iterator, context) { iterator = iterator || function(v) { return v; }; if (obj.some) return obj.some(iterator, context); var result = false; @@ -114,11 +119,11 @@ window.Underscore = { if (result = !!iterator.call(context, value, index)) throw '__break__'; }); return result; - }, + }; // Determine if a given value is included in the array or object, // based on '==='. - include : function(obj, target) { + _.include = function(obj, target) { if (_.isArray(obj)) return _.indexOf(obj, target) != -1; var found = false; _.each(obj, function(pair) { @@ -128,25 +133,25 @@ window.Underscore = { } }); return found; - }, + }; // Invoke a method with arguments on every item in a collection. - invoke : function(obj, method) { + _.invoke = function(obj, method) { var args = _.toArray(arguments).slice(2); return _.map(obj, function(value) { return (method ? value[method] : value).apply(value, args); }); - }, + }; // Optimized version of a common use case of map: fetching a property. - pluck : function(obj, key) { + _.pluck = function(obj, key) { var results = []; _.each(obj, function(value){ results.push(value[key]); }); return results; - }, + }; // Return the maximum item or (item-based computation). - max : function(obj, iterator, context) { + _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); var result; _.each(obj, function(value, index) { @@ -154,10 +159,10 @@ window.Underscore = { if (result == null || computed >= result.computed) result = {value : value, computed : computed}; }); return result.value; - }, + }; // Return the minimum element (or element-based computation). - min : function(obj, iterator, context) { + _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); var result; _.each(obj, function(value, index) { @@ -165,10 +170,10 @@ window.Underscore = { if (result == null || computed < result.computed) result = {value : value, computed : computed}; }); return result.value; - }, + }; // Sort the object's values by a criteria produced by an iterator. - sortBy : function(obj, iterator, context) { + _.sortBy = function(obj, iterator, context) { return _.pluck(_.map(obj, function(value, index) { return { value : value, @@ -178,11 +183,11 @@ window.Underscore = { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }), 'value'); - }, + }; // Use a comparator function to figure out at what index an object should // be inserted so as to maintain order. Uses binary search. - sortedIndex : function(array, obj, iterator) { + _.sortedIndex = function(array, obj, iterator) { iterator = iterator || function(val) { return val; }; var low = 0, high = array.length; while (low < high) { @@ -190,163 +195,182 @@ window.Underscore = { iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; } return low; - }, + }; // Convert anything iterable into a real, live array. - toArray : function(iterable) { + _.toArray = function(iterable) { if (!iterable) return []; if (_.isArray(iterable)) return iterable; return _.map(iterable, function(val){ return val; }); - }, + }; // Return the number of elements in an object. - size : function(obj) { + _.size = function(obj) { return _.toArray(obj).length; - }, + }; /*-------------------------- Array Functions: ------------------------------*/ // Get the first element of an array. - first : function(array) { + _.first = function(array) { return array[0]; - }, + }; // Get the last element of an array. - last : function(array) { + _.last = function(array) { return array[array.length - 1]; - }, + }; // Trim out all falsy values from an array. - compact : function(array) { + _.compact = function(array) { return _.select(array, function(value){ return !!value; }); - }, + }; // Return a completely flattened version of an array. - flatten : function(array) { - return _.inject(array, [], function(memo, value) { + _.flatten = function(array) { + return _.reduce(array, [], function(memo, value) { if (_.isArray(value)) return memo.concat(_.flatten(value)); memo.push(value); return memo; }); - }, + }; // Return a version of the array that does not contain the specified value(s). - without : function(array) { + _.without = function(array) { var values = array.slice.call(arguments, 0); return _.select(array, function(value){ return !_.include(values, value); }); - }, + }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. - uniq : function(array, isSorted) { - return _.inject(array, [], function(memo, el, i) { + _.uniq = function(array, isSorted) { + return _.reduce(array, [], function(memo, el, i) { if (0 == i || (isSorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); return memo; }); - }, + }; // Produce an array that contains every item shared between all the // passed-in arrays. - intersect : function(array) { + _.intersect = function(array) { var rest = _.toArray(arguments).slice(1); return _.select(_.uniq(array), function(item) { return _.all(rest, function(other) { return _.indexOf(other, item) >= 0; }); }); - }, + }; // Zip together multiple lists into a single array -- elements that share // an index go together. - zip : function() { + _.zip = function() { var args = _.toArray(arguments); var length = _.max(_.pluck(args, 'length')); var results = new Array(length); for (var i=0; i=0; i--) if (array[i] === item) return i; + return -1; + }; /* ----------------------- Function Functions: -----------------------------*/ // 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, context) { if (!context) return func; var args = _.toArray(arguments).slice(2); return function() { var a = args.concat(_.toArray(arguments)); return func.apply(context, a); }; - }, + }; // 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() { + _.bindAll = function() { var args = _.toArray(arguments); var context = args.pop(); _.each(args, function(methodName) { context[methodName] = _.bind(context[methodName], context); }); - }, + }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. - delay : function(func, wait) { + _.delay = function(func, wait) { var args = _.toArray(arguments).slice(2); - return window.setTimeout(function(){ return func.apply(func, args); }, wait); - }, + return setTimeout(function(){ return func.apply(func, args); }, wait); + }; // Defers a function, scheduling it to run after the current call stack has // cleared. - defer : function(func) { + _.defer = function(func) { return _.delay.apply(_, [func, 1].concat(_.toArray(arguments).slice(1))); - }, + }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. - wrap : function(func, wrapper) { + _.wrap = function(func, wrapper) { return function() { var args = [func].concat(_.toArray(arguments)); return wrapper.apply(wrapper, args); }; - }, + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = _.toArray(arguments); + return function() { + for (var i=funcs.length-1; i >= 0; i--) { + arguments = [funcs[i].apply(this, arguments)]; + } + return arguments[0]; + }; + }; /* ------------------------- Object Functions: ---------------------------- */ // Retrieve the names of an object's properties. - keys : function(obj) { + _.keys = function(obj) { return _.pluck(obj, 'key'); - }, + }; // Retrieve the values of an object's properties. - values : function(obj) { + _.values = function(obj) { return _.pluck(obj, 'value'); - }, + }; // Extend a given object with all of the properties in a source object. - extend : function(destination, source) { + _.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; - }, + }; // Create a (shallow-cloned) duplicate of an object. - clone : function(obj) { + _.clone = function(obj) { return _.extend({}, obj); - }, + }; // Perform a deep comparison to check if two objects are equal. - isEqual : function(a, b) { + _.isEqual = function(a, b) { // Check object identity. if (a === b) return true; // Different types? @@ -365,47 +389,47 @@ window.Underscore = { // Recursive comparison of contents. for (var key in a) if (!_.isEqual(a[key], b[key])) return false; return true; - }, + }; // Is a given value a DOM element? - isElement : function(obj) { + _.isElement = function(obj) { return !!(obj && obj.nodeType == 1); - }, + }; // Is a given value a real Array? - isArray : function(obj) { + _.isArray = function(obj) { return Object.prototype.toString.call(obj) == '[object Array]'; - }, + }; // Is a given value a Function? - isFunction : function(obj) { + _.isFunction = function(obj) { return typeof obj == 'function'; - }, + }; // Is a given variable undefined? - isUndefined : function(obj) { + _.isUndefined = function(obj) { return typeof obj == 'undefined'; - }, + }; /* -------------------------- Utility Functions: -------------------------- */ // Run Underscore.js in noConflict mode, returning the '_' variable to its // previous owner. Returns a reference to the Underscore object. - noConflict : function() { - window._ = Underscore.PREVIOUS_UNDERSCORE; + _.noConflict = function() { + root._ = previousUnderscore; return this; - }, + }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. - uniqueId : function(prefix) { + _.uniqueId = function(prefix) { var id = this._idCounter = (this._idCounter || 0) + 1; return prefix ? prefix + id : id; - }, + }; // Javascript templating a-la ERB, pilfered from John Resig's // "Secrets of the Javascript Ninja", page 83. - template : function(str, data) { + _.template = function(str, data) { var fn = new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};' + 'with(obj){p.push(\'' + @@ -419,8 +443,18 @@ window.Underscore = { .split("\r").join("\\'") + "');}return p.join('');"); return data ? fn(data) : fn; - } + }; -}; + /*------------------------------- Aliases ----------------------------------*/ -window._ = Underscore; + _.forEach = _.each; + _.inject = _.reduce; + _.filter = _.select; + _.every = _.all; + _.some = _.any; + + /*------------------------- Export for ServerJS ----------------------------*/ + + if (!_.isUndefined(exports)) exports = _; + +})(); From df2742de1767c0ded3cf37dd88d094c7d59bc882 Mon Sep 17 00:00:00 2001 From: Dmitry Baranovskiy Date: Thu, 29 Oct 2009 11:05:45 +1100 Subject: [PATCH 022/533] Some little optimisation. --- underscore.js | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/underscore.js b/underscore.js index 7b48ab101..901f69594 100644 --- a/underscore.js +++ b/underscore.js @@ -26,12 +26,12 @@ if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length) { - for (var i=0; i= result.computed) result = {value : value, computed : computed}; + computed >= result.computed && (result = {value : value, computed : computed}); }); return result.value; }; @@ -164,10 +161,10 @@ // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); - var result; + var result = {computed : Infinity}; _.each(obj, function(value, index) { var computed = iterator ? iterator.call(context, value, index) : value; - if (result == null || computed < result.computed) result = {value : value, computed : computed}; + computed < result.computed && (result = {value : value, computed : computed}); }); return result.value; }; @@ -276,15 +273,16 @@ // item in an array, or -1 if the item is not included in the array. _.indexOf = function(array, item) { if (array.indexOf) return array.indexOf(item); - for (i=0; i=0; i--) if (array[i] === item) return i; + var i = array.length; + while (i--) if (array[i] === item) return i; return -1; }; @@ -403,7 +401,7 @@ // Is a given value a Function? _.isFunction = function(obj) { - return typeof obj == 'function'; + return Object.prototype.toString.call(obj) == '[object Function]'; }; // Is a given variable undefined? @@ -427,8 +425,8 @@ return prefix ? prefix + id : id; }; - // Javascript templating a-la ERB, pilfered from John Resig's - // "Secrets of the Javascript Ninja", page 83. + // JavaScript templating a-la ERB, pilfered from John Resig's + // "Secrets of the JavaScript Ninja", page 83. _.template = function(str, data) { var fn = new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};' + From 79c4c992d86eb8bac5f2dd1477ce4daf67e04c38 Mon Sep 17 00:00:00 2001 From: kriskowal Date: Wed, 28 Oct 2009 17:10:51 -0700 Subject: [PATCH 023/533] Fixed a couple bugs in package.json. Works in Narwhal. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0f55aaa87..3e7ce81f3 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ -// ServerJS package specification. { + "!": "This is a Narwhal package descriptor.", "name": "underscore", "description": "Functional programming aid for Javascript. Works well with jQuery.", "url": "http://documentcloud.github.com/underscore/", @@ -8,5 +8,5 @@ "maintainer": "Kris Kowal ", "contributors": [], "dependencies": [], - "lib", "." + "lib": "." } From 5026224597bfee903b4cadf051d22af0a2ff1620 Mon Sep 17 00:00:00 2001 From: kriskowal Date: Wed, 28 Oct 2009 17:16:59 -0700 Subject: [PATCH 024/533] Fixed an object.hasOwnProperty('hasOwnProperty') bug. --- underscore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 901f69594..fc5630281 100644 --- a/underscore.js +++ b/underscore.js @@ -31,7 +31,7 @@ obj.each(function(value) { iterator.call(context, value, index++); }); } else { var i = 0; - for (var key in obj) if (obj.hasOwnProperty(key)) { + for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) { var value = obj[key], pair = [key, value]; pair.key = key; pair.value = value; From f997a17c8965c9cfa9a19e932666348695712332 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 28 Oct 2009 22:30:52 -0400 Subject: [PATCH 025/533] docs typo fix --- underscore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 7b48ab101..30990a84e 100644 --- a/underscore.js +++ b/underscore.js @@ -2,7 +2,7 @@ // (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the terms of the MIT license. // Portions of Underscore are inspired by or borrowed from Prototype.js, -// Oliver Steele's Functional, And John Resig's Micro-Templating. +// Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore/ From 4f783846de50e8de6d3001eb1ae23b9d041ccf23 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 28 Oct 2009 23:21:24 -0400 Subject: [PATCH 026/533] merged in kriskowal's CommonJS branch and Dmitry Baranovskiy's optimizations --- README | 4 ++-- index.html | 14 +++++++------- package.json | 19 +++++++++---------- test/collections.js | 7 +++++++ underscore.js | 17 ++++++++++------- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/README b/README index 1bf4faf29..e41015cd7 100644 --- a/README +++ b/README @@ -8,10 +8,10 @@ \ \____/ \/___/ -Underscore is a utility-belt library for Javascript that provides +Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built- -in Javascript objects. It's the tie to go along with jQuery's tux. +in JavaScript objects. It's the tie to go along with jQuery's tux. For Docs, License, Tests, and pre-packed downloads, see: http://documentcloud.github.com/underscore/ diff --git a/index.html b/index.html index 11f706290..88c32d94f 100644 --- a/index.html +++ b/index.html @@ -72,11 +72,11 @@

          Underscore is a - utility-belt library for Javascript that provides a lot of the + utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), - but without extending any of the built-in Javascript objects. It's the + but without extending any of the built-in JavaScript objects. It's the tie to go along with jQuery's tux.

          @@ -86,7 +86,7 @@ as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in functions, if present, so - Javascript 1.6 + JavaScript 1.6 compliant browsers will use the native implementations of forEach, map, filter, every, some and indexOf. @@ -171,7 +171,7 @@
          Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is - passed. If list is a Javascript object, a pair with key + passed. If list is a JavaScript object, a pair with key and value properties will be yielded. If the list has an each method of its own, it will be used instead. Delegates to the native forEach function if it exists. @@ -403,7 +403,7 @@ _.last([3, 2, 1]); compact_.compact(array)
          Returns a copy of the array with all falsy values removed. - In Javascript, false, null, 0, "", + In JavaScript, false, null, 0, "", undefined and NaN are all falsy.

          @@ -713,10 +713,10 @@ _.uniqueId('contact_');
                 

          template_.template(templateString, [context])
          - Compiles Javascript templates into functions that can be evaluated + Compiles JavaScript templates into functions that can be evaluated for rendering. Useful for rendering complicated bits of HTML from JSON data sources. Template functions can both interpolate variables, using
          - <%= … %>, as well as execute arbitrary Javascript code, with + <%= … %>, as well as execute arbitrary JavaScript code, with <% … %>. When you evaluate a template function, pass in a context object that has properties corresponding to the template's free variables. If you're writing a one-off, you can pass the context diff --git a/package.json b/package.json index 3e7ce81f3..2cbc8128d 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,11 @@ { - "!": "This is a Narwhal package descriptor.", - "name": "underscore", - "description": "Functional programming aid for Javascript. Works well with jQuery.", - "url": "http://documentcloud.github.com/underscore/", - "keywords": ["util", "functional", "server", "client", "browser"], - "author": "Jeremy Ashkenas ", - "maintainer": "Kris Kowal ", - "contributors": [], - "dependencies": [], - "lib": "." + "name" : "underscore", + "description" : "Functional programming aid for JavaScript. Works well with jQuery.", + "url" : "http://documentcloud.github.com/underscore/", + "keywords" : ["util", "functional", "server", "client", "browser"], + "author" : "Jeremy Ashkenas ", + "maintainer" : "Kris Kowal ", + "contributors" : [], + "dependencies" : [], + "lib" : "." } diff --git a/test/collections.js b/test/collections.js index f97e6e520..8fb684a07 100644 --- a/test/collections.js +++ b/test/collections.js @@ -18,6 +18,13 @@ $(document).ready(function() { answers = []; _.forEach([1, 2, 3], function(num){ answers.push(num); }); equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); + + answers = []; + var obj = {one : 1, two : 2, three : 3}; + obj.constructor.prototype.four = 4; + _.each(obj, function(pair){ answers.push(pair.key); }); + equals(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.'); + delete obj.constructor.prototype.four; }); test('collections: map', function() { diff --git a/underscore.js b/underscore.js index c89842dd9..f5b8610c2 100644 --- a/underscore.js +++ b/underscore.js @@ -12,6 +12,8 @@ var previousUnderscore = root._; + var identity = function(value) { return value; }; + var _ = root._ = {}; _.VERSION = '0.2.0'; @@ -26,7 +28,7 @@ if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length) { - for (var i=0, ii = obj.length; i> 1; @@ -273,7 +276,7 @@ // item in an array, or -1 if the item is not included in the array. _.indexOf = function(array, item) { if (array.indexOf) return array.indexOf(item); - for (i=0, ii=array.length; i Date: Wed, 28 Oct 2009 23:53:40 -0400 Subject: [PATCH 027/533] elaborate underscore initialization so that it works seamlessly on CommonJS, as well as in the browser --- test/collections.js | 6 +++++- underscore.js | 28 +++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/test/collections.js b/test/collections.js index 8fb684a07..7f28a7705 100644 --- a/test/collections.js +++ b/test/collections.js @@ -12,9 +12,13 @@ $(document).ready(function() { equals(answer, 2, 'the loop broke in the middle'); var answers = []; - _.each([1, 2, 3], function(num) { answers.push(num * this.multiplier);}, {multiplier : 5}); + _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); + answers = []; + _.each("moe", function(letter){ answers.push(letter); }); + equals(answers.join(', '), 'm, o, e', 'iterates over the letters in strings'); + answers = []; _.forEach([1, 2, 3], function(num){ answers.push(num); }); equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); diff --git a/underscore.js b/underscore.js index f5b8610c2..8fa0379f4 100644 --- a/underscore.js +++ b/underscore.js @@ -8,15 +8,25 @@ (function() { - var root = (typeof window != 'undefined') ? window : exports; + /*------------------------- Baseline setup ---------------------------------*/ - var previousUnderscore = root._; + // Are we running in CommonJS or in the browser? + var commonJS = (typeof window === 'undefined' && typeof exports !== 'undefined'); + // Save the previous value of the "_" variable. + var previousUnderscore = commonJS ? null : window._; + + // Keep the identity function around for default iterators. var identity = function(value) { return value; }; - - var _ = root._ = {}; - _.VERSION = '0.2.0'; + // Create a safe reference to the Underscore object for the functions below. + var _ = {}; + + // Export the Underscore object for CommonJS, assign it globally otherwise. + commonJS ? _ = exports : window._ = _; + + // Current version. + _.VERSION = '0.2.1'; /*------------------------ Collection Functions: ---------------------------*/ @@ -417,7 +427,7 @@ // Run Underscore.js in noConflict mode, returning the '_' variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { - root._ = previousUnderscore; + if (!commonJS) window._ = previousUnderscore; return this; }; @@ -452,10 +462,6 @@ _.inject = _.reduce; _.filter = _.select; _.every = _.all; - _.some = _.any; - - /*------------------------- Export for ServerJS ----------------------------*/ - - if (typeof exports != 'undefined') exports = _; + _.some = _.any; })(); From e381f7b626107165b86b7549b90b32245e934b8a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 29 Oct 2009 00:04:00 -0400 Subject: [PATCH 028/533] updating docs and minified version for 0.3.0 --- index.html | 13 +++++++++++-- underscore-min.js | 2 +- underscore.js | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 88c32d94f..99380fece 100644 --- a/index.html +++ b/index.html @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.2.0)Development Version (0.3.0) 16kb, Uncompressed with Comments
          Production Version (0.2.0)Production Version (0.3.0) 4kb, Packed and Gzipped
          @@ -735,6 +735,15 @@ _.template(list, {people : ['moe', 'curly', 'larry']});

          Change Log

          +

          + 0.3.0
          + Added Dmitry Baranovskiy's + comprehensive optimizations, merged in + Kris Kowal's patches to make Underscore + CommonJS and + Narwhal compliant. +

          +

          0.2.0
          Added compose and lastIndexOf, renamed inject to diff --git a/underscore-min.js b/underscore-min.js index 98d0437b5..65150df82 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var a=(typeof window!="undefined")?window:exports;var c=a._;var b=a._={};b.VERSION="0.2.0";b.each=function(g,j,d){var k=0;try{if(g.forEach){g.forEach(j,d)}else{if(g.length){for(var h=0;h=d.computed){d={value:k,computed:j}}});return d.value};b.min=function(g,f,e){if(!f&&b.isArray(g)){return Math.min.apply(Math,g)}var d;b.each(g,function(k,h){var j=f?f.call(e,k,h):k;if(d==null||jg?1:0}),"value")};b.sortedIndex=function(j,h,f){f=f||function(k){return k};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;i--){if(e[i]===d){return i}}return -1};b.bind=function(f,e){if(!e){return f}var d=b.toArray(arguments).slice(2);return function(){var g=d.concat(b.toArray(arguments));return f.apply(e,g)}};b.bindAll=function(){var d=b.toArray(arguments);var e=d.pop();b.each(d,function(f){e[f]=b.bind(e[f],e)})};b.delay=function(e,f){var d=b.toArray(arguments).slice(2);return setTimeout(function(){return e.apply(e,d)},f)};b.defer=function(d){return b.delay.apply(b,[d,1].concat(b.toArray(arguments).slice(1)))};b.wrap=function(d,e){return function(){var f=[d].concat(b.toArray(arguments));return e.apply(e,f)}};b.compose=function(){var d=b.toArray(arguments);return function(){for(var e=d.length-1;e>=0;e--){arguments=[d[e].apply(this,arguments)]}return arguments[0]}};b.keys=function(d){return b.pluck(d,"key")};b.values=function(d){return b.pluck(d,"value")};b.extend=function(d,f){for(var e in f){d[e]=f[e]}return d};b.clone=function(d){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 typeof d=="function"};b.isUndefined=function(d){return typeof d=="undefined"};b.noConflict=function(){a._=c;return this};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.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any;if(!b.isUndefined(exports)){exports=b}})(); \ No newline at end of file +(function(){var d=(typeof window==="undefined"&&typeof exports!=="undefined");var c=d?null:window._;var a=function(e){return e};var b={};d?b=exports:window._=b;b.VERSION="0.3.0";b.each=function(j,m,f){var n=0;try{if(j.forEach){j.forEach(m,f)}else{if(j.length){for(var k=0,g=j.length;k=e.computed&&(e={value:m,computed:k})});return e.value};b.min=function(h,g,f){if(!g&&b.isArray(h)){return Math.min.apply(Math,h)}var e={computed:Infinity};b.each(h,function(m,j){var k=g?g.call(f,m,j):m;kh?1:0}),"value")};b.sortedIndex=function(k,j,g){g=g||a;var e=0,h=k.length;while(e>1;g(k[f])=0})})};b.zip=function(){var e=b.toArray(arguments);var h=b.max(b.pluck(e,"length"));var g=new Array(h);for(var f=0;f=0;f--){arguments=[e[f].apply(this,arguments)]}return arguments[0]}};b.keys=function(e){return b.pluck(e,"key")};b.values=function(e){return b.pluck(e,"value")};b.extend=function(e,g){for(var f in g){e[f]=g[f]}return e};b.clone=function(e){return b.extend({},e)};b.isEqual=function(f,e){if(f===e){return true}var j=typeof(f),m=typeof(e);if(j!=m){return false}if(f==e){return true}if(f.isEqual){return f.isEqual(e)}if(j!=="object"){return false}var g=b.keys(f),k=b.keys(e);if(g.length!=k.length){return false}for(var h in f){if(!b.isEqual(f[h],e[h])){return false}}return true};b.isElement=function(e){return !!(e&&e.nodeType==1)};b.isArray=function(e){return Object.prototype.toString.call(e)=="[object Array]"};b.isFunction=function(e){return Object.prototype.toString.call(e)=="[object Function]"};b.isUndefined=function(e){return typeof e=="undefined"};b.noConflict=function(){if(!d){window._=c}return this};b.uniqueId=function(e){var f=this._idCounter=(this._idCounter||0)+1;return e?e+f:f};b.template=function(g,f){var e=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+g.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 f?e(f):e};b.forEach=b.each;b.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 8fa0379f4..9c050fc80 100644 --- a/underscore.js +++ b/underscore.js @@ -26,7 +26,7 @@ commonJS ? _ = exports : window._ = _; // Current version. - _.VERSION = '0.2.1'; + _.VERSION = '0.3.0'; /*------------------------ Collection Functions: ---------------------------*/ From 8e7b8d2dea2ec194f024f47b9eef0f90b6b1a0a5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 29 Oct 2009 10:26:16 -0400 Subject: [PATCH 029/533] started passing in the collection as the third argument to _.each iterators (Issue #1) --- index.html | 4 +++- test/collections.js | 4 ++++ underscore.js | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 99380fece..f032259a6 100644 --- a/index.html +++ b/index.html @@ -171,7 +171,9 @@
          Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is - passed. If list is a JavaScript object, a pair with key + passed. Each invocation of iterator is called with three arguments, + element, index, and the list. + If list is a JavaScript object, a pair with key and value properties will be yielded. If the list has an each method of its own, it will be used instead. Delegates to the native forEach function if it exists. diff --git a/test/collections.js b/test/collections.js index 7f28a7705..69fe9ec47 100644 --- a/test/collections.js +++ b/test/collections.js @@ -29,6 +29,10 @@ $(document).ready(function() { _.each(obj, function(pair){ answers.push(pair.key); }); equals(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.'); delete obj.constructor.prototype.four; + + answer = null; + _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); + ok(answer, 'can reference the original collection from inside the iterator'); }); test('collections: map', function() { diff --git a/underscore.js b/underscore.js index 9c050fc80..90cafc284 100644 --- a/underscore.js +++ b/underscore.js @@ -38,16 +38,16 @@ if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length) { - for (var i=0, l = obj.length; i Date: Thu, 29 Oct 2009 10:46:53 -0400 Subject: [PATCH 030/533] Issue #2 -- each now calls iterator with (value, key, list) when iterating over javascript objects --- index.html | 7 +++---- test/collections.js | 4 ++-- underscore.js | 24 +++++++++++++----------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/index.html b/index.html index f032259a6..33716be85 100644 --- a/index.html +++ b/index.html @@ -171,10 +171,9 @@
          Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is - passed. Each invocation of iterator is called with three arguments, - element, index, and the list. - If list is a JavaScript object, a pair with key - and value properties will be yielded. If the list has an each + passed. Each invocation of iterator is called with three arguments: + (element, index, list). If list is a JavaScript object, iterator's + arguments will be (value, key, list). If the list has an each method of its own, it will be used instead. Delegates to the native forEach function if it exists.

          diff --git a/test/collections.js b/test/collections.js index 69fe9ec47..3ef2ccba3 100644 --- a/test/collections.js +++ b/test/collections.js @@ -26,7 +26,7 @@ $(document).ready(function() { answers = []; var obj = {one : 1, two : 2, three : 3}; obj.constructor.prototype.four = 4; - _.each(obj, function(pair){ answers.push(pair.key); }); + _.each(obj, function(value, key){ answers.push(key); }); equals(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.'); delete obj.constructor.prototype.four; @@ -136,7 +136,7 @@ $(document).ready(function() { ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); var numbers = _.toArray({one : 1, two : 2, three : 3}); - equals(_.pluck(numbers, '0').join(', '), 'one, two, three', 'object flattened into array'); + equals(numbers.join(', '), '1, 2, 3', 'object flattened into array'); }); test('collections: size', function() { diff --git a/underscore.js b/underscore.js index 90cafc284..f9c642062 100644 --- a/underscore.js +++ b/underscore.js @@ -42,12 +42,8 @@ } else if (obj.each) { obj.each(function(value) { iterator.call(context, value, index++, obj); }); } else { - var i = 0; for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) { - var value = obj[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator.call(context, pair, i++, obj); + iterator.call(context, obj[key], key, obj); } } } catch(e) { @@ -61,8 +57,8 @@ _.map = function(obj, iterator, context) { if (obj && obj.map) return obj.map(iterator, context); var results = []; - _.each(obj, function(value, index) { - results.push(iterator.call(context, value, index)); + _.each(obj, function(value, index, list) { + results.push(iterator.call(context, value, index, list)); }); return results; }; @@ -137,8 +133,8 @@ _.include = function(obj, target) { if (_.isArray(obj)) return _.indexOf(obj, target) != -1; var found = false; - _.each(obj, function(pair) { - if (found = pair.value === target) { + _.each(obj, function(value) { + if (found = value === target) { throw '__break__'; } }); @@ -361,12 +357,18 @@ // Retrieve the names of an object's properties. _.keys = function(obj) { - return _.pluck(obj, 'key'); + return _.reduce(obj, [], function(memo, value, key) { + memo.push(key); + return memo; + }); }; // Retrieve the values of an object's properties. _.values = function(obj) { - return _.pluck(obj, 'value'); + return _.reduce(obj, [], function(memo, value) { + memo.push(value); + return memo; + }); }; // Extend a given object with all of the properties in a source object. From 1d8420af003c2b429a5bae7103cff648c591cd5d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 29 Oct 2009 10:53:23 -0400 Subject: [PATCH 031/533] started passing in 'list' as the third argument to all the iterators in underscore --- underscore.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/underscore.js b/underscore.js index f9c642062..7a3d43646 100644 --- a/underscore.js +++ b/underscore.js @@ -66,8 +66,8 @@ // Reduce builds up a single result from a list of values. Also known as // inject, or foldl. _.reduce = function(obj, memo, iterator, context) { - _.each(obj, function(value, index) { - memo = iterator.call(context, memo, value, index); + _.each(obj, function(value, index, list) { + memo = iterator.call(context, memo, value, index, list); }); return memo; }; @@ -75,8 +75,8 @@ // Return the first value which passes a truth test. _.detect = function(obj, iterator, context) { var result; - _.each(obj, function(value, index) { - if (iterator.call(context, value, index)) { + _.each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { result = value; throw '__break__'; } @@ -89,8 +89,8 @@ _.select = function(obj, iterator, context) { if (obj.filter) return obj.filter(iterator, context); var results = []; - _.each(obj, function(value, index) { - iterator.call(context, value, index) && results.push(value); + _.each(obj, function(value, index, list) { + iterator.call(context, value, index, list) && results.push(value); }); return results; }; @@ -98,8 +98,8 @@ // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { var results = []; - _.each(obj, function(value, index) { - !iterator.call(context, value, index) && results.push(value); + _.each(obj, function(value, index, list) { + !iterator.call(context, value, index, list) && results.push(value); }); return results; }; @@ -110,8 +110,8 @@ iterator = iterator || identity; if (obj.every) return obj.every(iterator, context); var result = true; - _.each(obj, function(value, index) { - if (!(result = result && iterator.call(context, value, index))) throw '__break__'; + _.each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) throw '__break__'; }); return result; }; @@ -122,8 +122,8 @@ iterator = iterator || identity; if (obj.some) return obj.some(iterator, context); var result = false; - _.each(obj, function(value, index) { - if (result = iterator.call(context, value, index)) throw '__break__'; + _.each(obj, function(value, index, list) { + if (result = iterator.call(context, value, index, list)) throw '__break__'; }); return result; }; @@ -160,8 +160,8 @@ _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); var result = {computed : -Infinity}; - _.each(obj, function(value, index) { - var computed = iterator ? iterator.call(context, value, index) : value; + _.each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; computed >= result.computed && (result = {value : value, computed : computed}); }); return result.value; @@ -171,8 +171,8 @@ _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); var result = {computed : Infinity}; - _.each(obj, function(value, index) { - var computed = iterator ? iterator.call(context, value, index) : value; + _.each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); }); return result.value; @@ -180,10 +180,10 @@ // Sort the object's values by a criteria produced by an iterator. _.sortBy = function(obj, iterator, context) { - return _.pluck(_.map(obj, function(value, index) { + return _.pluck(_.map(obj, function(value, index, list) { return { value : value, - criteria : iterator.call(context, value, index) + criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; From 9b1da9b2581ecfe549abeb443f81e4feca76bbbc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 29 Oct 2009 11:03:53 -0400 Subject: [PATCH 032/533] optimized keys, values, and pluck --- index.html | 4 ++-- underscore.js | 16 ++++------------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index 33716be85..883780995 100644 --- a/index.html +++ b/index.html @@ -298,8 +298,8 @@ _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');

          pluck_.pluck(list, propertyName)
          - An optimized version of what is perhaps the most common use-case for - map: returning a list of property values. + An convenient version of what is perhaps the most common use-case for + map: extracting a list of property values.

           var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
          diff --git a/underscore.js b/underscore.js
          index 7a3d43646..444ee010f 100644
          --- a/underscore.js
          +++ b/underscore.js
          @@ -149,11 +149,9 @@
               });
             };
             
          -  // Optimized version of a common use case of map: fetching a property.
          +  // Convenience version of a common use case of map: fetching a property.
             _.pluck = function(obj, key) {
          -    var results = [];
          -    _.each(obj, function(value){ results.push(value[key]); });
          -    return results;
          +    return _.map(obj, function(value){ return value[key]; });
             };
             
             // Return the maximum item or (item-based computation).
          @@ -357,18 +355,12 @@
             
             // Retrieve the names of an object's properties.
             _.keys = function(obj) {
          -    return _.reduce(obj, [], function(memo, value, key) { 
          -      memo.push(key);
          -      return memo;
          -    });
          +    return _.map(obj, function(value, key){ return key; });
             };
             
             // Retrieve the values of an object's properties.
             _.values = function(obj) {
          -    return _.reduce(obj, [], function(memo, value) { 
          -      memo.push(value); 
          -      return memo; 
          -    });
          +    return _.map(obj, identity);
             };
             
             // Extend a given object with all of the properties in a source object.
          
          From 4d09a85bae8691876c4c767451449daac88eac82 Mon Sep 17 00:00:00 2001
          From: Jeremy Ashkenas 
          Date: Thu, 29 Oct 2009 11:12:41 -0400
          Subject: [PATCH 033/533] version 0.3.1 is on the books
          
          ---
           index.html        | 12 ++++++++++--
           underscore-min.js |  2 +-
           underscore.js     |  2 +-
           3 files changed, 12 insertions(+), 4 deletions(-)
          
          diff --git a/index.html b/index.html
          index 883780995..74f4b1662 100644
          --- a/index.html
          +++ b/index.html
          @@ -107,11 +107,11 @@
               

          - + - +
          Development Version (0.3.0)Development Version (0.3.1) 16kb, Uncompressed with Comments
          Production Version (0.3.0)Production Version (0.3.1) 4kb, Packed and Gzipped
          @@ -736,6 +736,14 @@ _.template(list, {people : ['moe', 'curly', 'larry']});

          Change Log

          +

          + 0.3.1
          + 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. +

          +

          0.3.0
          Added Dmitry Baranovskiy's diff --git a/underscore-min.js b/underscore-min.js index 65150df82..56898bab1 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var d=(typeof window==="undefined"&&typeof exports!=="undefined");var c=d?null:window._;var a=function(e){return e};var b={};d?b=exports:window._=b;b.VERSION="0.3.0";b.each=function(j,m,f){var n=0;try{if(j.forEach){j.forEach(m,f)}else{if(j.length){for(var k=0,g=j.length;k=e.computed&&(e={value:m,computed:k})});return e.value};b.min=function(h,g,f){if(!g&&b.isArray(h)){return Math.min.apply(Math,h)}var e={computed:Infinity};b.each(h,function(m,j){var k=g?g.call(f,m,j):m;kh?1:0}),"value")};b.sortedIndex=function(k,j,g){g=g||a;var e=0,h=k.length;while(e>1;g(k[f])=0})})};b.zip=function(){var e=b.toArray(arguments);var h=b.max(b.pluck(e,"length"));var g=new Array(h);for(var f=0;f=0;f--){arguments=[e[f].apply(this,arguments)]}return arguments[0]}};b.keys=function(e){return b.pluck(e,"key")};b.values=function(e){return b.pluck(e,"value")};b.extend=function(e,g){for(var f in g){e[f]=g[f]}return e};b.clone=function(e){return b.extend({},e)};b.isEqual=function(f,e){if(f===e){return true}var j=typeof(f),m=typeof(e);if(j!=m){return false}if(f==e){return true}if(f.isEqual){return f.isEqual(e)}if(j!=="object"){return false}var g=b.keys(f),k=b.keys(e);if(g.length!=k.length){return false}for(var h in f){if(!b.isEqual(f[h],e[h])){return false}}return true};b.isElement=function(e){return !!(e&&e.nodeType==1)};b.isArray=function(e){return Object.prototype.toString.call(e)=="[object Array]"};b.isFunction=function(e){return Object.prototype.toString.call(e)=="[object Function]"};b.isUndefined=function(e){return typeof e=="undefined"};b.noConflict=function(){if(!d){window._=c}return this};b.uniqueId=function(e){var f=this._idCounter=(this._idCounter||0)+1;return e?e+f:f};b.template=function(g,f){var e=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+g.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 f?e(f):e};b.forEach=b.each;b.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any})(); \ No newline at end of file +(function(){var d=(typeof window==="undefined"&&typeof exports!=="undefined");var c=d?null:window._;var a=function(e){return e};var b={};d?b=exports:window._=b;b.VERSION="0.3.1";b.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=e.computed&&(e={value:n,computed:k})});return e.value};b.min=function(h,g,f){if(!g&&b.isArray(h)){return Math.min.apply(Math,h)}var e={computed:Infinity};b.each(h,function(n,j,m){var k=g?g.call(f,n,j,m):n;kh?1:0}),"value")};b.sortedIndex=function(k,j,g){g=g||a;var e=0,h=k.length;while(e>1;g(k[f])=0})})};b.zip=function(){var e=b.toArray(arguments);var h=b.max(b.pluck(e,"length"));var g=new Array(h);for(var f=0;f=0;f--){arguments=[e[f].apply(this,arguments)]}return arguments[0]}};b.keys=function(e){return b.map(e,function(g,f){return f})};b.values=function(e){return b.map(e,a)};b.extend=function(e,g){for(var f in g){e[f]=g[f]}return e};b.clone=function(e){return b.extend({},e)};b.isEqual=function(f,e){if(f===e){return true}var j=typeof(f),m=typeof(e);if(j!=m){return false}if(f==e){return true}if(f.isEqual){return f.isEqual(e)}if(j!=="object"){return false}var g=b.keys(f),k=b.keys(e);if(g.length!=k.length){return false}for(var h in f){if(!b.isEqual(f[h],e[h])){return false}}return true};b.isElement=function(e){return !!(e&&e.nodeType==1)};b.isArray=function(e){return Object.prototype.toString.call(e)=="[object Array]"};b.isFunction=function(e){return Object.prototype.toString.call(e)=="[object Function]"};b.isUndefined=function(e){return typeof e=="undefined"};b.noConflict=function(){if(!d){window._=c}return this};b.uniqueId=function(e){var f=this._idCounter=(this._idCounter||0)+1;return e?e+f:f};b.template=function(g,f){var e=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+g.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 f?e(f):e};b.forEach=b.each;b.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 444ee010f..45b0ddddb 100644 --- a/underscore.js +++ b/underscore.js @@ -26,7 +26,7 @@ commonJS ? _ = exports : window._ = _; // Current version. - _.VERSION = '0.3.0'; + _.VERSION = '0.3.1'; /*------------------------ Collection Functions: ---------------------------*/ From d2d1285e26a206278ae9f711a589b5e49f54c60e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 29 Oct 2009 14:45:56 -0400 Subject: [PATCH 034/533] version 0.3.2, with 'identity', and Rhino support --- index.html | 29 +++++++++++++++++++++++++---- underscore-min.js | 2 +- underscore.js | 32 +++++++++++++++++--------------- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index 74f4b1662..ebbf82dbc 100644 --- a/index.html +++ b/index.html @@ -81,7 +81,7 @@

          - Underscore provides 44-odd functions that support both the usual + Underscore provides 45-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.3.1)Development Version (0.3.2) 16kb, Uncompressed with Comments
          Production Version (0.3.1)Production Version (0.3.2) 4kb, Packed and Gzipped
          @@ -158,7 +158,8 @@ Utility
          noConflict, - uniqueId, template + identity, uniqueId, + template

          @@ -700,6 +701,19 @@ _.isUndefined(window.missingVariable);
           var underscore = _.noConflict();
          +

          + identity_.identity(value) +
          + Returns the same value that is used as the argument. In math: + f(x) = x
          + This function looks useless, but is used throughout Underscore as + a default iterator. +

          +
          +var moe = {name : 'moe'};
          +moe === _.identity(moe);
          +=> true
          +

          uniqueId_.uniqueId([prefix])
          @@ -736,6 +750,13 @@ _.template(list, {people : ['moe', 'curly', 'larry']});

          Change Log

          +

          + 0.3.2
          + Now runs on stock Rhino + interpreters with: load("underscore.js"). + Added identity as a utility function. +

          +

          0.3.1
          All iterators are now passed in the original collection as their third diff --git a/underscore-min.js b/underscore-min.js index 56898bab1..ee3af5177 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var d=(typeof window==="undefined"&&typeof exports!=="undefined");var c=d?null:window._;var a=function(e){return e};var b={};d?b=exports:window._=b;b.VERSION="0.3.1";b.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=e.computed&&(e={value:n,computed:k})});return e.value};b.min=function(h,g,f){if(!g&&b.isArray(h)){return Math.min.apply(Math,h)}var e={computed:Infinity};b.each(h,function(n,j,m){var k=g?g.call(f,n,j,m):n;kh?1:0}),"value")};b.sortedIndex=function(k,j,g){g=g||a;var e=0,h=k.length;while(e>1;g(k[f])=0})})};b.zip=function(){var e=b.toArray(arguments);var h=b.max(b.pluck(e,"length"));var g=new Array(h);for(var f=0;f=0;f--){arguments=[e[f].apply(this,arguments)]}return arguments[0]}};b.keys=function(e){return b.map(e,function(g,f){return f})};b.values=function(e){return b.map(e,a)};b.extend=function(e,g){for(var f in g){e[f]=g[f]}return e};b.clone=function(e){return b.extend({},e)};b.isEqual=function(f,e){if(f===e){return true}var j=typeof(f),m=typeof(e);if(j!=m){return false}if(f==e){return true}if(f.isEqual){return f.isEqual(e)}if(j!=="object"){return false}var g=b.keys(f),k=b.keys(e);if(g.length!=k.length){return false}for(var h in f){if(!b.isEqual(f[h],e[h])){return false}}return true};b.isElement=function(e){return !!(e&&e.nodeType==1)};b.isArray=function(e){return Object.prototype.toString.call(e)=="[object Array]"};b.isFunction=function(e){return Object.prototype.toString.call(e)=="[object Function]"};b.isUndefined=function(e){return typeof e=="undefined"};b.noConflict=function(){if(!d){window._=c}return this};b.uniqueId=function(e){var f=this._idCounter=(this._idCounter||0)+1;return e?e+f:f};b.template=function(g,f){var e=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+g.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 f?e(f):e};b.forEach=b.each;b.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any})(); \ No newline at end of file +(function(){var a=this;var c=a._;var b=a._={};if(typeof exports!=="undefined"){b=exports}b.VERSION="0.3.2";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){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.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 45b0ddddb..fee281f8d 100644 --- a/underscore.js +++ b/underscore.js @@ -10,23 +10,20 @@ /*------------------------- Baseline setup ---------------------------------*/ - // Are we running in CommonJS or in the browser? - var commonJS = (typeof window === 'undefined' && typeof exports !== 'undefined'); + // Establish the root object, "window" in the browser, or "global" on the server. + var root = this; // Save the previous value of the "_" variable. - var previousUnderscore = commonJS ? null : window._; - - // Keep the identity function around for default iterators. - var identity = function(value) { return value; }; + var previousUnderscore = root._; // Create a safe reference to the Underscore object for the functions below. - var _ = {}; + var _ = root._ = {}; - // Export the Underscore object for CommonJS, assign it globally otherwise. - commonJS ? _ = exports : window._ = _; + // Export the Underscore object for CommonJS. + if (typeof exports !== 'undefined') _ = exports; // Current version. - _.VERSION = '0.3.1'; + _.VERSION = '0.3.2'; /*------------------------ Collection Functions: ---------------------------*/ @@ -107,7 +104,7 @@ // 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) { - iterator = iterator || identity; + iterator = iterator || _.identity; if (obj.every) return obj.every(iterator, context); var result = true; _.each(obj, function(value, index, list) { @@ -119,7 +116,7 @@ // 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) { - iterator = iterator || identity; + iterator = iterator || _.identity; if (obj.some) return obj.some(iterator, context); var result = false; _.each(obj, function(value, index, list) { @@ -192,7 +189,7 @@ // Use a comparator function to figure out at what index an object should // be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator) { - iterator = iterator || identity; + iterator = iterator || _.identity; var low = 0, high = array.length; while (low < high) { var mid = (low + high) >> 1; @@ -360,7 +357,7 @@ // Retrieve the values of an object's properties. _.values = function(obj) { - return _.map(obj, identity); + return _.map(obj, _.identity); }; // Extend a given object with all of the properties in a source object. @@ -421,10 +418,15 @@ // Run Underscore.js in noConflict mode, returning the '_' variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { - if (!commonJS) window._ = previousUnderscore; + root._ = previousUnderscore; return this; }; + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. _.uniqueId = function(prefix) { From e9bc165bc33f086ea99fe44db83e228f44481c71 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 29 Oct 2009 14:50:48 -0400 Subject: [PATCH 035/533] adding an identity test (silly, I know) to utility tests --- test/utility.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/utility.js b/test/utility.js index af9680bb5..e40755be5 100644 --- a/test/utility.js +++ b/test/utility.js @@ -9,6 +9,11 @@ $(document).ready(function() { equals(intersection.join(', '), '1, 2', 'but the intersection function still works'); window._ = underscore; }); + + test("utility: identity", function() { + var moe = {name : 'moe'}; + equals(_.identity(moe), moe, 'moe is the same has his identity'); + }); test("utility: uniqueId", function() { var ids = [], i = 0; From eca085a9d93deab4cc58e8ab7f9304f26f3fe84f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 29 Oct 2009 14:58:50 -0400 Subject: [PATCH 036/533] typo --- test/utility.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utility.js b/test/utility.js index e40755be5..b58844294 100644 --- a/test/utility.js +++ b/test/utility.js @@ -12,7 +12,7 @@ $(document).ready(function() { test("utility: identity", function() { var moe = {name : 'moe'}; - equals(_.identity(moe), moe, 'moe is the same has his identity'); + equals(_.identity(moe), moe, 'moe is the same as his identity'); }); test("utility: uniqueId", function() { From cb38c6ae6358693725f816e9152034f236d08755 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 30 Oct 2009 09:40:19 -0400 Subject: [PATCH 037/533] adding a reduceRight (it's in JS 1.8), and aliasing foldl and foldr --- index.html | 27 ++++++++++++++++++++++----- test/collections.js | 9 +++++++++ underscore.js | 18 ++++++++++++++++-- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index ebbf82dbc..9850daf9d 100644 --- a/index.html +++ b/index.html @@ -123,10 +123,14 @@ Collections
          each, map, - reduce, detect, select, reject, all, - any, include, invoke, pluck, max, - min, sortBy, sortedIndex, toArray, - size + reduce, reduceRight, + detect, select, + reject, all, + any, include, + invoke, pluck, + max, min, + sortBy, sortedIndex, + toArray, size

          @@ -195,7 +199,7 @@ _.map([1, 2, 3], function(num){ return num * 3 });

          reduce_.reduce(list, memo, iterator, [context]) - Alias: inject + Aliases: inject, foldl
          Also known as inject and foldl, reduce boils down a list of values into a single value. Memo is the initial state @@ -205,6 +209,19 @@ _.map([1, 2, 3], function(num){ return num * 3 });

           var sum = _.reduce([1, 2, 3], 0, function(memo, num){ return memo + num });
           => 6
          +
          + +

          + reduceRight_.reduceRight(list, memo, iterator, [context]) + Alias: foldr +
          + The right-associative version of reduce. Delegates to the + JavaScript 1.8 version of reduceRight, if it exists. +

          +
          +var list = [[0, 1], [2, 3], [4, 5]];
          +var flat = _.reduceRight(list, [], function(a, b) { return a.concat(b); });
          +=> [4, 5, 2, 3, 0, 1]
           

          diff --git a/test/collections.js b/test/collections.js index 3ef2ccba3..a55d02e88 100644 --- a/test/collections.js +++ b/test/collections.js @@ -47,10 +47,19 @@ $(document).ready(function() { var sum = _.reduce([1, 2, 3], 0, function(sum, num){ return sum + num; }); equals(sum, 6, 'can sum up an array'); + var context = {multiplier : 3}; + sum = _.reduce([1, 2, 3], 0, function(sum, num){ return sum + num * this.multiplier; }, context); + equals(sum, 18, 'can reduce with a context object'); + sum = _.inject([1, 2, 3], 0, function(sum, num){ return sum + num; }); equals(sum, 6, 'aliased as "inject"'); }); + test('collections: reduceRight', function() { + var list = _.foldr([1, 2, 3], '', function(memo, num){ return memo + num; }); + equals(list, '321', 'can perform right folds'); + }); + test('collections: detect', function() { var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; }); equals(result, 2, 'found the first "2" and broke the loop'); diff --git a/underscore.js b/underscore.js index fee281f8d..9b2dfb84b 100644 --- a/underscore.js +++ b/underscore.js @@ -61,14 +61,26 @@ }; // Reduce builds up a single result from a list of values. Also known as - // inject, or foldl. + // inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. _.reduce = function(obj, memo, iterator, context) { + if (obj && obj.reduce) return obj.reduce(_.bind(iterator, context), memo); _.each(obj, function(value, index, list) { memo = iterator.call(context, memo, value, index, list); }); return memo; }; + // The right-associative version of reduce, also known as foldr. Uses + // JavaScript 1.8's version of reduceRight, if available. + _.reduceRight = function(obj, memo, iterator, context) { + if (obj && obj.reduceRight) return obj.reduceRight(_.bind(iterator, context), memo); + var reversed = _.clone(_.toArray(obj)).reverse(); + _.each(reversed, function(value, index) { + memo = iterator.call(context, memo, value, index, obj); + }); + return memo; + }; + // Return the first value which passes a truth test. _.detect = function(obj, iterator, context) { var result; @@ -368,6 +380,7 @@ // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { + if (_.isArray(obj)) return obj.slice(0); return _.extend({}, obj); }; @@ -455,7 +468,8 @@ /*------------------------------- Aliases ----------------------------------*/ _.forEach = _.each; - _.inject = _.reduce; + _.foldl = _.inject = _.reduce; + _.foldr = _.reduceRight; _.filter = _.select; _.every = _.all; _.some = _.any; From d4a5ed6a733c75c6b1813e17a5ff5063ac087db7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 31 Oct 2009 08:26:03 -0400 Subject: [PATCH 038/533] version 0.3.3 is on the books -- with reduceRight --- index.html | 18 +++++++++++++----- underscore-min.js | 2 +- underscore.js | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 9850daf9d..a25d68a86 100644 --- a/index.html +++ b/index.html @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.3.2)Development Version (0.3.3) 16kb, Uncompressed with Comments
          Production Version (0.3.2)Production Version (0.3.3) 4kb, Packed and Gzipped
          @@ -216,7 +216,9 @@ var sum = _.reduce([1, 2, 3], 0, function(memo, num){ return memo + num }); Alias: foldr
          The right-associative version of reduce. Delegates to the - JavaScript 1.8 version of reduceRight, if it exists. + JavaScript 1.8 version of reduceRight, if it exists. Foldr + is not as useful in JavaScript as it would be in a language with lazy + evaluation.

           var list = [[0, 1], [2, 3], [4, 5]];
          @@ -229,8 +231,8 @@ var flat = _.reduceRight(list, [], function(a, b) { return a.concat(b); });
                   
          Looks through each value in the list, returning the first one that passes a truth test (iterator). The function returns as - soon as it finds the first acceptable element, and doesn't continue to - traverse the list. + soon as it finds an acceptable element, and doesn't traverse the + entire list.

           var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
          @@ -767,6 +769,12 @@ _.template(list, {people : ['moe', 'curly', 'larry']});
           
                 

          Change Log

          +

          + 0.3.3
          + Added the JavaScript 1.8 function reduceRight. Aliased it + as foldr, and aliased reduce as foldl. +

          +

          0.3.2
          Now runs on stock Rhino diff --git a/underscore-min.js b/underscore-min.js index ee3af5177..ec7f39785 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.2";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){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.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any})(); \ No newline at end of file +(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 diff --git a/underscore.js b/underscore.js index 9b2dfb84b..e198f6a4b 100644 --- a/underscore.js +++ b/underscore.js @@ -23,7 +23,7 @@ if (typeof exports !== 'undefined') _ = exports; // Current version. - _.VERSION = '0.3.2'; + _.VERSION = '0.3.3'; /*------------------------ Collection Functions: ---------------------------*/ From 51298c78ca730dd05f3b76c213d5a6d88c229a5e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 31 Oct 2009 08:30:49 -0400 Subject: [PATCH 039/533] looks like gzipped, it's only 1.9k --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index a25d68a86..256824685 100644 --- a/index.html +++ b/index.html @@ -112,7 +112,7 @@ Production Version (0.3.3) - 4kb, Packed and Gzipped + 2kb, Packed and Gzipped

          From 4f0afda61cae565fbdccb8f20493b401b4e4521d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 7 Nov 2009 12:39:59 -0500 Subject: [PATCH 040/533] adding OO-style object wrapping (thanks macournoyer) -- now you can to _(array).each(); --- test/arrays.js | 2 ++ test/collections.js | 9 ++++++++- test/functions.js | 9 ++++++--- test/objects.js | 1 + test/speed.js | 6 ++++++ underscore.js | 32 +++++++++++++++++++++++++++----- 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index dd54c7266..d069d9d62 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -4,6 +4,7 @@ $(document).ready(function() { test("arrays: first", function() { equals(_.first([1,2,3]), 1, 'can pull out the first element of an array'); + equals(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"'); }); test("arrays: last", function() { @@ -35,6 +36,7 @@ $(document).ready(function() { test("arrays: intersect", function() { var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; equals(_.intersect(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays'); + equals(_(stooges).intersect(leaders).join(''), 'moe', 'can perform an OO-style intersection'); }); test('arrays: zip', function() { diff --git a/test/collections.js b/test/collections.js index a55d02e88..1a6f5992d 100644 --- a/test/collections.js +++ b/test/collections.js @@ -32,7 +32,7 @@ $(document).ready(function() { answer = null; _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); - ok(answer, 'can reference the original collection from inside the iterator'); + ok(answer, 'can reference the original collection from inside the iterator'); }); test('collections: map', function() { @@ -41,6 +41,9 @@ $(document).ready(function() { var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3}); equals(tripled.join(', '), '3, 6, 9', 'tripled numbers with context'); + + var doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); + equals(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers'); }); test('collections: reduce', function() { @@ -53,6 +56,9 @@ $(document).ready(function() { sum = _.inject([1, 2, 3], 0, function(sum, num){ return sum + num; }); equals(sum, 6, 'aliased as "inject"'); + + sum = _([1, 2, 3]).reduce(0, function(sum, num){ return sum + num; }); + equals(sum, 6, 'OO-style reduce'); }); test('collections: reduceRight', function() { @@ -100,6 +106,7 @@ $(document).ready(function() { ok(_.include([1,2,3], 2), 'two is in the array'); ok(!_.include([1,3,9], 2), 'two is not in the array'); ok(_.include({moe:1, larry:3, curly:9}, 3), '_.include on objects checks their values'); + ok(_([1,2,3]).include(2), 'OO-style include'); }); test('collections: invoke', function() { diff --git a/test/functions.js b/test/functions.js index 9e401e4c7..80c5cb963 100644 --- a/test/functions.js +++ b/test/functions.js @@ -8,12 +8,15 @@ $(document).ready(function() { var bound = _.bind(func, context); equals(bound(), 'name: moe', 'can bind a function to a context'); - var func = function(salutation, name) { return salutation + ': ' + name; }; + bound = _(func).bind(context); + equals(bound(), 'name: moe', 'can do OO-style binding'); + + func = function(salutation, name) { return salutation + ': ' + name; }; func = _.bind(func, this, 'hello'); equals(func('moe'), 'hello: moe', 'the function was partially applied in advance'); - func = _.bind(func, this, 'curly'); - equals(func(), 'hello: curly', 'the function was completely applied in advance'); + var func = _.bind(func, this, 'curly'); + equals(func(), 'hello: curly', 'the function was completely applied in advance'); }); test("functions: bindAll", function() { diff --git a/test/objects.js b/test/objects.js index eb73245d8..6a20e8d9a 100644 --- a/test/objects.js +++ b/test/objects.js @@ -33,6 +33,7 @@ $(document).ready(function() { var clone = {name : 'moe', lucky : [13, 27, 34]}; ok(moe != clone, 'basic equality between objects is false'); ok(_.isEqual(moe, clone), 'deep equality is true'); + ok(_(moe).isEqual(clone), 'OO-style deep equality works'); }); test("objects: isElement", function() { diff --git a/test/speed.js b/test/speed.js index 5258fb58f..ce4f878d6 100644 --- a/test/speed.js +++ b/test/speed.js @@ -11,6 +11,12 @@ return timesTwo; }); + JSLitmus.test('_(list).each()', function() { + var timesTwo = []; + _(numbers).each(function(num){ timesTwo.push(num * 2); }); + return timesTwo; + }); + JSLitmus.test('_.map()', function() { return _.map(objects, function(obj){ return obj.num; }); }); diff --git a/underscore.js b/underscore.js index e198f6a4b..8f045b9ab 100644 --- a/underscore.js +++ b/underscore.js @@ -16,15 +16,20 @@ // Save the previous value of the "_" variable. var previousUnderscore = root._; - // Create a safe reference to the Underscore object for the functions below. - var _ = root._ = {}; + // 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; }; + + // Create a safe reference to the Underscore object for reference below. + var _ = root._ = function(obj) { return new wrapper(obj); }; // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') _ = exports; // Current version. - _.VERSION = '0.3.3'; - + _.VERSION = '0.3.3'; + /*------------------------ Collection Functions: ---------------------------*/ // The cornerstone, an each implementation. @@ -447,6 +452,13 @@ return prefix ? prefix + id : id; }; + // Return a sorted list of the function names available in Underscore. + _.functions = function() { + var functions = []; + for (var key in _) if (Object.prototype.hasOwnProperty.call(_, key)) functions.push(key); + return _.without(functions, 'VERSION', 'prototype', 'noConflict'); + }; + // JavaScript templating a-la ERB, pilfered from John Resig's // "Secrets of the JavaScript Ninja", page 83. _.template = function(str, data) { @@ -472,6 +484,16 @@ _.foldr = _.reduceRight; _.filter = _.select; _.every = _.all; - _.some = _.any; + _.some = _.any; + _.methods = _.functions; + + /*------------------- Add all Functions to the Wrapper: --------------------*/ + + _.each(_.functions(), function(name) { + wrapper.prototype[name] = function() { + Array.prototype.unshift.call(arguments, this.wrapped); + return _[name].apply(_, arguments); + }; + }); })(); From ed37b9df4953d554d3f72dd3b0d9df8fb1efad91 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 7 Nov 2009 14:29:40 -0500 Subject: [PATCH 041/533] 0.4.0 is out, with OOP-style and chaining --- index.html | 66 ++++++++++++++++++++++++++++++++++++++++++++--- test/chaining.js | 35 +++++++++++++++++++++++++ test/test.html | 1 + test/utility.js | 12 +++++++++ underscore-min.js | 2 +- underscore.js | 32 ++++++++++++++++------- 6 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 test/chaining.js 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; + }; })(); From 51aef3b04d321016a2e7988bfaa51bd9bbac84a5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 7 Nov 2009 14:59:08 -0500 Subject: [PATCH 042/533] including docs for chaining --- index.html | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 540843faf..0d698948a 100644 --- a/index.html +++ b/index.html @@ -108,7 +108,7 @@ - + @@ -117,7 +117,7 @@
          Development Version (0.4.0)16kb, Uncompressed with Comments18kb, Uncompressed with Comments
          Production Version (0.4.0)

          -

          Object-Oriented and Functional Styles

          +

          Object-Oriented and Functional Styles

          You can use Underscore in either an object-oriented or a functional style, @@ -205,6 +205,12 @@ _(lyrics).chain() template

          +

          + Chaining +
          + chain, get +

          +

          Collection Functions (Arrays or Objects)

          @@ -817,6 +823,30 @@ _.template(list, {people : ['moe', 'curly', 'larry']}); => "<li>moe</li><li>curly</li><li>larry</li>"
          +

          Chaining

          + +

          + chain_(obj).chain() +
          + Returns a wrapped object. Calling methods on this object will continue + to return wrapped objects until get is used. ( + A more realistic example.) +

          +
          +_({moe : false, curly : true}).chain().values().any().isEqual(true).get();
          +=> true
          +
          + +

          + get_(obj).get() +
          + Extracts the value of a wrapped object. +

          +
          +_([1, 2, 3]).get();
          +=> [1, 2, 3]
          +
          +

          Change Log

          From fb1889869edeeaa4d5f9f6420d43e8cf398980cb Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 7 Nov 2009 15:09:32 -0500 Subject: [PATCH 043/533] docs tweak --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 0d698948a..5fb003072 100644 --- a/index.html +++ b/index.html @@ -154,7 +154,7 @@ _(lyrics).chain() return counts; }).get(); -=> returns a hash containing the word counts...

          +=> {lumberjack : 2, all : 4, night : 2 ... }

          Table of Contents

          From d4f6e1a42ff1b0e5e29796aeecb8a71b9be58f7e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 7 Nov 2009 22:48:47 -0500 Subject: [PATCH 044/533] removed _.each support of objects with their own 'each' method -- it was a little bit funky --- underscore.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/underscore.js b/underscore.js index 318268e2c..692dbe905 100644 --- a/underscore.js +++ b/underscore.js @@ -33,7 +33,7 @@ /*------------------------ Collection Functions: ---------------------------*/ // The cornerstone, an each implementation. - // Handles objects implementing forEach, each, arrays, and raw objects. + // Handles objects implementing forEach, arrays, and raw objects. _.each = function(obj, iterator, context) { var index = 0; try { @@ -41,8 +41,6 @@ obj.forEach(iterator, context); } else if (obj.length) { for (var i=0, l = obj.length; i Date: Sat, 7 Nov 2009 23:04:18 -0500 Subject: [PATCH 045/533] added an isEmpty function that works on arrays and objects --- index.html | 15 ++++++++++++++- test/objects.js | 11 +++++++++++ test/utility.js | 2 +- underscore.js | 9 ++++++--- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 5fb003072..22f09bc5e 100644 --- a/index.html +++ b/index.html @@ -192,7 +192,8 @@ _(lyrics).chain() Objects
          keys, values, - extend, clone, isEqual, isElement, + extend, clone, + isEqual, isEmpty, isElement, isArray, isFunction, isUndefined

          @@ -710,6 +711,18 @@ moe == clone; => false _.isEqual(moe, clone); => true +
          + +

          + isEmpty_.isEmpty(object) +
          + Returns true if object contains no values. +

          +
          +_.isEmpty([1, 2, 3]);
          +=> false
          +_.isEmpty({});
          +=> true
           

          diff --git a/test/objects.js b/test/objects.js index 6a20e8d9a..489c39f2a 100644 --- a/test/objects.js +++ b/test/objects.js @@ -36,6 +36,17 @@ $(document).ready(function() { ok(_(moe).isEqual(clone), 'OO-style deep equality works'); }); + test("objects: isEmpty", function() { + ok(!_([1]).isEmpty(), '[1] is not empty'); + ok(_.isEmpty([]), '[] is empty'); + ok(!_.isEmpty({one : 1}), '{one : 1} is not empty'); + ok(_.isEmpty({}), '{} is empty'); + + var obj = {one : 1}; + delete obj.one; + ok(_.isEmpty(obj), 'deleting all the keys from an object empties it'); + }); + test("objects: isElement", function() { ok(!_.isElement('div'), 'strings are not dom elements'); ok(_.isElement($('html')[0]), 'the html tag is a DOM element'); diff --git a/test/utility.js b/test/utility.js index 4022410ea..171340830 100644 --- a/test/utility.js +++ b/test/utility.js @@ -25,7 +25,7 @@ $(document).ready(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", + "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", "isFunction", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", "methods", "min", "pluck", "reduce", "reduceRight", "reject", "select", "size", "some", "sortBy", "sortedIndex", "template", "toArray", "uniq", diff --git a/underscore.js b/underscore.js index 692dbe905..9386b8564 100644 --- a/underscore.js +++ b/underscore.js @@ -146,9 +146,7 @@ if (_.isArray(obj)) return _.indexOf(obj, target) != -1; var found = false; _.each(obj, function(value) { - if (found = value === target) { - throw '__break__'; - } + if (found = value === target) throw '__break__'; }); return found; }; @@ -409,6 +407,11 @@ return true; }; + // Is a given array or object empty? + _.isEmpty = function(obj) { + return (_.isArray(obj) ? obj : _.values(obj)).length == 0; + }; + // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType == 1); From ef35fe1d861252bdd905611c877985d3b82232b8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 7 Nov 2009 23:17:27 -0500 Subject: [PATCH 046/533] allowing bind with undefined contexts, but with arguments --- underscore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 9386b8564..38eb363be 100644 --- a/underscore.js +++ b/underscore.js @@ -308,7 +308,7 @@ // 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) { - if (!context) return func; + context = context || root; var args = _.toArray(arguments).slice(2); return function() { var a = args.concat(_.toArray(arguments)); From 9d4e34e19e03654082cff8589903ceaf026c11fc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 7 Nov 2009 23:24:12 -0500 Subject: [PATCH 047/533] testing bind without context --- test/functions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/functions.js b/test/functions.js index 80c5cb963..a764d75c0 100644 --- a/test/functions.js +++ b/test/functions.js @@ -4,19 +4,22 @@ $(document).ready(function() { test("functions: bind", function() { var context = {name : 'moe'}; - var func = function() { return "name: " + this.name; }; + 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() { From cda4612a00a778cd205e6990df73ba2454df6151 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 8 Nov 2009 10:07:56 -0500 Subject: [PATCH 048/533] axeing trailing whitespace --- underscore.js | 180 +++++++++++++++++++++++++------------------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/underscore.js b/underscore.js index 38eb363be..b4db3a9c9 100644 --- a/underscore.js +++ b/underscore.js @@ -1,37 +1,37 @@ // Underscore.js // (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the terms of the MIT license. -// Portions of Underscore are inspired by or borrowed from Prototype.js, +// Portions of Underscore are inspired by or borrowed from Prototype.js, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore/ (function() { - + /*------------------------- Baseline setup ---------------------------------*/ - + // Establish the root object, "window" in the browser, or "global" on the server. var root = this; - + // Save the previous value of the "_" variable. var previousUnderscore = root._; - + // 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 + // can be used OO-style. This wrapper holds altered versions of all the // 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); }; - + // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') _ = exports; - + // Current version. - _.VERSION = '0.4.0'; - + _.VERSION = '0.4.0'; + /*------------------------ Collection Functions: ---------------------------*/ - + // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function(obj, iterator, context) { @@ -51,7 +51,7 @@ } return obj; }; - + // Return the results of applying the iterator to each element. Use JavaScript // 1.6's version of map, if possible. _.map = function(obj, iterator, context) { @@ -62,7 +62,7 @@ }); return results; }; - + // 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. _.reduce = function(obj, memo, iterator, context) { @@ -72,8 +72,8 @@ }); return memo; }; - - // The right-associative version of reduce, also known as foldr. Uses + + // The right-associative version of reduce, also known as foldr. Uses // JavaScript 1.8's version of reduceRight, if available. _.reduceRight = function(obj, memo, iterator, context) { if (obj && obj.reduceRight) return obj.reduceRight(_.bind(iterator, context), memo); @@ -83,7 +83,7 @@ }); return memo; }; - + // Return the first value which passes a truth test. _.detect = function(obj, iterator, context) { var result; @@ -95,7 +95,7 @@ }); 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) { @@ -106,7 +106,7 @@ }); return results; }; - + // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { var results = []; @@ -115,7 +115,7 @@ }); 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) { @@ -127,7 +127,7 @@ }); 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) { @@ -139,8 +139,8 @@ }); return result; }; - - // Determine if a given value is included in the array or object, + + // Determine if a given value is included in the array or object, // based on '==='. _.include = function(obj, target) { if (_.isArray(obj)) return _.indexOf(obj, target) != -1; @@ -150,7 +150,7 @@ }); return found; }; - + // Invoke a method with arguments on every item in a collection. _.invoke = function(obj, method) { var args = _.toArray(arguments).slice(2); @@ -158,12 +158,12 @@ return (method ? value[method] : value).apply(value, args); }); }; - + // Convenience version of a common use case of map: fetching a property. _.pluck = function(obj, key) { return _.map(obj, function(value){ return value[key]; }); }; - + // Return the maximum item or (item-based computation). _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); @@ -174,7 +174,7 @@ }); return result.value; }; - + // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); @@ -185,7 +185,7 @@ }); return result.value; }; - + // Sort the object's values by a criteria produced by an iterator. _.sortBy = function(obj, iterator, context) { return _.pluck(_.map(obj, function(value, index, list) { @@ -198,7 +198,7 @@ return a < b ? -1 : a > b ? 1 : 0; }), 'value'); }; - + // Use a comparator function to figure out at what index an object should // be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator) { @@ -210,36 +210,36 @@ } return low; }; - + // Convert anything iterable into a real, live array. _.toArray = function(iterable) { if (!iterable) return []; if (_.isArray(iterable)) return iterable; return _.map(iterable, function(val){ return val; }); }; - + // Return the number of elements in an object. _.size = function(obj) { return _.toArray(obj).length; }; - + /*-------------------------- Array Functions: ------------------------------*/ - + // Get the first element of an array. _.first = function(array) { return array[0]; }; - + // Get the last element of an array. _.last = function(array) { return array[array.length - 1]; }; - + // Trim out all falsy values from an array. _.compact = function(array) { return _.select(array, function(value){ return !!value; }); }; - + // Return a completely flattened version of an array. _.flatten = function(array) { return _.reduce(array, [], function(memo, value) { @@ -248,13 +248,13 @@ return memo; }); }; - + // Return a version of the array that does not contain the specified value(s). _.without = function(array) { var values = array.slice.call(arguments, 0); return _.select(array, function(value){ return !_.include(values, value); }); }; - + // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. _.uniq = function(array, isSorted) { @@ -263,18 +263,18 @@ return memo; }); }; - - // Produce an array that contains every item shared between all the + + // Produce an array that contains every item shared between all the // passed-in arrays. _.intersect = function(array) { var rest = _.toArray(arguments).slice(1); return _.select(_.uniq(array), function(item) { - return _.all(rest, function(other) { + return _.all(rest, function(other) { return _.indexOf(other, item) >= 0; }); }); }; - + // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { @@ -284,16 +284,16 @@ for (var i=0; i)[^\t]*)'/g, "$1\r") - .replace(/\t=(.*?)%>/g, "',$1,'") - .split("\t").join("');") - .split("%>").join("p.push('") - .split("\r").join("\\'") + .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 data ? fn(data) : fn; + return data ? fn(data) : fn; }; - + /*------------------------------- Aliases ----------------------------------*/ _.forEach = _.each; @@ -488,9 +488,9 @@ _.every = _.all; _.some = _.any; _.methods = _.functions; - + /*------------------------ Setup the OOP Wrapper: --------------------------*/ - + // Add all of the Underscore functions to the wrapper object. _.each(_.functions(), function(name) { wrapper.prototype[name] = function() { @@ -499,13 +499,13 @@ 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; From 5eec4e5d22dae5518b585f36e867112aac0606df Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 8 Nov 2009 12:07:10 -0500 Subject: [PATCH 049/533] adding breakLoop --- index.html | 240 +++++++++++++++++++++++++----------------------- test/utility.js | 11 ++- underscore.js | 13 ++- 3 files changed, 146 insertions(+), 118 deletions(-) diff --git a/index.html b/index.html index 22f09bc5e..06de0f1b9 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - Underscore.js + Underscore.js - +

          - +

          Underscore.js

          - +

          - Underscore is a + Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in - Prototype.js - (or Ruby), - but without extending any of the built-in JavaScript objects. It's the + Prototype.js + (or Ruby), + but without extending any of the built-in JavaScript objects. It's the tie to go along with jQuery's tux.

          - +

          - Underscore provides 45-odd functions that support both the usual - functional suspects: map, select, invoke — - as well as more specialized helpers: function binding, javascript - templating, deep equality testing, and so on. It delegates to built-in - functions, if present, so - JavaScript 1.6 - compliant browsers will use the - native implementations of forEach, map, filter, + Underscore provides 45-odd functions that support both the usual + functional suspects: map, select, invoke — + as well as more specialized helpers: function binding, javascript + templating, deep equality testing, and so on. It delegates to built-in + functions, if present, so + JavaScript 1.6 + compliant browsers will use the + native implementations of forEach, map, filter, every, some and indexOf.

          - +

          Underscore includes a complete Test & Benchmark Suite for your perusal.

          - +

          - The unabridged source code is + The unabridged source code is available on GitHub.

          - +

          Downloads (Right-click, and use "Save As")

          - +

          @@ -116,28 +116,28 @@

          - +

          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 + 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, + 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 + 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"},
          @@ -149,73 +149,73 @@ var lyrics = [
           _(lyrics).chain()
             .map(function(line) { return line.words.split(' '); })
             .flatten()
          -  .reduce({}, function(counts, word) { 
          +  .reduce({}, function(counts, word) {
               counts[word] = (counts[word] || 0) + 1;
               return counts;
           }).get();
           
           => {lumberjack : 2, all : 4, night : 2 ... }
          - +

          Table of Contents

          - +

          Collections -
          - each, map, - reduce, reduceRight, - detect, select, - reject, all, - any, include, - invoke, pluck, - max, min, - sortBy, sortedIndex, +
          + each, map, + reduce, reduceRight, + detect, select, + reject, all, + any, include, + invoke, pluck, + max, min, + sortBy, sortedIndex, toArray, size

          - +

          Arrays
          - first, last, - compact, flatten, without, uniq, + first, last, + compact, flatten, without, uniq, intersect, zip, indexOf, lastIndexOf

          - +

          Functions
          - bind, bindAll, delay, + bind, bindAll, delay, defer, wrap, compose

          - +

          Objects
          - keys, values, - extend, clone, - isEqual, isEmpty, isElement, + keys, values, + extend, clone, + isEqual, isEmpty, isElement, isArray, isFunction, isUndefined

          - +

          Utility
          noConflict, - identity, uniqueId, - template + identity, breakLoop, + uniqueId, template

          - +

          Chaining
          chain, get

          - +
          - +

          Collection Functions (Arrays or Objects)

          - +

          each_.each(list, iterator, [context]) Alias: forEach @@ -224,19 +224,19 @@ _(lyrics).chain() function. The iterator is bound to the context object, if one is passed. Each invocation of iterator is called with three arguments: (element, index, list). If list is a JavaScript object, iterator's - arguments will be (value, key, list). If the list has an each - method of its own, it will be used instead. Delegates to the native + arguments will be (value, key, list). If the list has an each + method of its own, it will be used instead. Delegates to the native forEach function if it exists.

           _.each([1, 2, 3], function(num){ alert(num); });
           => alerts each number in turn...
          - +

          map_.map(list, iterator, [context])
          - Produces a new array of values by mapping each value in list - through a transformation function (iterator). If the native + Produces a new array of values by mapping each value in list + through a transformation function (iterator). If the native map method exists, it will be used instead.

          @@ -247,7 +247,7 @@ _.map([1, 2, 3], function(num){ return num * 3 });
                   reduce_.reduce(list, memo, iterator, [context])
                   Aliases: inject, foldl
                   
          - Also known as inject and foldl, reduce boils down a + Also known as inject and foldl, reduce boils down a list of values into a single value. Memo is the initial state of the reduction, and each successive step of it should be returned by iterator. @@ -261,7 +261,7 @@ var sum = _.reduce([1, 2, 3], 0, function(memo, num){ return memo + num }); reduceRight_.reduceRight(list, memo, iterator, [context]) Alias: foldr
          - The right-associative version of reduce. Delegates to the + The right-associative version of reduce. Delegates to the JavaScript 1.8 version of reduceRight, if it exists. Foldr is not as useful in JavaScript as it would be in a language with lazy evaluation. @@ -275,8 +275,8 @@ var flat = _.reduceRight(list, [], function(a, b) { return a.concat(b); });

          detect_.detect(list, iterator, [context])
          - Looks through each value in the list, returning the first one that - passes a truth test (iterator). The function returns as + Looks through each value in the list, returning the first one that + passes a truth test (iterator). The function returns as soon as it finds an acceptable element, and doesn't traverse the entire list.

          @@ -314,7 +314,7 @@ var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); Alias: every
          Returns true if all of the values in the list pass the iterator - truth test. If an iterator is not provided, the truthy value of + truth test. If an iterator is not provided, the truthy value of the element will be used instead. Delegates to the native method every, if present.

          @@ -364,7 +364,7 @@ _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');

          pluck_.pluck(list, propertyName)
          - An convenient version of what is perhaps the most common use-case for + An convenient version of what is perhaps the most common use-case for map: extracting a list of property values.

          @@ -443,9 +443,9 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);
           _.size({one : 1, two : 2, three : 3});
           => 3
           
          - +

          Array Functions

          - +

          first_.first(array)
          @@ -469,8 +469,8 @@ _.last([3, 2, 1]);

          compact_.compact(array)
          - Returns a copy of the array with all falsy values removed. - In JavaScript, false, null, 0, "", + Returns a copy of the array with all falsy values removed. + In JavaScript, false, null, 0, "", undefined and NaN are all falsy.

          @@ -537,7 +537,7 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
                 

          indexOf_.indexOf(array, value)
          - Returns the index at which value can be found in the array, + Returns the index at which value can be found in the array, or -1 if value is not present in the array. Uses the native indexOf function unless it's missing.

          @@ -549,8 +549,8 @@ _.indexOf([1, 2, 3], 2);

          lastIndexOf_.lastIndexOf(array, value)
          - Returns the index of the last occurrence of value in the array, - or -1 if value is not present. Uses the native lastIndexOf + Returns the index of the last occurrence of value in the array, + or -1 if value is not present. Uses the native lastIndexOf function if possible.

          @@ -566,7 +566,7 @@ _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
                   Bind a function to a context object, meaning that whenever
                   the function is called, the value of this will be the context.
                   Optionally, bind arguments to the function to pre-fill them,
          -        also known as currying. 
          +        also known as currying.
                 

           var func = function(greeting){ return greeting + ': ' + this.name };
          @@ -578,15 +578,15 @@ func();
                 

          bindAll_.bindAll(*methodNames, context)
          - Binds a number of methods on the context object, specified by + Binds a number of methods on the context 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 + as event handlers, which would otherwise be invoked with a fairly useless this.

           var buttonView = {
          -  label   : 'underscore', 
          +  label   : 'underscore',
             onClick : function(){ alert('clicked: ' + this.label); },
             onHover : function(){ console.log('hovering: ' + this.label); }
           };
          @@ -624,8 +624,8 @@ _.defer(function(){ alert('deferred'); });
                 

          wrap_.wrap(function, wrapper)
          - Wraps the first function inside of the wrapper function, - passing it as the first argument. This allows the wrapper to + Wraps the first function inside of the wrapper function, + passing it as the first argument. This allows the wrapper to execute code before and after the function runs, adjust the arguments, and execute it conditionally.

          @@ -641,7 +641,7 @@ hello();

          compose_.compose(*functions)
          - Returns the composition of a list of functions, where each function + Returns the composition of a list of functions, where each function consumes the return value of the function that follows. In math terms, composing the functions f(), g(), and h() produces f(g(h())). @@ -679,7 +679,7 @@ _.values({one : 1, two : 2, three : 3});

          extend_.extend(destination, source)
          - Copy all of the properties in the source object over to the + Copy all of the properties in the source object over to the destination object.

          @@ -768,7 +768,7 @@ _.isUndefined(window.missingVariable);
           

          Utility Functions

          - +

          noConflict_.noConflict()
          @@ -781,20 +781,34 @@ var underscore = _.noConflict();

          identity_.identity(value)
          - Returns the same value that is used as the argument. In math: + Returns the same value that is used as the argument. In math: f(x) = x
          - This function looks useless, but is used throughout Underscore as + This function looks useless, but is used throughout Underscore as a default iterator.

           var moe = {name : 'moe'};
           moe === _.identity(moe);
          -=> true
          +=> true
          + +

          + breakLoop_.breakLoop() +
          + Breaks out of the current loop iteration. Similar to the break + keyword in regular "for" loop, but works within an iterator function. +

          +
          +var result = null;
          +_.each([1, 2, 3], function(num) { 
          +  if ((result = num) == 2) _.breakLoop(); 
          +});
          +result;
          +=> 2

          uniqueId_.uniqueId([prefix])
          - Generate a globally-unique id for client-side models or DOM elements + Generate a globally-unique id for client-side models or DOM elements that need one. If prefix is passed, the id will be appended to it.

          @@ -819,7 +833,7 @@ _.functions();
                   Compiles JavaScript templates into functions that can be evaluated
                   for rendering. Useful for rendering complicated bits of HTML from JSON
                   data sources. Template functions can both interpolate variables, using
          - <%= … %>, as well as execute arbitrary JavaScript code, with + <%= … %>, as well as execute arbitrary JavaScript code, with <% … %>. When you evaluate a template function, pass in a context object that has properties corresponding to the template's free variables. If you're writing a one-off, you can pass the context @@ -834,10 +848,10 @@ compiled({name : 'moe'}); var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>"; _.template(list, {people : ['moe', 'curly', 'larry']}); => "<li>moe</li><li>curly</li><li>larry</li>" -
          +

          Chaining

          - +

          chain_(obj).chain()
          @@ -861,30 +875,30 @@ _([1, 2, 3]).get();

          Change Log

          - +

          0.4.0
          - All Underscore functions can now be called in an object-oriented style, + 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. + Marc-André Cournoyer. Wrapped objects can be chained through multiple - method invocations. A functions method + 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 as foldr, and aliased reduce as foldl.

          - +

          0.3.2
          - Now runs on stock Rhino + Now runs on stock Rhino interpreters with: load("underscore.js"). Added identity as a utility function.

          - +

          0.3.1
          All iterators are now passed in the original collection as their third @@ -892,29 +906,29 @@ _([1, 2, 3]).get(); objects is now called with (value, key, collection), for details see _.each.

          - +

          0.3.0
          - Added Dmitry Baranovskiy's - comprehensive optimizations, merged in - Kris Kowal's patches to make Underscore - CommonJS and + Added Dmitry Baranovskiy's + comprehensive optimizations, merged in + Kris Kowal's patches to make Underscore + CommonJS and Narwhal compliant.

          - +

          0.2.0
          Added compose and lastIndexOf, renamed inject to - reduce, added aliases for inject, filter, + reduce, added aliases for inject, filter, every, some, and forEach.

          - +

          0.1.1
          - Added noConflict, so that the "Underscore" object can be assigned to + Added noConflict, so that the "Underscore" object can be assigned to other variables.

          - +

          0.1.0
          Initial release of Underscore.js. @@ -925,9 +939,9 @@ _([1, 2, 3]).get(); A DocumentCloud Project

          - +
          - +
          diff --git a/test/utility.js b/test/utility.js index 171340830..4515ba4c4 100644 --- a/test/utility.js +++ b/test/utility.js @@ -14,6 +14,15 @@ $(document).ready(function() { var moe = {name : 'moe'}; 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; @@ -22,7 +31,7 @@ $(document).ready(function() { }); test("utility: functions", function() { - var expected = ["all", "any", "bind", "bindAll", "clone", "compact", "compose", + var expected = ["all", "any", "bind", "bindAll", "breakLoop", "clone", "compact", "compose", "defer", "delay", "detect", "each", "every", "extend", "filter", "first", "flatten", "foldl", "foldr", "forEach", "functions", "identity", "include", "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", diff --git a/underscore.js b/underscore.js index b4db3a9c9..748c6e247 100644 --- a/underscore.js +++ b/underscore.js @@ -90,7 +90,7 @@ _.each(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) { result = value; - throw '__break__'; + _.breakLoop(); } }); return result; @@ -123,7 +123,7 @@ if (obj.every) return obj.every(iterator, context); var result = true; _.each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) throw '__break__'; + if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); }); return result; }; @@ -135,7 +135,7 @@ if (obj.some) return obj.some(iterator, context); var result = false; _.each(obj, function(value, index, list) { - if (result = iterator.call(context, value, index, list)) throw '__break__'; + if (result = iterator.call(context, value, index, list)) _.breakLoop(); }); return result; }; @@ -146,7 +146,7 @@ if (_.isArray(obj)) return _.indexOf(obj, target) != -1; var found = false; _.each(obj, function(value) { - if (found = value === target) throw '__break__'; + if (found = value === target) _.breakLoop(); }); return found; }; @@ -446,6 +446,11 @@ return value; }; + // Break out of the middle of an iteration. + _.breakLoop = function() { + throw "__break__"; + }; + // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; From cb85480659b9bfbe0daa65f4ca22a6e36ddea8e4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 8 Nov 2009 14:18:24 -0500 Subject: [PATCH 050/533] 0.4.1 is out, with array methods proxied for wrapped objects, an _.breakLoop(), and an _.isEmpty() --- index.html | 32 +++++++++++++++++++++++++++----- test/chaining.js | 22 +++++++++++++++++----- underscore-min.js | 2 +- underscore.js | 25 ++++++++++++++++++++++--- 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index 06de0f1b9..dc65e3f33 100644 --- a/index.html +++ b/index.html @@ -81,7 +81,7 @@

          - Underscore provides 45-odd functions that support both the usual + Underscore provides 50-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.4.0)Development Version (0.4.1) 18kb, Uncompressed with Comments
          Production Version (0.4.0)Production Version (0.4.1) 2kb, Packed and Gzipped
          @@ -156,6 +156,14 @@ _(lyrics).chain() => {lumberjack : 2, all : 4, night : 2 ... } +

          + In addition, the + Array prototype's methods + are proxied through the chained Underscore object, so you can slip a + reverse or a push into your chain, and continue to + modify the array. +

          +

          Table of Contents

          @@ -860,8 +868,13 @@ _.template(list, {people : ['moe', 'curly', 'larry']}); A more realistic example.)

          -_({moe : false, curly : true}).chain().values().any().isEqual(true).get();
          -=> true
          +var stooges = [{name : 'curly', age : 25}, {name : 'moe', age : 21}, {name : 'larry', age : 23}];
          +var youngest = _(stooges).chain()
          +  .sortBy(function(stooge){ return stooge.age; })
          +  .map(function(stooge){ return stooge.name + ' is ' + stooge.age; })
          +  .first()
          +  .get();
          +=> "moe is 21"
           

          @@ -875,6 +888,15 @@ _([1, 2, 3]).get();

          Change Log

          + +

          + 0.4.1
          + Chained Underscore objects now support the Array prototype methods, so + that you can perform the full range of operations on a wrapped array + without having to break your chain. Added a breakLoop method + to break in the middle of any Underscore iteration. Added an + isEmpty function that works on arrays and objects. +

          0.4.0
          diff --git a/test/chaining.js b/test/chaining.js index 0f3b10037..dc6cef6a4 100644 --- a/test/chaining.js +++ b/test/chaining.js @@ -1,7 +1,7 @@ $(document).ready(function() { - + module("Underscore chaining."); - + test("chaining: map/flatten/reduce", function() { var lyrics = [ "I'm a lumberjack and I'm okay", @@ -12,14 +12,14 @@ $(document).ready(function() { var counts = _(lyrics).chain() .map(function(line) { return line.split(''); }) .flatten() - .reduce({}, function(hash, l) { + .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) { @@ -31,5 +31,17 @@ $(document).ready(function() { }).get(); equals(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers"); }); - + + test("chaining: reverse/concat/unshift/pop/map", function() { + var numbers = [1,2,3,4,5]; + numbers = _(numbers).chain() + .reverse() + .concat([5, 5, 5]) + .unshift(17) + .pop() + .map(function(n){ return n * 2; }) + .get(); + equals(numbers.join(', '), "34, 10, 8, 6, 4, 2, 10, 10", 'can chain together array functions.'); + }); + }); diff --git a/underscore-min.js b/underscore-min.js index 25ad77f4c..e0fd42d76 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(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 +(function(){var c=this;var e=c._;var f=function(g){this._wrapped=g};var d=c._=function(g){return new f(g)};if(typeof exports!=="undefined"){d=exports}d.VERSION="0.4.1";d.each=function(p,n,m){var h=0;try{if(p.forEach){p.forEach(n,m)}else{if(p.length){for(var k=0,g=p.length;k=g.computed&&(g={value:p,computed:n})});return g.value};d.min=function(k,j,h){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var g={computed:Infinity};d.each(k,function(p,m,o){var n=j?j.call(h,p,m,o):p;nk?1:0}),"value")};d.sortedIndex=function(n,m,j){j=j||d.identity;var g=0,k=n.length;while(g>1;j(n[h])=0})})};d.zip=function(){var g=d.toArray(arguments);var k=d.max(d.pluck(g,"length"));var j=new Array(k);for(var h=0;h=0;h--){arguments=[g[h].apply(this,arguments)]}return arguments[0]}};d.keys=function(g){return d.map(g,function(j,h){return h})};d.values=function(g){return d.map(g,d.identity)};d.extend=function(g,j){for(var h in j){g[h]=j[h]}return g};d.clone=function(g){if(d.isArray(g)){return g.slice(0)}return d.extend({},g)};d.isEqual=function(h,g){if(h===g){return true}var m=typeof(h),o=typeof(g);if(m!=o){return false}if(h==g){return true}if(h.isEqual){return h.isEqual(g)}if(m!=="object"){return false}var j=d.keys(h),n=d.keys(g);if(j.length!=n.length){return false}for(var k in h){if(!d.isEqual(h[k],g[k])){return false}}return true};d.isEmpty=function(g){return(d.isArray(g)?g:d.values(g)).length==0};d.isElement=function(g){return !!(g&&g.nodeType==1)};d.isArray=function(g){return Object.prototype.toString.call(g)=="[object Array]"};d.isFunction=function(g){return Object.prototype.toString.call(g)=="[object Function]"};d.isUndefined=function(g){return typeof g=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(g){return g};d.breakLoop=function(){throw"__break__"};var b=0;d.uniqueId=function(g){var h=b++;return g?g+h:h};d.functions=function(){var h=[];for(var g in d){if(Object.prototype.hasOwnProperty.call(d,g)){h.push(g)}}return d.without(h,"VERSION","prototype","noConflict").sort()};d.template=function(j,h){var g=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 h?g(h):g};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(h,g){return g?d(h).chain():h};d.each(d.functions(),function(g){f.prototype[g]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[g].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(g){f.prototype[g]=function(){Array.prototype[g].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(g){f.prototype[g]=function(){return a(Array.prototype[g].apply(this._wrapped,arguments),this._chain)}});f.prototype.chain=function(){this._chain=true;return this};f.prototype.get=function(){return this._wrapped}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 748c6e247..c0fedb145 100644 --- a/underscore.js +++ b/underscore.js @@ -28,7 +28,7 @@ if (typeof exports !== 'undefined') _ = exports; // Current version. - _.VERSION = '0.4.0'; + _.VERSION = '0.4.1'; /*------------------------ Collection Functions: ---------------------------*/ @@ -496,12 +496,31 @@ /*------------------------ Setup the OOP Wrapper: --------------------------*/ + // Helper function to continue chaining intermediate results. + var result = function(obj, chain) { + return chain ? _(obj).chain() : obj; + }; + // 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); - var result = _[name].apply(_, arguments); - return this._chain ? _(result).chain() : result; + return result(_[name].apply(_, arguments), this._chain); + }; + }); + + // Add all mutator Array functions to the wrapper. + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + wrapper.prototype[name] = function() { + Array.prototype[name].apply(this._wrapped, arguments); + return result(this._wrapped, this._chain); + }; + }); + + // Add all accessor Array functions to the wrapper. + _.each(['concat', 'join', 'slice'], function(name) { + wrapper.prototype[name] = function() { + return result(Array.prototype[name].apply(this._wrapped, arguments), this._chain); }; }); From 7127d404d2addc9a8518b95ee34c4bc012c43898 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 8 Nov 2009 14:25:54 -0500 Subject: [PATCH 051/533] correcting documentation --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index dc65e3f33..753942fcc 100644 --- a/index.html +++ b/index.html @@ -232,8 +232,8 @@ _(lyrics).chain() function. The iterator is bound to the context object, if one is passed. Each invocation of iterator is called with three arguments: (element, index, list). If list is a JavaScript object, iterator's - arguments will be (value, key, list). If the list has an each - method of its own, it will be used instead. Delegates to the native + arguments will be (value, key, list). Use breakLoop + to break out of the iteration. Delegates to the native forEach function if it exists.

          
          From f6e67a5bf28bc544b1acd84d168e4f9d58427610 Mon Sep 17 00:00:00 2001
          From: Jeremy Ashkenas 
          Date: Mon, 9 Nov 2009 08:28:32 -0500
          Subject: [PATCH 052/533] version 0.4.2 -- quick patch to rename get() to
           value() for clarity, and adding jQuery comparisons in the speed tests
          
          ---
           index.html        | 25 +++++++++++++++----------
           test/chaining.js  |  6 +++---
           test/speed.js     | 34 ++++++++++++++++++++++------------
           underscore-min.js |  2 +-
           underscore.js     |  4 ++--
           5 files changed, 43 insertions(+), 28 deletions(-)
          
          diff --git a/index.html b/index.html
          index 753942fcc..3502dde88 100644
          --- a/index.html
          +++ b/index.html
          @@ -107,11 +107,11 @@
               

          - + - +
          Development Version (0.4.1)Development Version (0.4.2) 18kb, Uncompressed with Comments
          Production Version (0.4.1)Production Version (0.4.2) 2kb, Packed and Gzipped
          @@ -133,7 +133,7 @@ _([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 + use value 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.

          @@ -152,7 +152,7 @@ _(lyrics).chain() .reduce({}, function(counts, word) { counts[word] = (counts[word] || 0) + 1; return counts; -}).get(); +}).value(); => {lumberjack : 2, all : 4, night : 2 ... } @@ -217,7 +217,7 @@ _(lyrics).chain()

          Chaining
          - chain, get + chain, value

          @@ -864,7 +864,7 @@ _.template(list, {people : ['moe', 'curly', 'larry']}); chain_(obj).chain()
          Returns a wrapped object. Calling methods on this object will continue - to return wrapped objects until get is used. ( + to return wrapped objects until value is used. ( A more realistic example.)

          @@ -873,22 +873,27 @@ var youngest = _(stooges).chain()
             .sortBy(function(stooge){ return stooge.age; })
             .map(function(stooge){ return stooge.name + ' is ' + stooge.age; })
             .first()
          -  .get();
          +  .value();
           => "moe is 21"
           
          -

          - get_(obj).get() +

          + value_(obj).value()
          Extracts the value of a wrapped object.

          -_([1, 2, 3]).get();
          +_([1, 2, 3]).value();
           => [1, 2, 3]
           

          Change Log

          +

          + 0.4.2
          + Renamed the unwrapping function to value, for clarity. +

          +

          0.4.1
          Chained Underscore objects now support the Array prototype methods, so diff --git a/test/chaining.js b/test/chaining.js index dc6cef6a4..55f0c8c0d 100644 --- a/test/chaining.js +++ b/test/chaining.js @@ -16,7 +16,7 @@ $(document).ready(function() { hash[l] = hash[l] || 0; hash[l]++; return hash; - }).get(); + }).value(); ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song'); }); @@ -28,7 +28,7 @@ $(document).ready(function() { return n % 4 == 0; }).sortBy(function(n) { return -n; - }).get(); + }).value(); equals(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers"); }); @@ -40,7 +40,7 @@ $(document).ready(function() { .unshift(17) .pop() .map(function(n){ return n * 2; }) - .get(); + .value(); equals(numbers.join(', '), "34, 10, 8, 6, 4, 2, 10, 10", 'can chain together array functions.'); }); diff --git a/test/speed.js b/test/speed.js index ce4f878d6..8313b1d56 100644 --- a/test/speed.js +++ b/test/speed.js @@ -1,54 +1,64 @@ (function() { - + var numbers = []; for (var i=0; i<1000; i++) numbers.push(i); var objects = _.map(numbers, function(n){ return {num : n}; }); var randomized = _.sortBy(numbers, function(){ return Math.random(); }); - + JSLitmus.test('_.each()', function() { var timesTwo = []; _.each(numbers, function(num){ timesTwo.push(num * 2); }); return timesTwo; }); - + JSLitmus.test('_(list).each()', function() { var timesTwo = []; _(numbers).each(function(num){ timesTwo.push(num * 2); }); return timesTwo; }); - + + JSLitmus.test('jQuery.each()', function() { + var timesTwo = []; + jQuery.each(numbers, function(){ timesTwo.push(this * 2); }); + return timesTwo; + }); + JSLitmus.test('_.map()', function() { return _.map(objects, function(obj){ return obj.num; }); }); - + + JSLitmus.test('jQuery.map()', function() { + return jQuery.map(objects, function(obj){ return obj.num; }); + }); + JSLitmus.test('_.pluck()', function() { return _.pluck(objects, 'num'); }); - + JSLitmus.test('_.uniq()', function() { return _.uniq(randomized); }); - + JSLitmus.test('_.uniq() (sorted)', function() { return _.uniq(numbers, true); }); - + JSLitmus.test('_.sortBy()', function() { return _.sortBy(numbers, function(num){ return -num; }); }); - + JSLitmus.test('_.isEqual()', function() { return _.isEqual(numbers, randomized); }); - + JSLitmus.test('_.keys()', function() { return _.keys(objects); }); - + JSLitmus.test('_.values()', function() { return _.values(objects); }); - + JSLitmus.test('_.intersect()', function() { return _.intersect(numbers, randomized); }); diff --git a/underscore-min.js b/underscore-min.js index e0fd42d76..8ec85c2f2 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var c=this;var e=c._;var f=function(g){this._wrapped=g};var d=c._=function(g){return new f(g)};if(typeof exports!=="undefined"){d=exports}d.VERSION="0.4.1";d.each=function(p,n,m){var h=0;try{if(p.forEach){p.forEach(n,m)}else{if(p.length){for(var k=0,g=p.length;k=g.computed&&(g={value:p,computed:n})});return g.value};d.min=function(k,j,h){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var g={computed:Infinity};d.each(k,function(p,m,o){var n=j?j.call(h,p,m,o):p;nk?1:0}),"value")};d.sortedIndex=function(n,m,j){j=j||d.identity;var g=0,k=n.length;while(g>1;j(n[h])=0})})};d.zip=function(){var g=d.toArray(arguments);var k=d.max(d.pluck(g,"length"));var j=new Array(k);for(var h=0;h=0;h--){arguments=[g[h].apply(this,arguments)]}return arguments[0]}};d.keys=function(g){return d.map(g,function(j,h){return h})};d.values=function(g){return d.map(g,d.identity)};d.extend=function(g,j){for(var h in j){g[h]=j[h]}return g};d.clone=function(g){if(d.isArray(g)){return g.slice(0)}return d.extend({},g)};d.isEqual=function(h,g){if(h===g){return true}var m=typeof(h),o=typeof(g);if(m!=o){return false}if(h==g){return true}if(h.isEqual){return h.isEqual(g)}if(m!=="object"){return false}var j=d.keys(h),n=d.keys(g);if(j.length!=n.length){return false}for(var k in h){if(!d.isEqual(h[k],g[k])){return false}}return true};d.isEmpty=function(g){return(d.isArray(g)?g:d.values(g)).length==0};d.isElement=function(g){return !!(g&&g.nodeType==1)};d.isArray=function(g){return Object.prototype.toString.call(g)=="[object Array]"};d.isFunction=function(g){return Object.prototype.toString.call(g)=="[object Function]"};d.isUndefined=function(g){return typeof g=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(g){return g};d.breakLoop=function(){throw"__break__"};var b=0;d.uniqueId=function(g){var h=b++;return g?g+h:h};d.functions=function(){var h=[];for(var g in d){if(Object.prototype.hasOwnProperty.call(d,g)){h.push(g)}}return d.without(h,"VERSION","prototype","noConflict").sort()};d.template=function(j,h){var g=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 h?g(h):g};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(h,g){return g?d(h).chain():h};d.each(d.functions(),function(g){f.prototype[g]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[g].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(g){f.prototype[g]=function(){Array.prototype[g].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(g){f.prototype[g]=function(){return a(Array.prototype[g].apply(this._wrapped,arguments),this._chain)}});f.prototype.chain=function(){this._chain=true;return this};f.prototype.get=function(){return this._wrapped}})(); \ No newline at end of file +(function(){var c=this;var e=c._;var f=function(g){this._wrapped=g};var d=c._=function(g){return new f(g)};if(typeof exports!=="undefined"){d=exports}d.VERSION="0.4.2";d.each=function(p,n,m){var h=0;try{if(p.forEach){p.forEach(n,m)}else{if(p.length){for(var k=0,g=p.length;k=g.computed&&(g={value:p,computed:n})});return g.value};d.min=function(k,j,h){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var g={computed:Infinity};d.each(k,function(p,m,o){var n=j?j.call(h,p,m,o):p;nk?1:0}),"value")};d.sortedIndex=function(n,m,j){j=j||d.identity;var g=0,k=n.length;while(g>1;j(n[h])=0})})};d.zip=function(){var g=d.toArray(arguments);var k=d.max(d.pluck(g,"length"));var j=new Array(k);for(var h=0;h=0;h--){arguments=[g[h].apply(this,arguments)]}return arguments[0]}};d.keys=function(g){return d.map(g,function(j,h){return h})};d.values=function(g){return d.map(g,d.identity)};d.extend=function(g,j){for(var h in j){g[h]=j[h]}return g};d.clone=function(g){if(d.isArray(g)){return g.slice(0)}return d.extend({},g)};d.isEqual=function(h,g){if(h===g){return true}var m=typeof(h),o=typeof(g);if(m!=o){return false}if(h==g){return true}if(h.isEqual){return h.isEqual(g)}if(m!=="object"){return false}var j=d.keys(h),n=d.keys(g);if(j.length!=n.length){return false}for(var k in h){if(!d.isEqual(h[k],g[k])){return false}}return true};d.isEmpty=function(g){return(d.isArray(g)?g:d.values(g)).length==0};d.isElement=function(g){return !!(g&&g.nodeType==1)};d.isArray=function(g){return Object.prototype.toString.call(g)=="[object Array]"};d.isFunction=function(g){return Object.prototype.toString.call(g)=="[object Function]"};d.isUndefined=function(g){return typeof g=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(g){return g};d.breakLoop=function(){throw"__break__"};var b=0;d.uniqueId=function(g){var h=b++;return g?g+h:h};d.functions=function(){var h=[];for(var g in d){if(Object.prototype.hasOwnProperty.call(d,g)){h.push(g)}}return d.without(h,"VERSION","prototype","noConflict").sort()};d.template=function(j,h){var g=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 h?g(h):g};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(h,g){return g?d(h).chain():h};d.each(d.functions(),function(g){f.prototype[g]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[g].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(g){f.prototype[g]=function(){Array.prototype[g].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(g){f.prototype[g]=function(){return a(Array.prototype[g].apply(this._wrapped,arguments),this._chain)}});f.prototype.chain=function(){this._chain=true;return this};f.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index c0fedb145..3dbe9bb9c 100644 --- a/underscore.js +++ b/underscore.js @@ -28,7 +28,7 @@ if (typeof exports !== 'undefined') _ = exports; // Current version. - _.VERSION = '0.4.1'; + _.VERSION = '0.4.2'; /*------------------------ Collection Functions: ---------------------------*/ @@ -531,7 +531,7 @@ }; // Extracts the result from a wrapped and chained object. - wrapper.prototype.get = function() { + wrapper.prototype.value = function() { return this._wrapped; }; From 111f1cbc0d65e4d6680398091a017a9c4e53d153 Mon Sep 17 00:00:00 2001 From: Tim Caswell Date: Mon, 9 Nov 2009 16:46:34 -0600 Subject: [PATCH 053/533] Make the exports system include the wrapper function in node.js --- underscore-min.js | 2 +- underscore.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/underscore-min.js b/underscore-min.js index 8ec85c2f2..20e7c78b1 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var c=this;var e=c._;var f=function(g){this._wrapped=g};var d=c._=function(g){return new f(g)};if(typeof exports!=="undefined"){d=exports}d.VERSION="0.4.2";d.each=function(p,n,m){var h=0;try{if(p.forEach){p.forEach(n,m)}else{if(p.length){for(var k=0,g=p.length;k=g.computed&&(g={value:p,computed:n})});return g.value};d.min=function(k,j,h){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var g={computed:Infinity};d.each(k,function(p,m,o){var n=j?j.call(h,p,m,o):p;nk?1:0}),"value")};d.sortedIndex=function(n,m,j){j=j||d.identity;var g=0,k=n.length;while(g>1;j(n[h])=0})})};d.zip=function(){var g=d.toArray(arguments);var k=d.max(d.pluck(g,"length"));var j=new Array(k);for(var h=0;h=0;h--){arguments=[g[h].apply(this,arguments)]}return arguments[0]}};d.keys=function(g){return d.map(g,function(j,h){return h})};d.values=function(g){return d.map(g,d.identity)};d.extend=function(g,j){for(var h in j){g[h]=j[h]}return g};d.clone=function(g){if(d.isArray(g)){return g.slice(0)}return d.extend({},g)};d.isEqual=function(h,g){if(h===g){return true}var m=typeof(h),o=typeof(g);if(m!=o){return false}if(h==g){return true}if(h.isEqual){return h.isEqual(g)}if(m!=="object"){return false}var j=d.keys(h),n=d.keys(g);if(j.length!=n.length){return false}for(var k in h){if(!d.isEqual(h[k],g[k])){return false}}return true};d.isEmpty=function(g){return(d.isArray(g)?g:d.values(g)).length==0};d.isElement=function(g){return !!(g&&g.nodeType==1)};d.isArray=function(g){return Object.prototype.toString.call(g)=="[object Array]"};d.isFunction=function(g){return Object.prototype.toString.call(g)=="[object Function]"};d.isUndefined=function(g){return typeof g=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(g){return g};d.breakLoop=function(){throw"__break__"};var b=0;d.uniqueId=function(g){var h=b++;return g?g+h:h};d.functions=function(){var h=[];for(var g in d){if(Object.prototype.hasOwnProperty.call(d,g)){h.push(g)}}return d.without(h,"VERSION","prototype","noConflict").sort()};d.template=function(j,h){var g=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 h?g(h):g};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(h,g){return g?d(h).chain():h};d.each(d.functions(),function(g){f.prototype[g]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[g].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(g){f.prototype[g]=function(){Array.prototype[g].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(g){f.prototype[g]=function(){return a(Array.prototype[g].apply(this._wrapped,arguments),this._chain)}});f.prototype.chain=function(){this._chain=true;return this};f.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file +(function(){var c=this;var e=c._;var f=function(g){this._wrapped=g};var d=c._=function(g){return new f(g)};if(module&&typeof module.exports!=='undefined'){module.exports=d}else{if(typeof exports!=='undefined'){d=exports}}d.VERSION="0.4.2";d.each=function(p,n,m){var h=0;try{if(p.forEach){p.forEach(n,m)}else{if(p.length){for(var k=0,g=p.length;k=g.computed&&(g={value:p,computed:n})});return g.value};d.min=function(k,j,h){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var g={computed:Infinity};d.each(k,function(p,m,o){var n=j?j.call(h,p,m,o):p;nk?1:0}),"value")};d.sortedIndex=function(n,m,j){j=j||d.identity;var g=0,k=n.length;while(g>1;j(n[h])=0})})};d.zip=function(){var g=d.toArray(arguments);var k=d.max(d.pluck(g,"length"));var j=new Array(k);for(var h=0;h=0;h--){arguments=[g[h].apply(this,arguments)]}return arguments[0]}};d.keys=function(g){return d.map(g,function(j,h){return h})};d.values=function(g){return d.map(g,d.identity)};d.extend=function(g,j){for(var h in j){g[h]=j[h]}return g};d.clone=function(g){if(d.isArray(g)){return g.slice(0)}return d.extend({},g)};d.isEqual=function(h,g){if(h===g){return true}var m=typeof(h),o=typeof(g);if(m!=o){return false}if(h==g){return true}if(h.isEqual){return h.isEqual(g)}if(m!=="object"){return false}var j=d.keys(h),n=d.keys(g);if(j.length!=n.length){return false}for(var k in h){if(!d.isEqual(h[k],g[k])){return false}}return true};d.isEmpty=function(g){return(d.isArray(g)?g:d.values(g)).length==0};d.isElement=function(g){return !!(g&&g.nodeType==1)};d.isArray=function(g){return Object.prototype.toString.call(g)=="[object Array]"};d.isFunction=function(g){return Object.prototype.toString.call(g)=="[object Function]"};d.isUndefined=function(g){return typeof g=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(g){return g};d.breakLoop=function(){throw"__break__"};var b=0;d.uniqueId=function(g){var h=b++;return g?g+h:h};d.functions=function(){var h=[];for(var g in d){if(Object.prototype.hasOwnProperty.call(d,g)){h.push(g)}}return d.without(h,"VERSION","prototype","noConflict").sort()};d.template=function(j,h){var g=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 h?g(h):g};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(h,g){return g?d(h).chain():h};d.each(d.functions(),function(g){f.prototype[g]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[g].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(g){f.prototype[g]=function(){Array.prototype[g].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(g){f.prototype[g]=function(){return a(Array.prototype[g].apply(this._wrapped,arguments),this._chain)}});f.prototype.chain=function(){this._chain=true;return this};f.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 3dbe9bb9c..039bb04c9 100644 --- a/underscore.js +++ b/underscore.js @@ -25,7 +25,12 @@ var _ = root._ = function(obj) { return new wrapper(obj); }; // Export the Underscore object for CommonJS. - if (typeof exports !== 'undefined') _ = exports; + if (module && typeof module.exports !== 'undefined') { + // richer binding for systems that allow it (node.js for example) + module.exports = _; + } else { + if (typeof exports !== 'undefined') _ = exports; + } // Current version. _.VERSION = '0.4.2'; From b5e1101610ac5a957e685acf9494e0d297b41d5f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 9 Nov 2009 22:17:47 -0500 Subject: [PATCH 054/533] 0.4.3, with fixed export for CommonJS and StopIteration support --- index.html | 14 +++++++++++--- underscore-min.js | 2 +- underscore.js | 16 +++++++--------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index 3502dde88..55a74a7db 100644 --- a/index.html +++ b/index.html @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.4.2)Development Version (0.4.3) 18kb, Uncompressed with Comments
          Production Version (0.4.2)Production Version (0.4.3) 2kb, Packed and Gzipped
          @@ -803,7 +803,9 @@ moe === _.identity(moe); breakLoop_.breakLoop()
          Breaks out of the current loop iteration. Similar to the break - keyword in regular "for" loop, but works within an iterator function. + keyword in regular "for" loop, but works within an iterator function. + Uses the native StopIteration object in JavaScript 1.7 compliant + browsers.

           var result = null;
          @@ -889,6 +891,12 @@ _([1, 2, 3]).value();
           
                 

          Change Log

          +

          + 0.4.3
          + Started using the native StopIteration object in browsers that support it. + Fixed Underscore setup for CommonJS environments. +

          +

          0.4.2
          Renamed the unwrapping function to value, for clarity. diff --git a/underscore-min.js b/underscore-min.js index 20e7c78b1..0e6235f5d 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var c=this;var e=c._;var f=function(g){this._wrapped=g};var d=c._=function(g){return new f(g)};if(module&&typeof module.exports!=='undefined'){module.exports=d}else{if(typeof exports!=='undefined'){d=exports}}d.VERSION="0.4.2";d.each=function(p,n,m){var h=0;try{if(p.forEach){p.forEach(n,m)}else{if(p.length){for(var k=0,g=p.length;k=g.computed&&(g={value:p,computed:n})});return g.value};d.min=function(k,j,h){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var g={computed:Infinity};d.each(k,function(p,m,o){var n=j?j.call(h,p,m,o):p;nk?1:0}),"value")};d.sortedIndex=function(n,m,j){j=j||d.identity;var g=0,k=n.length;while(g>1;j(n[h])=0})})};d.zip=function(){var g=d.toArray(arguments);var k=d.max(d.pluck(g,"length"));var j=new Array(k);for(var h=0;h=0;h--){arguments=[g[h].apply(this,arguments)]}return arguments[0]}};d.keys=function(g){return d.map(g,function(j,h){return h})};d.values=function(g){return d.map(g,d.identity)};d.extend=function(g,j){for(var h in j){g[h]=j[h]}return g};d.clone=function(g){if(d.isArray(g)){return g.slice(0)}return d.extend({},g)};d.isEqual=function(h,g){if(h===g){return true}var m=typeof(h),o=typeof(g);if(m!=o){return false}if(h==g){return true}if(h.isEqual){return h.isEqual(g)}if(m!=="object"){return false}var j=d.keys(h),n=d.keys(g);if(j.length!=n.length){return false}for(var k in h){if(!d.isEqual(h[k],g[k])){return false}}return true};d.isEmpty=function(g){return(d.isArray(g)?g:d.values(g)).length==0};d.isElement=function(g){return !!(g&&g.nodeType==1)};d.isArray=function(g){return Object.prototype.toString.call(g)=="[object Array]"};d.isFunction=function(g){return Object.prototype.toString.call(g)=="[object Function]"};d.isUndefined=function(g){return typeof g=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(g){return g};d.breakLoop=function(){throw"__break__"};var b=0;d.uniqueId=function(g){var h=b++;return g?g+h:h};d.functions=function(){var h=[];for(var g in d){if(Object.prototype.hasOwnProperty.call(d,g)){h.push(g)}}return d.without(h,"VERSION","prototype","noConflict").sort()};d.template=function(j,h){var g=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 h?g(h):g};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(h,g){return g?d(h).chain():h};d.each(d.functions(),function(g){f.prototype[g]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[g].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(g){f.prototype[g]=function(){Array.prototype[g].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(g){f.prototype[g]=function(){return a(Array.prototype[g].apply(this._wrapped,arguments),this._chain)}});f.prototype.chain=function(){this._chain=true;return this};f.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file +(function(){var c=this;var e=c._;var g=function(h){this._wrapped=h};var f=typeof StopIteration!=="undefined"?StopIteration:"__break__";var d=c._=function(h){return new g(h)};if(typeof exports!=="undefined"){exports._=d}d.VERSION="0.4.3";d.each=function(q,o,n){var j=0;try{if(q.forEach){q.forEach(o,n)}else{if(q.length){for(var m=0,h=q.length;m=h.computed&&(h={value:q,computed:o})});return h.value};d.min=function(m,k,j){if(!k&&d.isArray(m)){return Math.min.apply(Math,m)}var h={computed:Infinity};d.each(m,function(q,n,p){var o=k?k.call(j,q,n,p):q;om?1:0}),"value")};d.sortedIndex=function(o,n,k){k=k||d.identity;var h=0,m=o.length;while(h>1;k(o[j])=0})})};d.zip=function(){var h=d.toArray(arguments);var m=d.max(d.pluck(h,"length"));var k=new Array(m);for(var j=0;j=0;j--){arguments=[h[j].apply(this,arguments)]}return arguments[0]}};d.keys=function(h){return d.map(h,function(k,j){return j})};d.values=function(h){return d.map(h,d.identity)};d.extend=function(h,k){for(var j in k){h[j]=k[j]}return h};d.clone=function(h){if(d.isArray(h)){return h.slice(0)}return d.extend({},h)};d.isEqual=function(j,h){if(j===h){return true}var n=typeof(j),p=typeof(h);if(n!=p){return false}if(j==h){return true}if(j.isEqual){return j.isEqual(h)}if(n!=="object"){return false}var k=d.keys(j),o=d.keys(h);if(k.length!=o.length){return false}for(var m in j){if(!d.isEqual(j[m],h[m])){return false}}return true};d.isEmpty=function(h){return(d.isArray(h)?h:d.values(h)).length==0};d.isElement=function(h){return !!(h&&h.nodeType==1)};d.isArray=function(h){return Object.prototype.toString.call(h)=="[object Array]"};d.isFunction=function(h){return Object.prototype.toString.call(h)=="[object Function]"};d.isUndefined=function(h){return typeof h=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(h){return h};d.breakLoop=function(){throw f};var b=0;d.uniqueId=function(h){var j=b++;return h?h+j:j};d.functions=function(){var j=[];for(var h in d){if(Object.prototype.hasOwnProperty.call(d,h)){j.push(h)}}return d.without(j,"VERSION","prototype","noConflict").sort()};d.template=function(k,j){var h=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+k.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 j?h(j):h};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(j,h){return h?d(j).chain():j};d.each(d.functions(),function(h){g.prototype[h]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[h].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(h){g.prototype[h]=function(){Array.prototype[h].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(h){g.prototype[h]=function(){return a(Array.prototype[h].apply(this._wrapped,arguments),this._chain)}});g.prototype.chain=function(){this._chain=true;return this};g.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 039bb04c9..99e738419 100644 --- a/underscore.js +++ b/underscore.js @@ -21,19 +21,17 @@ // underscore functions. Wrapped objects may be chained. var wrapper = function(obj) { this._wrapped = obj; }; + // Establish the object that gets thrown to break out of a loop iteration. + var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__'; + // Create a safe reference to the Underscore object for reference below. var _ = root._ = function(obj) { return new wrapper(obj); }; // Export the Underscore object for CommonJS. - if (module && typeof module.exports !== 'undefined') { - // richer binding for systems that allow it (node.js for example) - module.exports = _; - } else { - if (typeof exports !== 'undefined') _ = exports; - } + if (typeof exports !== 'undefined') exports._ = _; // Current version. - _.VERSION = '0.4.2'; + _.VERSION = '0.4.3'; /*------------------------ Collection Functions: ---------------------------*/ @@ -52,7 +50,7 @@ } } } catch(e) { - if (e != '__break__') throw e; + if (e != breaker) throw e; } return obj; }; @@ -453,7 +451,7 @@ // Break out of the middle of an iteration. _.breakLoop = function() { - throw "__break__"; + throw breaker; }; // Generate a unique integer id (unique within the entire client session). From 80939448a70ee494a562aacc055228bce6d893c6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 12 Nov 2009 21:31:03 -0500 Subject: [PATCH 055/533] fixing broken _.breakLoop test -- implementation was fine, test was out of date. --- test/collections.js | 74 ++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/test/collections.js b/test/collections.js index 1a6f5992d..c0bfe1ee8 100644 --- a/test/collections.js +++ b/test/collections.js @@ -1,89 +1,89 @@ $(document).ready(function() { - + module("Collection functions (each, any, select, and so on...)"); - + test("collections: each", function() { _.each([1, 2, 3], function(num, i) { equals(num, i + 1, 'each iterators provide value and iteration count'); }); - + var answer = null; - _.each([1, 2, 3], function(num){ if ((answer = num) == 2) throw '__break__'; }); + _.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'); - + answers = []; _.each("moe", function(letter){ answers.push(letter); }); equals(answers.join(', '), 'm, o, e', 'iterates over the letters in strings'); - + answers = []; _.forEach([1, 2, 3], function(num){ answers.push(num); }); equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); - + answers = []; var obj = {one : 1, two : 2, three : 3}; obj.constructor.prototype.four = 4; _.each(obj, function(value, key){ answers.push(key); }); equals(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.'); delete obj.constructor.prototype.four; - + answer = null; _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); - ok(answer, 'can reference the original collection from inside the iterator'); + ok(answer, 'can reference the original collection from inside the iterator'); }); - + test('collections: map', function() { var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); equals(doubled.join(', '), '2, 4, 6', 'doubled numbers'); - + var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3}); equals(tripled.join(', '), '3, 6, 9', 'tripled numbers with context'); - + var doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); equals(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers'); }); - + test('collections: reduce', function() { var sum = _.reduce([1, 2, 3], 0, function(sum, num){ return sum + num; }); equals(sum, 6, 'can sum up an array'); - + var context = {multiplier : 3}; sum = _.reduce([1, 2, 3], 0, function(sum, num){ return sum + num * this.multiplier; }, context); equals(sum, 18, 'can reduce with a context object'); - + sum = _.inject([1, 2, 3], 0, function(sum, num){ return sum + num; }); equals(sum, 6, 'aliased as "inject"'); - + sum = _([1, 2, 3]).reduce(0, function(sum, num){ return sum + num; }); equals(sum, 6, 'OO-style reduce'); }); - + test('collections: reduceRight', function() { var list = _.foldr([1, 2, 3], '', function(memo, num){ return memo + num; }); equals(list, '321', 'can perform right folds'); }); - + test('collections: detect', function() { var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; }); equals(result, 2, 'found the first "2" and broke the loop'); }); - + test('collections: select', function() { var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equals(evens.join(', '), '2, 4, 6', 'selected each even number'); - + evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equals(evens.join(', '), '2, 4, 6', 'aliased as "filter"'); }); - + test('collections: reject', function() { var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equals(odds.join(', '), '1, 3, 5', 'rejected each even number'); }); - + test('collections: all', function() { ok(_.all([]), 'the empty set'); ok(_.all([true, true, true]), 'all true values'); @@ -92,7 +92,7 @@ $(document).ready(function() { ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); ok(_.every([true, true, true]), 'aliased as "every"'); }); - + test('collections: any', function() { ok(!_.any([]), 'the empty set'); ok(!_.any([false, false, false]), 'all false values'); @@ -101,62 +101,62 @@ $(document).ready(function() { ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); ok(_.some([false, false, true]), 'aliased as "some"'); }); - + test('collections: include', function() { ok(_.include([1,2,3], 2), 'two is in the array'); ok(!_.include([1,3,9], 2), 'two is not in the array'); ok(_.include({moe:1, larry:3, curly:9}, 3), '_.include on objects checks their values'); ok(_([1,2,3]).include(2), 'OO-style include'); }); - + test('collections: invoke', function() { var list = [[5, 1, 7], [3, 2, 1]]; var result = _.invoke(list, 'sort'); equals(result[0].join(', '), '1, 5, 7', 'first array sorted'); equals(result[1].join(', '), '1, 2, 3', 'second array sorted'); }); - + test('collections: pluck', function() { var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; equals(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects'); }); - + test('collections: max', function() { equals(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); - + var neg = _.max([1, 2, 3], function(num){ return -num; }); equals(neg, 1, 'can perform a computation-based max'); }); - + test('collections: min', function() { equals(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); - + var neg = _.min([1, 2, 3], function(num){ return -num; }); equals(neg, 3, 'can perform a computation-based min'); }); - + test('collections: sortBy', function() { var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}]; people = _.sortBy(people, function(person){ return person.age; }); equals(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age'); }); - + test('collections: sortedIndex', function() { var numbers = [10, 20, 30, 40, 50], num = 35; var index = _.sortedIndex(numbers, num); equals(index, 3, '35 should be inserted at index 3'); }); - + test('collections: toArray', function() { ok(!_.isArray(arguments), 'arguments object is not an array'); ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); - + var numbers = _.toArray({one : 1, two : 2, three : 3}); equals(numbers.join(', '), '1, 2, 3', 'object flattened into array'); }); - + test('collections: size', function() { equals(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object'); }); - + }); From b932867dec302b6a36cab60aa664b2081391b791 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 14 Nov 2009 08:31:42 -0500 Subject: [PATCH 056/533] regression fix: implied global 'i' in _.indexOf() -- thanks to Sveinung Rosaker. --- underscore-min.js | 2 +- underscore.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/underscore-min.js b/underscore-min.js index 0e6235f5d..dcc64d912 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -(function(){var c=this;var e=c._;var g=function(h){this._wrapped=h};var f=typeof StopIteration!=="undefined"?StopIteration:"__break__";var d=c._=function(h){return new g(h)};if(typeof exports!=="undefined"){exports._=d}d.VERSION="0.4.3";d.each=function(q,o,n){var j=0;try{if(q.forEach){q.forEach(o,n)}else{if(q.length){for(var m=0,h=q.length;m=h.computed&&(h={value:q,computed:o})});return h.value};d.min=function(m,k,j){if(!k&&d.isArray(m)){return Math.min.apply(Math,m)}var h={computed:Infinity};d.each(m,function(q,n,p){var o=k?k.call(j,q,n,p):q;om?1:0}),"value")};d.sortedIndex=function(o,n,k){k=k||d.identity;var h=0,m=o.length;while(h>1;k(o[j])=0})})};d.zip=function(){var h=d.toArray(arguments);var m=d.max(d.pluck(h,"length"));var k=new Array(m);for(var j=0;j=0;j--){arguments=[h[j].apply(this,arguments)]}return arguments[0]}};d.keys=function(h){return d.map(h,function(k,j){return j})};d.values=function(h){return d.map(h,d.identity)};d.extend=function(h,k){for(var j in k){h[j]=k[j]}return h};d.clone=function(h){if(d.isArray(h)){return h.slice(0)}return d.extend({},h)};d.isEqual=function(j,h){if(j===h){return true}var n=typeof(j),p=typeof(h);if(n!=p){return false}if(j==h){return true}if(j.isEqual){return j.isEqual(h)}if(n!=="object"){return false}var k=d.keys(j),o=d.keys(h);if(k.length!=o.length){return false}for(var m in j){if(!d.isEqual(j[m],h[m])){return false}}return true};d.isEmpty=function(h){return(d.isArray(h)?h:d.values(h)).length==0};d.isElement=function(h){return !!(h&&h.nodeType==1)};d.isArray=function(h){return Object.prototype.toString.call(h)=="[object Array]"};d.isFunction=function(h){return Object.prototype.toString.call(h)=="[object Function]"};d.isUndefined=function(h){return typeof h=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(h){return h};d.breakLoop=function(){throw f};var b=0;d.uniqueId=function(h){var j=b++;return h?h+j:j};d.functions=function(){var j=[];for(var h in d){if(Object.prototype.hasOwnProperty.call(d,h)){j.push(h)}}return d.without(j,"VERSION","prototype","noConflict").sort()};d.template=function(k,j){var h=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+k.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 j?h(j):h};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(j,h){return h?d(j).chain():j};d.each(d.functions(),function(h){g.prototype[h]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[h].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(h){g.prototype[h]=function(){Array.prototype[h].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(h){g.prototype[h]=function(){return a(Array.prototype[h].apply(this._wrapped,arguments),this._chain)}});g.prototype.chain=function(){this._chain=true;return this};g.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file +(function(){var c=this;var e=c._;var g=function(h){this._wrapped=h};var f=typeof StopIteration!=="undefined"?StopIteration:"__break__";var d=c._=function(h){return new g(h)};if(typeof exports!=="undefined"){exports._=d}d.VERSION="0.4.3";d.each=function(q,o,n){var j=0;try{if(q.forEach){q.forEach(o,n)}else{if(q.length){for(var m=0,h=q.length;m=h.computed&&(h={value:o,computed:m})});return h.value};d.min=function(k,j,i){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var h={computed:Infinity};d.each(k,function(o,l,n){var m=j?j.call(i,o,l,n):o;mk?1:0}),"value")};d.sortedIndex=function(m,l,j){j=j||d.identity;var h=0,k=m.length;while(h>1;j(m[i])=0})})};d.zip=function(){var h=d.toArray(arguments);var l=d.max(d.pluck(h,"length"));var k=new Array(l);for(var j=0;j=0;j--){arguments=[h[j].apply(this,arguments)]}return arguments[0]}};d.keys=function(h){return d.map(h,function(j,i){return i})};d.values=function(h){return d.map(h,d.identity)};d.extend=function(h,j){for(var i in j){h[i]=j[i]}return h};d.clone=function(h){if(d.isArray(h)){return h.slice(0)}return d.extend({},h)};d.isEqual=function(i,h){if(i===h){return true}var l=typeof(i),n=typeof(h);if(l!=n){return false}if(i==h){return true}if(i.isEqual){return i.isEqual(h)}if(l!=="object"){return false}var j=d.keys(i),m=d.keys(h);if(j.length!=m.length){return false}for(var k in i){if(!d.isEqual(i[k],h[k])){return false}}return true};d.isEmpty=function(h){return(d.isArray(h)?h:d.values(h)).length==0};d.isElement=function(h){return !!(h&&h.nodeType==1)};d.isArray=function(h){return Object.prototype.toString.call(h)=="[object Array]"};d.isFunction=function(h){return Object.prototype.toString.call(h)=="[object Function]"};d.isUndefined=function(h){return typeof h=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(h){return h};d.breakLoop=function(){throw f};var b=0;d.uniqueId=function(h){var i=b++;return h?h+i:i};d.functions=function(){var i=[];for(var h in d){if(Object.prototype.hasOwnProperty.call(d,h)){i.push(h)}}return d.without(i,"VERSION","prototype","noConflict").sort()};d.template=function(j,i){var h=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 i?h(i):h};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(i,h){return h?d(i).chain():i};d.each(d.functions(),function(h){g.prototype[h]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[h].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(h){g.prototype[h]=function(){Array.prototype[h].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(h){g.prototype[h]=function(){return a(Array.prototype[h].apply(this._wrapped,arguments),this._chain)}});g.prototype.chain=function(){this._chain=true;return this};g.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 99e738419..4a85af41f 100644 --- a/underscore.js +++ b/underscore.js @@ -293,7 +293,7 @@ // item in an array, or -1 if the item is not included in the array. _.indexOf = function(array, item) { if (array.indexOf) return array.indexOf(item); - for (i=0, l=array.length; i Date: Wed, 18 Nov 2009 16:09:55 -0500 Subject: [PATCH 057/533] Underscore 0.4.4, with isNumber, isString, and isEqual(NaN, NaN) --- index.html | 33 ++++++++++++++++++++++++++++++--- test/objects.js | 40 ++++++++++++++++++++++++++-------------- test/test.html | 2 +- test/utility.js | 30 +++++++++++++++--------------- underscore-min.js | 15 ++++++++++++++- underscore.js | 14 +++++++++++++- 6 files changed, 99 insertions(+), 35 deletions(-) diff --git a/index.html b/index.html index 55a74a7db..f0bc7a335 100644 --- a/index.html +++ b/index.html @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.4.3)Development Version (0.4.4) 18kb, Uncompressed with Comments
          Production Version (0.4.3)Production Version (0.4.4) 2kb, Packed and Gzipped
          @@ -202,7 +202,8 @@ _(lyrics).chain() keys, values, extend, clone, isEqual, isEmpty, isElement, - isArray, isFunction, isUndefined + isArray, isFunction, isString, + isNumber, isUndefined

          @@ -763,6 +764,26 @@ _.isArray([1,2,3]);
           _.isFunction(alert);
           => true
          +
          + +

          + isString_.isString(object) +
          + Returns true if object is a String. +

          +
          +_.isFunction("moe");
          +=> true
          +
          + +

          + isNumber_.isNumber(object) +
          + Returns true if object is a Number. +

          +
          +_.isNumber(8.4 * 5);
          +=> true
           

          @@ -891,6 +912,12 @@ _([1, 2, 3]).value();

          Change Log

          +

          + 0.4.4
          + Added isString, and isNumber, for consistency. Fixed + _.isEqual(NaN, NaN) to return true (which is debatable). +

          +

          0.4.3
          Started using the native StopIteration object in browsers that support it. diff --git a/test/objects.js b/test/objects.js index 489c39f2a..d76d3e8e2 100644 --- a/test/objects.js +++ b/test/objects.js @@ -1,68 +1,80 @@ $(document).ready(function() { - + module("Object functions (values, extend, isEqual, and so on...)"); - + test("objects: keys", function() { equals(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object'); }); - + test("objects: values", function() { equals(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object'); }); - + test("objects: extend", function() { var source = {name : 'moe'}, dest = {age : 50}; _.extend(dest, source); equals(dest.name, 'moe', 'can extend an object with the attributes of another'); }); - + test("objects: clone", function() { var moe = {name : 'moe', lucky : [13, 27, 34]}; var clone = _.clone(moe); equals(clone.name, 'moe', 'the clone as the attributes of the original'); - + clone.name = 'curly'; ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original'); - + clone.lucky.push(101); equals(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original'); }); - + test("objects: isEqual", function() { var moe = {name : 'moe', lucky : [13, 27, 34]}; var clone = {name : 'moe', lucky : [13, 27, 34]}; ok(moe != clone, 'basic equality between objects is false'); ok(_.isEqual(moe, clone), 'deep equality is true'); ok(_(moe).isEqual(clone), 'OO-style deep equality works'); + ok(!_.isEqual(5, NaN), '5 is not equal to NaN'); + ok(_.isEqual(NaN, NaN), 'NaN is equal to NaN'); }); - + test("objects: isEmpty", function() { ok(!_([1]).isEmpty(), '[1] is not empty'); ok(_.isEmpty([]), '[] is empty'); ok(!_.isEmpty({one : 1}), '{one : 1} is not empty'); ok(_.isEmpty({}), '{} is empty'); - + var obj = {one : 1}; delete obj.one; ok(_.isEmpty(obj), 'deleting all the keys from an object empties it'); }); - + test("objects: isElement", function() { ok(!_.isElement('div'), 'strings are not dom elements'); ok(_.isElement($('html')[0]), 'the html tag is a DOM element'); }); - + test("objects: isArray", function() { ok(!_.isArray(arguments), 'the arguments object is not an array'); ok(_.isArray([1, 2, 3]), 'but arrays are'); }); - + + test("objects: isString", function() { + ok(!_.isString(document.body), 'the document body is not a string'); + ok(_.isString([1, 2, 3].join(', ')), 'but strings are'); + }); + + test("objects: isNumber", function() { + ok(!_.isNumber(arguments), 'the arguments object is not a number'); + ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are'); + }); + test("objects: isFunction", function() { ok(!_.isFunction([1, 2, 3]), 'arrays are not functions'); ok(!_.isFunction('moe'), 'strings are not functions'); ok(_.isFunction(_.isFunction), 'but functions are'); }); - + test("objects: isUndefined", function() { ok(!_.isUndefined(1), 'numbers are defined'); ok(!_.isUndefined(null), 'null is defined'); diff --git a/test/test.html b/test/test.html index 30ac9098f..ea26345fb 100644 --- a/test/test.html +++ b/test/test.html @@ -13,7 +13,7 @@ - +

          Underscore Test Suite

          diff --git a/test/utility.js b/test/utility.js index 4515ba4c4..ed6ee120b 100644 --- a/test/utility.js +++ b/test/utility.js @@ -1,7 +1,7 @@ $(document).ready(function() { - + module("Utility functions (uniqueId, template)"); - + test("utility: noConflict", function() { var underscore = _.noConflict(); ok(underscore.isUndefined(_), "The '_' variable has been returned to its previous state."); @@ -9,12 +9,12 @@ $(document).ready(function() { equals(intersection.join(', '), '1, 2', 'but the intersection function still works'); window._ = underscore; }); - + test("utility: identity", function() { var moe = {name : 'moe'}; 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) { @@ -23,25 +23,25 @@ $(document).ready(function() { }); equals(result, 3, 'broke out of a loop'); }); - + test("utility: uniqueId", function() { var ids = [], i = 0; while(i++ < 100) ids.push(_.uniqueId()); 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", "identity", "include", - "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", - "isFunction", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", - "methods", "min", "pluck", "reduce", "reduceRight", "reject", "select", - "size", "some", "sortBy", "sortedIndex", "template", "toArray", "uniq", + var expected = ["all", "any", "bind", "bindAll", "breakLoop", "clone", "compact", "compose", + "defer", "delay", "detect", "each", "every", "extend", "filter", "first", + "flatten", "foldl", "foldr", "forEach", "functions", "identity", "include", + "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", + "isFunction", "isNumber", "isString", "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'}); @@ -50,5 +50,5 @@ $(document).ready(function() { result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}}); equals(result, "
        1. Moe
        2. Larry
        3. Curly
        4. ", 'can run arbitrary javascript in templates'); }); - + }); diff --git a/underscore-min.js b/underscore-min.js index dcc64d912..dbaff85cd 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1,14 @@ -(function(){var c=this;var e=c._;var g=function(h){this._wrapped=h};var f=typeof StopIteration!=="undefined"?StopIteration:"__break__";var d=c._=function(h){return new g(h)};if(typeof exports!=="undefined"){exports._=d}d.VERSION="0.4.3";d.each=function(q,o,n){var j=0;try{if(q.forEach){q.forEach(o,n)}else{if(q.length){for(var m=0,h=q.length;m=h.computed&&(h={value:o,computed:m})});return h.value};d.min=function(k,j,i){if(!j&&d.isArray(k)){return Math.min.apply(Math,k)}var h={computed:Infinity};d.each(k,function(o,l,n){var m=j?j.call(i,o,l,n):o;mk?1:0}),"value")};d.sortedIndex=function(m,l,j){j=j||d.identity;var h=0,k=m.length;while(h>1;j(m[i])=0})})};d.zip=function(){var h=d.toArray(arguments);var l=d.max(d.pluck(h,"length"));var k=new Array(l);for(var j=0;j=0;j--){arguments=[h[j].apply(this,arguments)]}return arguments[0]}};d.keys=function(h){return d.map(h,function(j,i){return i})};d.values=function(h){return d.map(h,d.identity)};d.extend=function(h,j){for(var i in j){h[i]=j[i]}return h};d.clone=function(h){if(d.isArray(h)){return h.slice(0)}return d.extend({},h)};d.isEqual=function(i,h){if(i===h){return true}var l=typeof(i),n=typeof(h);if(l!=n){return false}if(i==h){return true}if(i.isEqual){return i.isEqual(h)}if(l!=="object"){return false}var j=d.keys(i),m=d.keys(h);if(j.length!=m.length){return false}for(var k in i){if(!d.isEqual(i[k],h[k])){return false}}return true};d.isEmpty=function(h){return(d.isArray(h)?h:d.values(h)).length==0};d.isElement=function(h){return !!(h&&h.nodeType==1)};d.isArray=function(h){return Object.prototype.toString.call(h)=="[object Array]"};d.isFunction=function(h){return Object.prototype.toString.call(h)=="[object Function]"};d.isUndefined=function(h){return typeof h=="undefined"};d.noConflict=function(){c._=e;return this};d.identity=function(h){return h};d.breakLoop=function(){throw f};var b=0;d.uniqueId=function(h){var i=b++;return h?h+i:i};d.functions=function(){var i=[];for(var h in d){if(Object.prototype.hasOwnProperty.call(d,h)){i.push(h)}}return d.without(i,"VERSION","prototype","noConflict").sort()};d.template=function(j,i){var h=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+j.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 i?h(i):h};d.forEach=d.each;d.foldl=d.inject=d.reduce;d.foldr=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.any;d.methods=d.functions;var a=function(i,h){return h?d(i).chain():i};d.each(d.functions(),function(h){g.prototype[h]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return a(d[h].apply(d,arguments),this._chain)}});d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(h){g.prototype[h]=function(){Array.prototype[h].apply(this._wrapped,arguments);return a(this._wrapped,this._chain)}});d.each(["concat","join","slice"],function(h){g.prototype[h]=function(){return a(Array.prototype[h].apply(this._wrapped,arguments),this._chain)}});g.prototype.chain=function(){this._chain=true;return this};g.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file +(function(){var j=this,m=j._;function i(a){this._wrapped=a}var l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.4.4";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); +return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a,function(c,d){return d})};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]= +c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length== +0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)=="[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)=="[object Number]"};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=m;return this};b.identity= +function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.methods=b.functions;function k(a,c){return c?b(a).chain():a}b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift", +"sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index 4a85af41f..ac1b8659d 100644 --- a/underscore.js +++ b/underscore.js @@ -31,7 +31,7 @@ if (typeof exports !== 'undefined') exports._ = _; // Current version. - _.VERSION = '0.4.3'; + _.VERSION = '0.4.4'; /*------------------------ Collection Functions: ---------------------------*/ @@ -399,6 +399,8 @@ if (a == b) return true; // One of them implements an isEqual()? if (a.isEqual) return a.isEqual(b); + // Both are NaN? + if (_.isNumber(a) && _.isNumber(b) && isNaN(a) && isNaN(b)) return true; // If a is not an object by this point, we can't handle it. if (atype !== 'object') return false; // Nothing else worked, deep compare the contents. @@ -430,6 +432,16 @@ return Object.prototype.toString.call(obj) == '[object Function]'; }; + // Is a given value a String? + _.isString = function(obj) { + return Object.prototype.toString.call(obj) == '[object String]'; + }; + + // Is a given value a Number? + _.isNumber = function(obj) { + return Object.prototype.toString.call(obj) == '[object Number]'; + }; + // Is a given variable undefined? _.isUndefined = function(obj) { return typeof obj == 'undefined'; From 86e6dd58c2817b40a1249e452cc915f3375e79a5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 18 Nov 2009 16:11:35 -0500 Subject: [PATCH 058/533] code-ify stopIteration in the docs --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index f0bc7a335..ff1a6dc9e 100644 --- a/index.html +++ b/index.html @@ -920,7 +920,7 @@ _([1, 2, 3]).value();

          0.4.3
          - Started using the native StopIteration object in browsers that support it. + Started using the native StopIteration object in browsers that support it. Fixed Underscore setup for CommonJS environments.

          From 8cac2d5bd75c606fa987a97a97213e48974ed7f0 Mon Sep 17 00:00:00 2001 From: Luke Sutton Date: Thu, 19 Nov 2009 11:07:14 +1030 Subject: [PATCH 059/533] Add init(), tail() and reverse() Array functions. Alias first() to head(); --- test/arrays.js | 15 +++++++++++++++ underscore.js | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/test/arrays.js b/test/arrays.js index d069d9d62..a9dcb4e90 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -58,4 +58,19 @@ $(document).ready(function() { equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); }); + test("arrays: tail", function() { + var numbers = [1, 2, 3, 4]; + equals(_.tail(numbers).join(", "), "2, 3, 4"); + }); + + test("arrays: init", function() { + var numbers = [1, 2, 3, 4]; + equals(_.init(numbers).join(", "), "1, 2, 3"); + }); + + test("arrays: reverse", function() { + var numbers = [1, 2, 4, 6]; + equals(_.reverse(numbers).join(", "), "6, 4, 2, 1"); + }); + }); diff --git a/underscore.js b/underscore.js index ac1b8659d..ad6dbecac 100644 --- a/underscore.js +++ b/underscore.js @@ -305,6 +305,29 @@ while (i--) if (array[i] === item) return i; return -1; }; + + // Returns everything but the first entry of the array. Conceptually the + // same as calling shift(), but doesn't mutate the array passed in. + _.tail = function(array) { + var tail = _.clone(array); + tail.shift(); + return tail; + }; + + // Returns everything but the last entry of the array. Conceptually the + // same as calling pop(), but doesn't mutate the array passed in. + _.init = function(array) { + var init = _.clone(array); + init.pop(); + return init; + }; + + // Returns a new array, with the entries or the passed-in array in reverse + // order. + _.reverse = function(array) { + var reverse = _.clone(array); + return reverse.reverse(); + }; /* ----------------------- Function Functions: -----------------------------*/ @@ -501,6 +524,7 @@ /*------------------------------- Aliases ----------------------------------*/ + _.head = _.first; _.forEach = _.each; _.foldl = _.inject = _.reduce; _.foldr = _.reduceRight; From 4ed79d5f778e8e8eb5fa0232545ac719cf4c9d42 Mon Sep 17 00:00:00 2001 From: Luke Sutton Date: Thu, 19 Nov 2009 11:35:21 +1030 Subject: [PATCH 060/533] Correct the test for functions() to account for the new functions and aliases. --- test/utility.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/utility.js b/test/utility.js index ed6ee120b..0acc6a16d 100644 --- a/test/utility.js +++ b/test/utility.js @@ -31,13 +31,13 @@ $(document).ready(function() { }); 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", "identity", "include", - "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", + 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", "init", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", "isFunction", "isNumber", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", - "methods", "min", "pluck", "reduce", "reduceRight", "reject", "select", - "size", "some", "sortBy", "sortedIndex", "template", "toArray", "uniq", + "methods", "min", "pluck", "reduce", "reduceRight", "reject", "reverse", "select", + "size", "some", "sortBy", "sortedIndex", "tail", "template", "toArray", "uniq", "uniqueId", "values", "without", "wrap", "zip"]; ok(_(expected).isEqual(_.methods()), 'provides a sorted list of functions'); }); From f8e939d30ac53ba672e0f94a20f680d2b6badcfa Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 18 Nov 2009 21:54:50 -0500 Subject: [PATCH 061/533] fixing template test validation for IE --- test/utility.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utility.js b/test/utility.js index ed6ee120b..16cb01013 100644 --- a/test/utility.js +++ b/test/utility.js @@ -46,9 +46,9 @@ $(document).ready(function() { var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); var result = basicTemplate({thing : 'This'}); equals(result, "This is gettin' on my noives!", 'can do basic attribute interpolation'); - var fancyTemplate = _.template("<% for (key in people) { %>
        5. <%= people[key] %>
        6. <% } %>"); + var fancyTemplate = _.template("
            <% for (key in people) { %>
          • <%= people[key] %>
          • <% } %>
          "); result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}}); - equals(result, "
        7. Moe
        8. Larry
        9. Curly
        10. ", 'can run arbitrary javascript in templates'); + equals(result, "
          • Moe
          • Larry
          • Curly
          ", 'can run arbitrary javascript in templates'); }); }); From ae968a6ea06dd4fd6f5184b3e6d14f9a077b3d01 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 19 Nov 2009 09:37:56 -0500 Subject: [PATCH 062/533] Underscore 0.4.5, with first/rest, head/tail, and all Array functions guaranteed to work on 'arguments' objects. Many method implementations reworked to use _.rest() --- index.html | 43 +++++++++++++++++++++++------ test/arrays.js | 69 ++++++++++++++++++++++++++++------------------- test/utility.js | 6 ++--- underscore-min.js | 22 +++++++-------- underscore.js | 63 ++++++++++++++++--------------------------- 5 files changed, 113 insertions(+), 90 deletions(-) diff --git a/index.html b/index.html index ff1a6dc9e..c2b1ac241 100644 --- a/index.html +++ b/index.html @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.4.4)Development Version (0.4.5) 18kb, Uncompressed with Comments
          Production Version (0.4.4)Production Version (0.4.5) 2kb, Packed and Gzipped
          @@ -183,7 +183,7 @@ _(lyrics).chain()

          Arrays
          - first, last, + first, rest, last, compact, flatten, without, uniq, intersect, zip, indexOf, lastIndexOf @@ -454,15 +454,33 @@ _.size({one : 1, two : 2, three : 3});

          Array Functions

          + +

          + Note: All array functions will also work on the arguments object. +

          - first_.first(array) + first_.first(array, [n]) + Alias: head
          - Convenience to return the first element of an array (identical to array[0]). + Returns the first element of an array. Passing n will + return the first n elements of the array.

          -_.first([3, 2, 1]);
          -=> 3
          +_.first([5, 4, 3, 2, 1]);
          +=> 5
          +
          + +

          + rest_.rest(array, [index]) + Alias: tail +
          + Returns the rest of the elements in an array. Pass an index + to return the values of the array from that index onward. +

          +
          +_.rest([5, 4, 3, 2, 1]);
          +=> [4, 3, 2, 1]
           

          @@ -471,7 +489,7 @@ _.first([3, 2, 1]); Returns the last element of an array.

          -_.last([3, 2, 1]);
          +_.last([5, 4, 3, 2, 1]);
           => 1
           
          @@ -912,6 +930,15 @@ _([1, 2, 3]).value();

          Change Log

          +

          + 0.4.5
          + Added rest for Arrays and arguments objects, and aliased + first as head, and rest as tail, + thanks to Luke Sutton's patches. + Added tests ensuring that all Underscore Array functions also work on + arguments objects. +

          +

          0.4.4
          Added isString, and isNumber, for consistency. Fixed diff --git a/test/arrays.js b/test/arrays.js index a9dcb4e90..5464756f6 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -1,76 +1,89 @@ $(document).ready(function() { - + module("Array-only functions (last, compact, uniq, and so on...)"); - + test("arrays: first", function() { equals(_.first([1,2,3]), 1, 'can pull out the first element of an array'); equals(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"'); + equals(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first'); + var result = (function(){ return _.first(arguments); })(4, 3, 2, 1); + equals(result, 4, 'works on an arguments object.'); }); - + + test("arrays: rest", function() { + var numbers = [1, 2, 3, 4]; + equals(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()'); + equals(_.rest(numbers, 2).join(', '), '3, 4', 'rest can take an index'); + var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4); + equals(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object'); + }); + test("arrays: last", function() { equals(_.last([1,2,3]), 3, 'can pull out the last element of an array'); + var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4); + equals(result, 4, 'works on an arguments object'); }); - + test("arrays: compact", function() { equals(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values'); + var result = (function(){ return _(arguments).compact().length; })(0, 1, false, 2, false, 3); + equals(result, 3, 'works on an arguments object'); }); - + test("arrays: flatten", function() { var list = [1, [2], [3, [[[4]]]]]; equals(_.flatten(list).join(', '), '1, 2, 3, 4', 'can flatten nested arrays'); + var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]); + equals(result.join(', '), '1, 2, 3, 4', 'works on an arguments object'); }); - + test("arrays: without", function() { var list = [1, 2, 1, 0, 3, 1, 4]; equals(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object'); + var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4); + equals(result.join(', '), '2, 3, 4', 'works on an arguments object'); }); - + test("arrays: uniq", function() { var list = [1, 2, 1, 3, 1, 4]; equals(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array'); - + var list = [1, 1, 1, 2, 2, 3]; equals(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster'); + + var result = (function(){ return _.uniq(arguments); })(1, 2, 1, 3, 1, 4); + equals(result.join(', '), '1, 2, 3, 4', 'works on an arguments object'); }); - + test("arrays: intersect", function() { var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; equals(_.intersect(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays'); equals(_(stooges).intersect(leaders).join(''), 'moe', 'can perform an OO-style intersection'); + var result = (function(){ return _.intersect(arguments, leaders); })('moe', 'curly', 'larry'); + equals(result.join(''), 'moe', 'works an an arguments object'); }); - + test('arrays: zip', function() { var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true]; var stooges = _.zip(names, ages, leaders); equals(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths'); }); - + test("arrays: indexOf", function() { var numbers = [1, 2, 3]; numbers.indexOf = null; equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); + var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3); + equals(result, 1, 'works on an arguments object'); }); - + test("arrays: lastIndexOf", function() { var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; numbers.lastIndexOf = null; equals(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); + var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0); + equals(result, 5, 'works on an arguments object'); }); - - test("arrays: tail", function() { - var numbers = [1, 2, 3, 4]; - equals(_.tail(numbers).join(", "), "2, 3, 4"); - }); - - test("arrays: init", function() { - var numbers = [1, 2, 3, 4]; - equals(_.init(numbers).join(", "), "1, 2, 3"); - }); - - test("arrays: reverse", function() { - var numbers = [1, 2, 4, 6]; - equals(_.reverse(numbers).join(", "), "6, 4, 2, 1"); - }); - + }); diff --git a/test/utility.js b/test/utility.js index 897d0ceec..6fd19af93 100644 --- a/test/utility.js +++ b/test/utility.js @@ -31,12 +31,12 @@ $(document).ready(function() { }); test("utility: functions", function() { - var expected = ["all", "any", "bind", "bindAll", "breakLoop", "clone", "compact", + 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", "init", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", + "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", "isFunction", "isNumber", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", - "methods", "min", "pluck", "reduce", "reduceRight", "reject", "reverse", "select", + "methods", "min", "pluck", "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'); diff --git a/underscore-min.js b/underscore-min.js index dbaff85cd..f8466a228 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,14 +1,14 @@ -(function(){var j=this,m=j._;function i(a){this._wrapped=a}var l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.4.4";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); +return e};b.include=function(a,c){if(b.isArray(a))return b.indexOf(a,c)!=-1;var d=false;b.each(a,function(e){if(d=e===c)b.breakLoop()});return d};b.invoke=function(a,c){var d=b.rest(arguments,2);return b.map(a,function(e){return(c?e[c]:e).apply(e,d)})};b.pluck=function(a,c){return b.map(a,function(d){return d[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})}); return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a,function(c,d){return d})};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]= -c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length== -0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)=="[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)=="[object Number]"};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=m;return this};b.identity= -function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.methods=b.functions;function k(a,c){return c?b(a).chain():a}b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift", -"sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); +(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(b.isArray(a))return a;return b.map(a,function(c){return c})};b.size=function(a){return b.toArray(a).length};b.first=function(a,c){return c?Array.prototype.slice.call(a,0,c):a[0]};b.rest=function(a,c){return Array.prototype.slice.call(a,b.isUndefined(c)?1:c)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.select(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a,[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d)); +c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.select(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c?b.last(d)!=e:!b.include(d,e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.select(b.uniq(a),function(d){return b.all(c,function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a,function(c,d){return d})};b.values=function(a){return b.map(a, +b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true}; +b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)=="[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)=="[object Number]"};b.isUndefined=function(a){return typeof a== +"undefined"};b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;function k(a,c){return c?b(a).chain():a}b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments), +this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index ad6dbecac..69851fb02 100644 --- a/underscore.js +++ b/underscore.js @@ -31,7 +31,7 @@ if (typeof exports !== 'undefined') exports._ = _; // Current version. - _.VERSION = '0.4.4'; + _.VERSION = '0.4.5'; /*------------------------ Collection Functions: ---------------------------*/ @@ -156,7 +156,7 @@ // Invoke a method with arguments on every item in a collection. _.invoke = function(obj, method) { - var args = _.toArray(arguments).slice(2); + var args = _.rest(arguments, 2); return _.map(obj, function(value) { return (method ? value[method] : value).apply(value, args); }); @@ -228,9 +228,17 @@ /*-------------------------- Array Functions: ------------------------------*/ - // Get the first element of an array. - _.first = function(array) { - return array[0]; + // Get the first element of an array. Passing "n" will return the first N + // values in the array. Aliased as "head". + _.first = function(array, n) { + return n ? Array.prototype.slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the first entry of the array. Aliased as "tail". + // Especially useful on the arguments object. Passing an "index" will return + // the rest of the values in the array from that index onward. + _.rest = function(array, index) { + return Array.prototype.slice.call(array, _.isUndefined(index) ? 1 : index); }; // Get the last element of an array. @@ -254,7 +262,7 @@ // Return a version of the array that does not contain the specified value(s). _.without = function(array) { - var values = array.slice.call(arguments, 0); + var values = _.rest(arguments); return _.select(array, function(value){ return !_.include(values, value); }); }; @@ -270,7 +278,7 @@ // Produce an array that contains every item shared between all the // passed-in arrays. _.intersect = function(array) { - var rest = _.toArray(arguments).slice(1); + var rest = _.rest(arguments); return _.select(_.uniq(array), function(item) { return _.all(rest, function(other) { return _.indexOf(other, item) >= 0; @@ -305,49 +313,23 @@ while (i--) if (array[i] === item) return i; return -1; }; - - // Returns everything but the first entry of the array. Conceptually the - // same as calling shift(), but doesn't mutate the array passed in. - _.tail = function(array) { - var tail = _.clone(array); - tail.shift(); - return tail; - }; - - // Returns everything but the last entry of the array. Conceptually the - // same as calling pop(), but doesn't mutate the array passed in. - _.init = function(array) { - var init = _.clone(array); - init.pop(); - return init; - }; - - // Returns a new array, with the entries or the passed-in array in reverse - // order. - _.reverse = function(array) { - var reverse = _.clone(array); - return reverse.reverse(); - }; /* ----------------------- Function Functions: -----------------------------*/ // 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) { - context = context || root; - var args = _.toArray(arguments).slice(2); + var args = _.rest(arguments, 2); return function() { - var a = args.concat(_.toArray(arguments)); - return func.apply(context, a); + return func.apply(context || 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 args = _.toArray(arguments); - var context = args.pop(); - _.each(args, function(methodName) { + var context = Array.prototype.pop.call(arguments); + _.each(arguments, function(methodName) { context[methodName] = _.bind(context[methodName], context); }); }; @@ -355,14 +337,14 @@ // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { - var args = _.toArray(arguments).slice(2); + var args = _.rest(arguments, 2); return setTimeout(function(){ return func.apply(func, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(_.toArray(arguments).slice(1))); + return _.delay.apply(_, [func, 1].concat(_.rest(arguments))); }; // Returns the first function passed as an argument to the second, @@ -524,13 +506,14 @@ /*------------------------------- Aliases ----------------------------------*/ - _.head = _.first; _.forEach = _.each; _.foldl = _.inject = _.reduce; _.foldr = _.reduceRight; _.filter = _.select; _.every = _.all; _.some = _.any; + _.head = _.first; + _.tail = _.rest; _.methods = _.functions; /*------------------------ Setup the OOP Wrapper: --------------------------*/ From 3c0b7125a70222448e2d83a7c503d3f4fd2fdd45 Mon Sep 17 00:00:00 2001 From: matehat Date: Sat, 21 Nov 2009 22:02:31 -0500 Subject: [PATCH 063/533] '_.toArray' now checks whether the iterable object contains a 'toArray' method to delegate to. --- underscore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/underscore.js b/underscore.js index 69851fb02..13e6feb77 100644 --- a/underscore.js +++ b/underscore.js @@ -217,6 +217,7 @@ // Convert anything iterable into a real, live array. _.toArray = function(iterable) { if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); if (_.isArray(iterable)) return iterable; return _.map(iterable, function(val){ return val; }); }; From 67f1e8a9c89f644ee54be215146742a1b3174b31 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 22 Nov 2009 00:50:08 -0500 Subject: [PATCH 064/533] merged matehat's toArray patch --- underscore-min.js | 20 ++++++++++---------- underscore.js | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/underscore-min.js b/underscore-min.js index f8466a228..da7d4a39c 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,14 +1,14 @@ -(function(){var j=this,m=j._;function i(a){this._wrapped=a}var l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.4.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a,function(c,d){return d})};b.values=function(a){return b.map(a, -b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true}; -b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)=="[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)=="[object Number]"};b.isUndefined=function(a){return typeof a== -"undefined"};b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;function k(a,c){return c?b(a).chain():a}b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments), -this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); +(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;return b.map(a,function(c){return c})};b.size=function(a){return b.toArray(a).length};b.first=function(a,c){return c?Array.prototype.slice.call(a,0,c):a[0]};b.rest=function(a,c){return Array.prototype.slice.call(a,b.isUndefined(c)?1:c)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.select(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a, +[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d));c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.select(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c?b.last(d)!=e:!b.include(d,e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.select(b.uniq(a),function(d){return b.all(c,function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c= +b.max(b.pluck(a,"length")),d=new Array(c),e=0;e=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a, +function(c,d){return d})};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false; +for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)=="[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)== +"[object Number]"};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+ +a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a,c){return c?b(a).chain():a};b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments, +this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index 13e6feb77..1d16e9911 100644 --- a/underscore.js +++ b/underscore.js @@ -216,8 +216,8 @@ // Convert anything iterable into a real, live array. _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); if (_.isArray(iterable)) return iterable; return _.map(iterable, function(val){ return val; }); }; From d8cf99ba89e9d4dd6fc5cc1865737464cdede272 Mon Sep 17 00:00:00 2001 From: Kirill Ishanov Date: Tue, 1 Dec 2009 00:44:13 +0300 Subject: [PATCH 065/533] initial implementation of _.range --- test/collections.js | 4 ++++ underscore.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/test/collections.js b/test/collections.js index c0bfe1ee8..0f3381b9d 100644 --- a/test/collections.js +++ b/test/collections.js @@ -2,6 +2,10 @@ $(document).ready(function() { module("Collection functions (each, any, select, and so on...)"); + test("generators: range", function() { + equals(_.range(4).join(' '), '0 1 2 3 4', 'range with positive number generates an array of of elements 0,1,2,...,n'); + }); + test("collections: each", function() { _.each([1, 2, 3], function(num, i) { equals(num, i + 1, 'each iterators provide value and iteration count'); diff --git a/underscore.js b/underscore.js index 1d16e9911..9d71638b5 100644 --- a/underscore.js +++ b/underscore.js @@ -33,6 +33,20 @@ // Current version. _.VERSION = '0.4.5'; + /*------------------------ Generator Functions: ----------------------------*/ + _.range = function(upper, lower, step) { + if (!lower) var lower = 0; + if (!step) var step = 1; + + var result = new Array(((upper - lower) / step) + 1)); + + for (var i = lower; i <= upper; i += step) { + result[i] = i; + } + + return result; + } + /*------------------------ Collection Functions: ---------------------------*/ // The cornerstone, an each implementation. From 451d9c5d62e5d24ff77c6bfc14fe337f591efe3c Mon Sep 17 00:00:00 2001 From: Kirill Ishanov Date: Tue, 1 Dec 2009 02:10:56 +0300 Subject: [PATCH 066/533] implemented 6 more passing tests for range. Now works like python's range --- test/collections.js | 4 ---- test/generators.js | 15 +++++++++++++++ test/test.html | 3 ++- test/utility.js | 2 +- underscore.js | 34 ++++++++++++++++++++++++---------- 5 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 test/generators.js diff --git a/test/collections.js b/test/collections.js index 0f3381b9d..c0bfe1ee8 100644 --- a/test/collections.js +++ b/test/collections.js @@ -2,10 +2,6 @@ $(document).ready(function() { module("Collection functions (each, any, select, and so on...)"); - test("generators: range", function() { - equals(_.range(4).join(' '), '0 1 2 3 4', 'range with positive number generates an array of of elements 0,1,2,...,n'); - }); - test("collections: each", function() { _.each([1, 2, 3], function(num, i) { equals(num, i + 1, 'each iterators provide value and iteration count'); diff --git a/test/generators.js b/test/generators.js new file mode 100644 index 000000000..ea7982de1 --- /dev/null +++ b/test/generators.js @@ -0,0 +1,15 @@ +$(document).ready(function() { + + module("Generator functions (range...)"); + + test("generators: range", function() { + equals(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array'); + equals(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1'); + equals(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a & b, a b-a, a < b generates an array with a single element, equal to a'); + equals(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a & b & c, a > b, c < 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b'); + }); +}); + diff --git a/test/test.html b/test/test.html index ea26345fb..86b73318e 100644 --- a/test/test.html +++ b/test/test.html @@ -7,6 +7,7 @@ + @@ -31,4 +32,4 @@
          - \ No newline at end of file + diff --git a/test/utility.js b/test/utility.js index 6fd19af93..9f6c855e9 100644 --- a/test/utility.js +++ b/test/utility.js @@ -36,7 +36,7 @@ $(document).ready(function() { "flatten", "foldl", "foldr", "forEach", "functions", "head", "identity", "include", "indexOf", "inject", "intersect", "invoke", "isArray", "isElement", "isEmpty", "isEqual", "isFunction", "isNumber", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", - "methods", "min", "pluck", "reduce", "reduceRight", "reject", "rest", "select", + "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'); diff --git a/underscore.js b/underscore.js index 9d71638b5..dacba7c01 100644 --- a/underscore.js +++ b/underscore.js @@ -33,19 +33,33 @@ // Current version. _.VERSION = '0.4.5'; - /*------------------------ Generator Functions: ----------------------------*/ - _.range = function(upper, lower, step) { - if (!lower) var lower = 0; - if (!step) var step = 1; + /*------------------------ Generator Functions: ---------------------------*/ - var result = new Array(((upper - lower) / step) + 1)); - - for (var i = lower; i <= upper; i += step) { - result[i] = i; + // Generates an Array, containing an arithmetic progressions + // Analog of python's built-in function 'range' + _.range = function(start, stop, step) { + if (!stop) { + var stop = start; + start = 0; } - return result; - } + if (!step) var step = 1; + + var length = Math.ceil((stop - start) / step); + + if (length < 0) { + return []; + } + + var results = new Array(length); + var resIdx = 0; + + for (var i = start; (start <= stop ? stop - i > 0 : i - stop > 0); i += step) { + results[resIdx++] = i; + } + + return results; + }; /*------------------------ Collection Functions: ---------------------------*/ From 64cac959a59acd1ab7bbf5199d55a6befc241898 Mon Sep 17 00:00:00 2001 From: Kirill Ishanov Date: Tue, 1 Dec 2009 02:26:04 +0300 Subject: [PATCH 067/533] re-generated underscore-min.js to include _.range() function --- underscore-min.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/underscore-min.js b/underscore-min.js index da7d4a39c..2fcd2eb12 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,14 +1,2 @@ -(function(){var j=this,m=j._,i=function(a){this._wrapped=a},l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.4.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); -return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c= -b.max(b.pluck(a,"length")),d=new Array(c),e=0;e=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a, -function(c,d){return d})};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false; -for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)=="[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)== -"[object Number]"};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+ -a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a,c){return c?b(a).chain():a};b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments, -this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); +(function(){var j=this;var k=j._;var m=function(a){this._wrapped=a};var o=typeof StopIteration!=='undefined'?StopIteration:'__break__';var _=j._=function(a){return new m(a)};if(typeof exports!=='undefined')exports._=_;_.VERSION='0.4.5';_.range=function(a,b,c){if(!b){var b=a;a=0}if(!c)var c=1;var d=Math.ceil((b-a)/c);if(d<0){return[]}var e=new Array(d);var f=0;for(var i=a;(a<=b?b-i>0:i-b>0);i+=c){e[f++]=i}return e};_.each=function(a,b,c){var d=0;try{if(a.forEach){a.forEach(b,c)}else if(a.length){for(var i=0,l=a.length;i=h.computed&&(h={value:a,computed:d})});return h.value};_.min=function(e,f,g){if(!f&&_.isArray(e))return Math.min.apply(Math,e);var h={computed:Infinity};_.each(e,function(a,b,c){var d=f?f.call(g,a,b,c):a;db?1:0}),'value')};_.sortedIndex=function(a,b,c){c=c||_.identity;var d=0,high=a.length;while(d>1;c(a[e])=0})})};_.zip=function(){var a=_.toArray(arguments);var b=_.max(_.pluck(a,'length'));var c=new Array(b);for(var i=0;i=0;i--){arguments=[a[i].apply(this,arguments)]}return arguments[0]}};_.keys=function(c){return _.map(c,function(a,b){return b})};_.values=function(a){return _.map(a,_.identity)};_.extend=function(a,b){for(var c in b)a[c]=b[c];return a};_.clone=function(a){if(_.isArray(a))return a.slice(0);return _.extend({},a)};_.isEqual=function(a,b){if(a===b)return true;var c=typeof(a),btype=typeof(b);if(c!=btype)return false;if(a==b)return true;if(a.isEqual)return a.isEqual(b);if(_.isNumber(a)&&_.isNumber(b)&&isNaN(a)&&isNaN(b))return true;if(c!=='object')return false;var d=_.keys(a),bKeys=_.keys(b);if(d.length!=bKeys.length)return false;for(var e in a)if(!_.isEqual(a[e],b[e]))return false;return true};_.isEmpty=function(a){return(_.isArray(a)?a:_.values(a)).length==0};_.isElement=function(a){return!!(a&&a.nodeType==1)};_.isArray=function(a){return Object.prototype.toString.call(a)=='[object Array]'};_.isFunction=function(a){return Object.prototype.toString.call(a)=='[object Function]'};_.isString=function(a){return Object.prototype.toString.call(a)=='[object String]'};_.isNumber=function(a){return Object.prototype.toString.call(a)=='[object Number]'};_.isUndefined=function(a){return typeof a=='undefined'};_.noConflict=function(){j._=k;return this};_.identity=function(a){return a};_.breakLoop=function(){throw o;};var p=0;_.uniqueId=function(a){var b=p++;return a?a+b:b};_.functions=function(){var a=[];for(var b in _)if(Object.prototype.hasOwnProperty.call(_,b))a.push(b);return _.without(a,'VERSION','prototype','noConflict').sort()};_.template=function(a,b){var c=new Function('obj','var p=[],print=function(){p.push.apply(p,arguments);};'+'with(obj){p.push(\''+a.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 b?c(b):c};_.forEach=_.each;_.foldl=_.inject=_.reduce;_.foldr=_.reduceRight;_.filter=_.select;_.every=_.all;_.some=_.any;_.head=_.first;_.tail=_.rest;_.methods=_.functions;var q=function(a,b){return b?_(a).chain():a};_.each(_.functions(),function(a){m.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return q(_[a].apply(_,arguments),this._chain)}});_.each(['pop','push','reverse','shift','sort','splice','unshift'],function(a){m.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return q(this._wrapped,this._chain)}});_.each(['concat','join','slice'],function(a){m.prototype[a]=function(){return q(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}})(); + From 4b2744a75adb3697ae2c99101704abdf512551dd Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 30 Nov 2009 23:20:11 -0500 Subject: [PATCH 068/533] 0.4.6 is on the books, with kylichuku's range function --- index.html | 37 ++++++++++++++++++++++++++++++++++--- test/arrays.js | 11 +++++++++++ test/generators.js | 15 --------------- test/speed.js | 4 ++++ test/test.html | 1 - underscore-min.js | 17 +++++++++++++++-- underscore.js | 46 +++++++++++++++++----------------------------- 7 files changed, 81 insertions(+), 50 deletions(-) delete mode 100644 test/generators.js diff --git a/index.html b/index.html index c2b1ac241..dd2bcbb0c 100644 --- a/index.html +++ b/index.html @@ -107,11 +107,11 @@

          - + - +
          Development Version (0.4.5)Development Version (0.4.6) 18kb, Uncompressed with Comments
          Production Version (0.4.5)Production Version (0.4.6) 2kb, Packed and Gzipped
          @@ -186,7 +186,7 @@ _(lyrics).chain() first, rest, last, compact, flatten, without, uniq, intersect, zip, indexOf, - lastIndexOf + lastIndexOf, range

          @@ -583,6 +583,28 @@ _.indexOf([1, 2, 3], 2);

           _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
           => 4
          +
          + +

          + range_.range([start], stop, [step]) +
          + A function to create flexibly-numbered lists of integers, handy for + each and map loops. start, if omitted, defaults + to 0; step defaults to 1. Returns a list of integers + from start to stop, incremented (or decremented) by step, + exclusive. +

          +
          +_.range(10);
          +=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
          +_.range(1, 11);
          +=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
          +_.range(0, 30, 5);
          +=> [0, 5, 10, 15, 20, 25]
          +_.range(0, -10, -1);
          +=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
          +_.range(0);
          +=> []
           

          Function (uh, ahem) Functions

          @@ -930,6 +952,15 @@ _([1, 2, 3]).value();

          Change Log

          +

          + 0.4.6
          + Added the range function, a port of the + Python + function of the same name, for generating flexibly-numbered lists + of integers. Original patch contributed by + Kirill Ishanov. +

          +

          0.4.5
          Added rest for Arrays and arguments objects, and aliased diff --git a/test/arrays.js b/test/arrays.js index 5464756f6..3b0ad20a1 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -86,4 +86,15 @@ $(document).ready(function() { equals(result, 5, 'works on an arguments object'); }); + test("arrays: range", function() { + equals(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array'); + equals(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1'); + equals(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a & b, a b-a, a < b generates an array with a single element, equal to a'); + equals(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a & b & c, a > b, c < 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b'); + equals(_.range(0, -10, -1).join(' '), '0 -1 -2 -3 -4 -5 -6 -7 -8 -9', 'final example in the Python docs'); + }); + }); diff --git a/test/generators.js b/test/generators.js deleted file mode 100644 index ea7982de1..000000000 --- a/test/generators.js +++ /dev/null @@ -1,15 +0,0 @@ -$(document).ready(function() { - - module("Generator functions (range...)"); - - test("generators: range", function() { - equals(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array'); - equals(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1'); - equals(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a & b, a b-a, a < b generates an array with a single element, equal to a'); - equals(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a & b & c, a > b, c < 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b'); - }); -}); - diff --git a/test/speed.js b/test/speed.js index 8313b1d56..86663a237 100644 --- a/test/speed.js +++ b/test/speed.js @@ -63,4 +63,8 @@ return _.intersect(numbers, randomized); }); + JSLitmus.test('_.range()', function() { + return _.range(1000); + }); + })(); \ No newline at end of file diff --git a/test/test.html b/test/test.html index 86b73318e..c174ea0a6 100644 --- a/test/test.html +++ b/test/test.html @@ -7,7 +7,6 @@ - diff --git a/underscore-min.js b/underscore-min.js index 2fcd2eb12..80bfa08c2 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,2 +1,15 @@ -(function(){var j=this;var k=j._;var m=function(a){this._wrapped=a};var o=typeof StopIteration!=='undefined'?StopIteration:'__break__';var _=j._=function(a){return new m(a)};if(typeof exports!=='undefined')exports._=_;_.VERSION='0.4.5';_.range=function(a,b,c){if(!b){var b=a;a=0}if(!c)var c=1;var d=Math.ceil((b-a)/c);if(d<0){return[]}var e=new Array(d);var f=0;for(var i=a;(a<=b?b-i>0:i-b>0);i+=c){e[f++]=i}return e};_.each=function(a,b,c){var d=0;try{if(a.forEach){a.forEach(b,c)}else if(a.length){for(var i=0,l=a.length;i=h.computed&&(h={value:a,computed:d})});return h.value};_.min=function(e,f,g){if(!f&&_.isArray(e))return Math.min.apply(Math,e);var h={computed:Infinity};_.each(e,function(a,b,c){var d=f?f.call(g,a,b,c):a;db?1:0}),'value')};_.sortedIndex=function(a,b,c){c=c||_.identity;var d=0,high=a.length;while(d>1;c(a[e])=0})})};_.zip=function(){var a=_.toArray(arguments);var b=_.max(_.pluck(a,'length'));var c=new Array(b);for(var i=0;i=0;i--){arguments=[a[i].apply(this,arguments)]}return arguments[0]}};_.keys=function(c){return _.map(c,function(a,b){return b})};_.values=function(a){return _.map(a,_.identity)};_.extend=function(a,b){for(var c in b)a[c]=b[c];return a};_.clone=function(a){if(_.isArray(a))return a.slice(0);return _.extend({},a)};_.isEqual=function(a,b){if(a===b)return true;var c=typeof(a),btype=typeof(b);if(c!=btype)return false;if(a==b)return true;if(a.isEqual)return a.isEqual(b);if(_.isNumber(a)&&_.isNumber(b)&&isNaN(a)&&isNaN(b))return true;if(c!=='object')return false;var d=_.keys(a),bKeys=_.keys(b);if(d.length!=bKeys.length)return false;for(var e in a)if(!_.isEqual(a[e],b[e]))return false;return true};_.isEmpty=function(a){return(_.isArray(a)?a:_.values(a)).length==0};_.isElement=function(a){return!!(a&&a.nodeType==1)};_.isArray=function(a){return Object.prototype.toString.call(a)=='[object Array]'};_.isFunction=function(a){return Object.prototype.toString.call(a)=='[object Function]'};_.isString=function(a){return Object.prototype.toString.call(a)=='[object String]'};_.isNumber=function(a){return Object.prototype.toString.call(a)=='[object Number]'};_.isUndefined=function(a){return typeof a=='undefined'};_.noConflict=function(){j._=k;return this};_.identity=function(a){return a};_.breakLoop=function(){throw o;};var p=0;_.uniqueId=function(a){var b=p++;return a?a+b:b};_.functions=function(){var a=[];for(var b in _)if(Object.prototype.hasOwnProperty.call(_,b))a.push(b);return _.without(a,'VERSION','prototype','noConflict').sort()};_.template=function(a,b){var c=new Function('obj','var p=[],print=function(){p.push.apply(p,arguments);};'+'with(obj){p.push(\''+a.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 b?c(b):c};_.forEach=_.each;_.foldl=_.inject=_.reduce;_.foldr=_.reduceRight;_.filter=_.select;_.every=_.all;_.some=_.any;_.head=_.first;_.tail=_.rest;_.methods=_.functions;var q=function(a,b){return b?_(a).chain():a};_.each(_.functions(),function(a){m.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return q(_[a].apply(_,arguments),this._chain)}});_.each(['pop','push','reverse','shift','sort','splice','unshift'],function(a){m.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return q(this._wrapped,this._chain)}});_.each(['concat','join','slice'],function(a){m.prototype[a]=function(){return q(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}})(); - +(function(){var j=this,m=j._,i=function(a){this._wrapped=a},l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.4.6";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); +return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c= +b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(){var a=Array.prototype.pop.call(arguments);b.each(arguments,function(c){a[c]=b.bind(a[c],a)})};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d= +[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=a.length-1;c>=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a,function(c,d){return d})};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true; +var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)== +"[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)=="[object Number]"};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions= +function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c? +a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a,c){return c?b(a).chain():a};b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped, +arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index dacba7c01..57f2659ab 100644 --- a/underscore.js +++ b/underscore.js @@ -31,35 +31,7 @@ if (typeof exports !== 'undefined') exports._ = _; // Current version. - _.VERSION = '0.4.5'; - - /*------------------------ Generator Functions: ---------------------------*/ - - // Generates an Array, containing an arithmetic progressions - // Analog of python's built-in function 'range' - _.range = function(start, stop, step) { - if (!stop) { - var stop = start; - start = 0; - } - - if (!step) var step = 1; - - var length = Math.ceil((stop - start) / step); - - if (length < 0) { - return []; - } - - var results = new Array(length); - var resIdx = 0; - - for (var i = start; (start <= stop ? stop - i > 0 : i - stop > 0); i += step) { - results[resIdx++] = i; - } - - return results; - }; + _.VERSION = '0.4.6'; /*------------------------ Collection Functions: ---------------------------*/ @@ -343,6 +315,22 @@ return -1; }; + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python range() function. See: + // http://docs.python.org/library/functions.html#range + _.range = function(start, stop, step) { + var a = _.toArray(arguments); + var solo = a.length <= 1; + var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1; + var len = Math.ceil((stop - start) / step); + if (len <= 0) return []; + var range = new Array(len); + for (var i = start, idx = 0; true; i += step) { + if ((step > 0 ? i - stop : stop - i) >= 0) return range; + range[idx++] = i; + } + }; + /* ----------------------- Function Functions: -----------------------------*/ // Create a function bound to a given object (assigning 'this', and arguments, From 6b1ad73cd2c8a596699b471bf4d09b35804f5cf1 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 30 Nov 2009 23:21:48 -0500 Subject: [PATCH 069/533] commented, underscore is 20K now --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index dd2bcbb0c..9abc6c919 100644 --- a/index.html +++ b/index.html @@ -108,7 +108,7 @@ - + From 8ca9739dfe19a457ccae419ad44d347c28dbb804 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 3 Dec 2009 21:03:49 -0500 Subject: [PATCH 070/533] adding a documentcloud credit to the doc page --- index.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 9abc6c919..68bdb3701 100644 --- a/index.html +++ b/index.html @@ -93,14 +93,18 @@

          - Underscore includes a complete Test & Benchmark Suite - for your perusal. + A complete Test & Benchmark Suite + is included for your perusal.

          The unabridged source code is available on GitHub.

          + +

          + Underscore is an open-source component of DocumentCloud. +

          Downloads (Right-click, and use "Save As")

          From 79cb7bb17bc2dad7a12544d9452aa6d577c6f0d5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 12:35:11 -0500 Subject: [PATCH 071/533] stop assigning to arguments object in _.compose --- underscore.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/underscore.js b/underscore.js index 57f2659ab..ba8450011 100644 --- a/underscore.js +++ b/underscore.js @@ -379,10 +379,11 @@ _.compose = function() { var funcs = _.toArray(arguments); return function() { + var args = _.toArray(arguments); for (var i=funcs.length-1; i >= 0; i--) { - arguments = [funcs[i].apply(this, arguments)]; + args = [funcs[i].apply(this, args)]; } - return arguments[0]; + return args[0]; }; }; From a97836a175c6d0bc03533cfc66e051f70cc5a4f8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 12:55:43 -0500 Subject: [PATCH 072/533] a couple of grayrest's speed improvements for _.isEqual --- underscore.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/underscore.js b/underscore.js index ba8450011..d76efb223 100644 --- a/underscore.js +++ b/underscore.js @@ -420,12 +420,16 @@ if (atype != btype) return false; // Basic equality test (watch out for coercions). if (a == b) return true; + // Check dates' integer values. + if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime(); // One of them implements an isEqual()? if (a.isEqual) return a.isEqual(b); // Both are NaN? if (_.isNumber(a) && _.isNumber(b) && isNaN(a) && isNaN(b)) return true; // If a is not an object by this point, we can't handle it. if (atype !== 'object') return false; + // Check for different array lengths before comparing contents. + if (!_.isUndefined(a.length) && a.length !== b.length) return false; // Nothing else worked, deep compare the contents. var aKeys = _.keys(a), bKeys = _.keys(b); // Different object sizes? From 689cd97e03a21b87567f9c615c8aa08890d19e6c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 13:16:44 -0500 Subject: [PATCH 073/533] pushed all hasOwnProperty checks into _.keys, speeding _.keys up by about 25%, and using it to simplify other functions: _.each, _.isEmpty, _.functions --- underscore.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/underscore.js b/underscore.js index d76efb223..e3f938ba1 100644 --- a/underscore.js +++ b/underscore.js @@ -43,11 +43,10 @@ if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length) { - for (var i=0, l = obj.length; i Date: Sun, 6 Dec 2009 13:20:56 -0500 Subject: [PATCH 074/533] maintain a single reference to the Object.prototype --- underscore.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/underscore.js b/underscore.js index e3f938ba1..09e781a42 100644 --- a/underscore.js +++ b/underscore.js @@ -30,6 +30,9 @@ // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') exports._ = _; + // Maintain a reference to the Object prototype for quick access. + var oproto = Object.prototype; + // Current version. _.VERSION = '0.4.6'; @@ -392,7 +395,7 @@ _.keys = function(obj) { if(_.isArray(obj)) return _.range(0, obj.length); var keys = []; - for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key); + for (var key in obj) if (oproto.hasOwnProperty.call(obj, key)) keys.push(key); return keys; }; @@ -453,22 +456,22 @@ // Is a given value a real Array? _.isArray = function(obj) { - return Object.prototype.toString.call(obj) == '[object Array]'; + return oproto.toString.call(obj) == '[object Array]'; }; // Is a given value a Function? _.isFunction = function(obj) { - return Object.prototype.toString.call(obj) == '[object Function]'; + return oproto.toString.call(obj) == '[object Function]'; }; // Is a given value a String? _.isString = function(obj) { - return Object.prototype.toString.call(obj) == '[object String]'; + return oproto.toString.call(obj) == '[object String]'; }; // Is a given value a Number? _.isNumber = function(obj) { - return Object.prototype.toString.call(obj) == '[object Number]'; + return oproto.toString.call(obj) == '[object Number]'; }; // Is a given variable undefined? From 7a1f92a8c5a9c6b0933abd88e84098dc6d5c582f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 13:43:16 -0500 Subject: [PATCH 075/533] documentation for Underscore 0.4.7, with isDate, isNaN, and isNull --- index.html | 53 +++++++++++++++++++++++++++++++++++++++++++++---- test/objects.js | 20 +++++++++++++++++++ test/utility.js | 4 ++-- underscore.js | 22 +++++++++++++++++--- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index 68bdb3701..0c48b1c99 100644 --- a/index.html +++ b/index.html @@ -81,7 +81,7 @@

          - Underscore provides 50-odd functions that support both the usual + Underscore provides 60-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in @@ -111,11 +111,11 @@

          Development Version (0.4.6)18kb, Uncompressed with Comments20kb, Uncompressed with Comments
          Production Version (0.4.6)
          - + - +
          Development Version (0.4.6)Development Version (0.4.7) 20kb, Uncompressed with Comments
          Production Version (0.4.6)Production Version (0.4.7) 2kb, Packed and Gzipped
          @@ -207,7 +207,9 @@ _(lyrics).chain() extend, clone, isEqual, isEmpty, isElement, isArray, isFunction, isString, - isNumber, isUndefined + isNumber, isDate + isNaN, isNull, + isUndefined

          @@ -828,6 +830,44 @@ _.isFunction("moe");
           _.isNumber(8.4 * 5);
           => true
          +
          + +

          + isDate_.isDate(object) +
          + Returns true if object is a Date. +

          +
          +_.isDate(new Date());
          +=> true
          +
          + +

          + isNaN_.isNaN(object) +
          + Returns true if object is NaN.
          Note: this is not + the same as the native isNaN function, which will also return + true if the variable is undefined. +

          +
          +_.isNaN(NaN);
          +=> true
          +isNaN(undefined);
          +=> true
          +_.isNaN(undefined);
          +=> false
          +
          + +

          + isNull_.isNull(object) +
          + Returns true if the value of object is null. +

          +
          +_.isNull(null);
          +=> true
          +_.isNull(undefined);
          +=> false
           

          @@ -956,6 +996,11 @@ _([1, 2, 3]).value();

          Change Log

          +

          + 0.4.7
          + +

          +

          0.4.6
          Added the range function, a port of the diff --git a/test/objects.js b/test/objects.js index d76d3e8e2..355b2937b 100644 --- a/test/objects.js +++ b/test/objects.js @@ -36,6 +36,7 @@ $(document).ready(function() { ok(_(moe).isEqual(clone), 'OO-style deep equality works'); ok(!_.isEqual(5, NaN), '5 is not equal to NaN'); ok(_.isEqual(NaN, NaN), 'NaN is equal to NaN'); + ok(_.isEqual(new Date(100), new Date(100)), 'identical dates are equal'); }); test("objects: isEmpty", function() { @@ -75,6 +76,25 @@ $(document).ready(function() { ok(_.isFunction(_.isFunction), 'but functions are'); }); + test("objects: isDate", function() { + ok(!_.isDate(100), 'numbers are not dates'); + ok(!_.isDate({}), 'objects are not dates'); + ok(_.isDate(new Date()), 'but dates are'); + }); + + test("objects: isNaN", function() { + ok(!_.isNaN(undefined), 'undefined is not NaN'); + ok(!_.isNaN(null), 'null is not NaN'); + ok(!_.isNaN(0), '0 is not NaN'); + ok(_.isNaN(NaN), 'but NaN is'); + }); + + test("objects: isNull", function() { + ok(!_.isNull(undefined), 'undefined is not null'); + ok(!_.isNull(NaN), 'NaN is not null'); + ok(_.isNull(null), 'but null is'); + }); + test("objects: isUndefined", function() { ok(!_.isUndefined(1), 'numbers are defined'); ok(!_.isUndefined(null), 'null is defined'); diff --git a/test/utility.js b/test/utility.js index 9f6c855e9..0d02b3041 100644 --- a/test/utility.js +++ b/test/utility.js @@ -34,8 +34,8 @@ $(document).ready(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", "isElement", "isEmpty", "isEqual", - "isFunction", "isNumber", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", + "indexOf", "inject", "intersect", "invoke", "isArray", "isDate", "isElement", "isEmpty", "isEqual", + "isFunction", "isNaN", "isNull", "isNumber", "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"]; diff --git a/underscore.js b/underscore.js index 09e781a42..cbdf3e2e7 100644 --- a/underscore.js +++ b/underscore.js @@ -34,7 +34,7 @@ var oproto = Object.prototype; // Current version. - _.VERSION = '0.4.6'; + _.VERSION = '0.4.7'; /*------------------------ Collection Functions: ---------------------------*/ @@ -426,11 +426,11 @@ // Basic equality test (watch out for coercions). if (a == b) return true; // Check dates' integer values. - if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime(); + if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); // One of them implements an isEqual()? if (a.isEqual) return a.isEqual(b); // Both are NaN? - if (_.isNumber(a) && _.isNumber(b) && isNaN(a) && isNaN(b)) return true; + if (_.isNaN(a) && _.isNaN(b)) return true; // If a is not an object by this point, we can't handle it. if (atype !== 'object') return false; // Check for different array lengths before comparing contents. @@ -474,6 +474,22 @@ return oproto.toString.call(obj) == '[object Number]'; }; + // Is a given value a Date? + _.isDate = function(obj) { + return oproto.toString.call(obj) == '[object Date]'; + }; + + // Is the given value NaN -- this one is interesting. NaN != NaN, and + // isNaN(undefined) == true, so we make sure it's a number first. + _.isNaN = function(obj) { + return _.isNumber(obj) && isNaN(obj); + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + // Is a given variable undefined? _.isUndefined = function(obj) { return typeof obj == 'undefined'; From 4bd535e7f11ee4570a534e30ced65b93eae4894f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 14:13:25 -0500 Subject: [PATCH 076/533] Underscore 0.4.7 is done --- Rakefile | 9 +++++++++ index.html | 5 ++++- underscore-min.js | 30 +++++++++++++++--------------- underscore.js | 14 +++++++------- 4 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 Rakefile diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..6af14bb6d --- /dev/null +++ b/Rakefile @@ -0,0 +1,9 @@ +require 'rubygems' +require 'closure-compiler' + +desc "Use the Closure Compiler to compress Underscore.js" +task :build do + js = File.open('underscore.js', 'r') + min = Closure::Compiler.new.compile(js) + File.open('underscore-min.js', 'w') {|f| f.write(min) } +end \ No newline at end of file diff --git a/index.html b/index.html index 0c48b1c99..fd49a24fa 100644 --- a/index.html +++ b/index.html @@ -998,7 +998,10 @@ _([1, 2, 3]).value();

          0.4.7
          - + Added isDate, isNaN, and isNull, for completeness. + Optimizations for isEqual when checking equality between Arrays + or Dates. _.keys is now 25%–2X faster (depending on your + browser) which speeds up the functions that rely on it, such as _.each.

          diff --git a/underscore-min.js b/underscore-min.js index 80bfa08c2..18b4bcfbc 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,15 +1,15 @@ -(function(){var j=this,m=j._,i=function(a){this._wrapped=a},l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.4.6";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); -return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c= -b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(){var a=Array.prototype.pop.call(arguments);b.each(arguments,function(c){a[c]=b.bind(a[c],a)})};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d= -[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=a.length-1;c>=0;c--)arguments=[a[c].apply(this,arguments)];return arguments[0]}};b.keys=function(a){return b.map(a,function(c,d){return d})};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true; -var d=typeof a,e=typeof c;if(d!=e)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isNumber(a)&&b.isNumber(c)&&isNaN(a)&&isNaN(c))return true;if(d!=="object")return false;d=b.keys(a);e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return(b.isArray(a)?a:b.values(a)).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return Object.prototype.toString.call(a)== -"[object Array]"};b.isFunction=function(a){return Object.prototype.toString.call(a)=="[object Function]"};b.isString=function(a){return Object.prototype.toString.call(a)=="[object String]"};b.isNumber=function(a){return Object.prototype.toString.call(a)=="[object Number]"};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions= -function(){var a=[];for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&a.push(c);return b.without(a,"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c? -a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a,c){return c?b(a).chain():a};b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped, -arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); +(function(){var k=this,n=k._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=k._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var j=Object.prototype;b.VERSION="0.4.7";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&& +(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e< +f;){var g=e+f>>1;d(a[g])=0})})};b.zip=function(){for(var a= +b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||k,d.concat(b.toArray(arguments)))}};b.bindAll=function(){var a=Array.prototype.pop.call(arguments);b.each(arguments,function(c){a[c]=b.bind(a[c],a)})};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d= +[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);var c=[];for(var d in a)j.hasOwnProperty.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0); +return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(a.isEqual)return a.isEqual(c);if(b.isNaN(a)&&b.isNaN(c))return true;if(d!=="object")return false;if(!b.isUndefined(a.length)&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length== +0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return j.toString.call(a)=="[object Array]"};b.isFunction=function(a){return j.toString.call(a)=="[object Function]"};b.isString=function(a){return j.toString.call(a)=="[object String]"};b.isNumber=function(a){return j.toString.call(a)=="[object Number]"};b.isDate=function(a){return j.toString.call(a)=="[object Date]"};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined= +function(a){return typeof a=="undefined"};b.noConflict=function(){k._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var o=0;b.uniqueId=function(a){var c=o++;return a?a+c:c};b.functions=function(){return b.without(b.keys(b),"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return l(b[a].apply(b,arguments),this._chain)}});b.each(["pop", +"push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return l(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index cbdf3e2e7..49d5b08c1 100644 --- a/underscore.js +++ b/underscore.js @@ -31,7 +31,7 @@ if (typeof exports !== 'undefined') exports._ = _; // Maintain a reference to the Object prototype for quick access. - var oproto = Object.prototype; + var objPro = Object.prototype; // Current version. _.VERSION = '0.4.7'; @@ -395,7 +395,7 @@ _.keys = function(obj) { if(_.isArray(obj)) return _.range(0, obj.length); var keys = []; - for (var key in obj) if (oproto.hasOwnProperty.call(obj, key)) keys.push(key); + for (var key in obj) if (objPro.hasOwnProperty.call(obj, key)) keys.push(key); return keys; }; @@ -456,27 +456,27 @@ // Is a given value a real Array? _.isArray = function(obj) { - return oproto.toString.call(obj) == '[object Array]'; + return objPro.toString.call(obj) == '[object Array]'; }; // Is a given value a Function? _.isFunction = function(obj) { - return oproto.toString.call(obj) == '[object Function]'; + return objPro.toString.call(obj) == '[object Function]'; }; // Is a given value a String? _.isString = function(obj) { - return oproto.toString.call(obj) == '[object String]'; + return objPro.toString.call(obj) == '[object String]'; }; // Is a given value a Number? _.isNumber = function(obj) { - return oproto.toString.call(obj) == '[object Number]'; + return objPro.toString.call(obj) == '[object Number]'; }; // Is a given value a Date? _.isDate = function(obj) { - return oproto.toString.call(obj) == '[object Date]'; + return objPro.toString.call(obj) == '[object Date]'; }; // Is the given value NaN -- this one is interesting. NaN != NaN, and From 66dc6c2ac1b4c4d74ad2f4d044cba0d056549957 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 22:48:40 -0500 Subject: [PATCH 077/533] shrunk down all of the 'is' functions into a single generation, added isRegExp, added a regexp equality test to isEqual, after grayrest's patch --- test/objects.js | 1 + test/utility.js | 2 +- underscore.js | 50 ++++++++++++++++++------------------------------- 3 files changed, 20 insertions(+), 33 deletions(-) diff --git a/test/objects.js b/test/objects.js index 355b2937b..a6b63e0d4 100644 --- a/test/objects.js +++ b/test/objects.js @@ -37,6 +37,7 @@ $(document).ready(function() { ok(!_.isEqual(5, NaN), '5 is not equal to NaN'); ok(_.isEqual(NaN, NaN), 'NaN is equal to NaN'); ok(_.isEqual(new Date(100), new Date(100)), 'identical dates are equal'); + ok(_.isEqual((/hello/ig), (/hello/ig)), 'identical regexes are equal'); }); test("objects: isEmpty", function() { diff --git a/test/utility.js b/test/utility.js index 0d02b3041..99aa7cab2 100644 --- a/test/utility.js +++ b/test/utility.js @@ -35,7 +35,7 @@ $(document).ready(function() { "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", "isString", "isUndefined", "keys", "last", "lastIndexOf", "map", "max", + "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"]; diff --git a/underscore.js b/underscore.js index 49d5b08c1..b734430cb 100644 --- a/underscore.js +++ b/underscore.js @@ -30,9 +30,6 @@ // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') exports._ = _; - // Maintain a reference to the Object prototype for quick access. - var objPro = Object.prototype; - // Current version. _.VERSION = '0.4.7'; @@ -395,7 +392,7 @@ _.keys = function(obj) { if(_.isArray(obj)) return _.range(0, obj.length); var keys = []; - for (var key in obj) if (objPro.hasOwnProperty.call(obj, key)) keys.push(key); + for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key); return keys; }; @@ -425,16 +422,22 @@ if (atype != btype) return false; // Basic equality test (watch out for coercions). if (a == b) return true; - // Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); // One of them implements an isEqual()? if (a.isEqual) return a.isEqual(b); + // Check dates' integer values. + if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); // Both are NaN? if (_.isNaN(a) && _.isNaN(b)) return true; + // Compare regular expressions. + if (_.isRegExp(a) && _.isRegExp(b)) + return a.source === b.source && + a.global === b.global && + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; // If a is not an object by this point, we can't handle it. if (atype !== 'object') return false; // Check for different array lengths before comparing contents. - if (!_.isUndefined(a.length) && a.length !== b.length) return false; + if (a.length && (a.length !== b.length)) return false; // Nothing else worked, deep compare the contents. var aKeys = _.keys(a), bKeys = _.keys(b); // Different object sizes? @@ -454,31 +457,6 @@ return !!(obj && obj.nodeType == 1); }; - // Is a given value a real Array? - _.isArray = function(obj) { - return objPro.toString.call(obj) == '[object Array]'; - }; - - // Is a given value a Function? - _.isFunction = function(obj) { - return objPro.toString.call(obj) == '[object Function]'; - }; - - // Is a given value a String? - _.isString = function(obj) { - return objPro.toString.call(obj) == '[object String]'; - }; - - // Is a given value a Number? - _.isNumber = function(obj) { - return objPro.toString.call(obj) == '[object Number]'; - }; - - // Is a given value a Date? - _.isDate = function(obj) { - return objPro.toString.call(obj) == '[object Date]'; - }; - // Is the given value NaN -- this one is interesting. NaN != NaN, and // isNaN(undefined) == true, so we make sure it's a number first. _.isNaN = function(obj) { @@ -495,6 +473,14 @@ return typeof obj == 'undefined'; }; + // Define the isArray, isDate, isFunction, isNumber, isRegExp, and + // isString functions based on their toString identifiers. + _.each(['Array', 'Date', 'Function', 'Number', 'RegExp', 'String'], function(type) { + _['is' + type] = function(obj) { + return Object.prototype.toString.call(obj) == '[object ' + type + ']'; + }; + }); + /* -------------------------- Utility Functions: -------------------------- */ // Run Underscore.js in noConflict mode, returning the '_' variable to its From 53122d08bd7ed9f98ac42c5eda4e3093a6e46770 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 22:50:38 -0500 Subject: [PATCH 078/533] documenting isRegExp --- index.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index fd49a24fa..b309c0046 100644 --- a/index.html +++ b/index.html @@ -207,7 +207,7 @@ _(lyrics).chain() extend, clone, isEqual, isEmpty, isElement, isArray, isFunction, isString, - isNumber, isDate + isNumber, isDate, isRegExp isNaN, isNull, isUndefined @@ -840,6 +840,16 @@ _.isNumber(8.4 * 5);

           _.isDate(new Date());
           => true
          +
          + +

          + isRegExp_.isRegExp(object) +
          + Returns true if object is a RegExp. +

          +
          +_.isRegExp(/moe/);
          +=> true
           

          From 2875f3145d6d90acc97b7e3df1de5ae075f3e569 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 23:09:36 -0500 Subject: [PATCH 079/533] added a links section to the documentation, with Underscore.lua and collections libraries galore --- index.html | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/index.html b/index.html index b309c0046..70669fc13 100644 --- a/index.html +++ b/index.html @@ -1004,6 +1004,35 @@ _([1, 2, 3]).value(); => [1, 2, 3] +

          Links & Suggested Reading

          + +

          + Underscore.lua, + a Lua port of the functions that are applicable in both languages. + Includes OOP-wrapping and chaining. + The source is + available on GitHub. +

          + +

          + Ruby's Enumerable module. +

          + +

          + Prototype.js, which provides + JavaScript with collection functions in the manner closest to Ruby's Enumerable. +

          + +

          + Oliver Steele's + Functional JavaScript, + which includes comprehensive higher-order function support as well as string lambdas. +

          + +

          + Python's itertools. +

          +

          Change Log

          From 06c74e76f085adf61a8a3690d3e84c8967c4d201 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 23:10:44 -0500 Subject: [PATCH 080/533] added an isRegExp test --- test/objects.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/objects.js b/test/objects.js index a6b63e0d4..97556747c 100644 --- a/test/objects.js +++ b/test/objects.js @@ -83,6 +83,11 @@ $(document).ready(function() { ok(_.isDate(new Date()), 'but dates are'); }); + test("objects: isRegExp", function() { + ok(!_.isRegExp(_.identity), 'functions are not RegExps'); + ok(_.isRegExp(/identity/), 'but RegExps are'); + }); + test("objects: isNaN", function() { ok(!_.isNaN(undefined), 'undefined is not NaN'); ok(!_.isNaN(null), 'null is not NaN'); From 39001bd02937106c7841b9d2bc1521aaccf32d38 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 6 Dec 2009 23:54:41 -0500 Subject: [PATCH 081/533] 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); From f2670259d1a5895a0e56711ce24478f097bf52d3 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 7 Dec 2009 00:14:07 -0500 Subject: [PATCH 082/533] 0.5.0 is out, with variants of grayrest's patches --- index.html | 17 +++++++++++++++-- underscore-min.js | 30 +++++++++++++++--------------- underscore.js | 3 ++- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index 0d49f955d..a881ae043 100644 --- a/index.html +++ b/index.html @@ -111,11 +111,11 @@

          - + - +
          Development Version (0.4.7)Development Version (0.5.0) 20kb, Uncompressed with Comments
          Production Version (0.4.7)Production Version (0.5.0) 2kb, Packed and Gzipped
          @@ -1041,6 +1041,19 @@ _([1, 2, 3]).value();

          Change Log

          +

          + 0.5.0
          + [API Changes] _.bindAll now takes the context object as + its first parameter. If no method names are passed, all of the context + object's methods are bound to it, enabling chaining and easier binding. + _.functions now takes a single argument and returns the names + of its Function properties. Calling _.functions(_) will get you + the previous behavior. + Added _.isRegExp so that isEqual can now test for RegExp equality. + All of the "is" functions have been shrunk down into a single definition. + Karl Guertin contributed patches. +

          +

          0.4.7
          Added isDate, isNaN, and isNull, for completeness. diff --git a/underscore-min.js b/underscore-min.js index 18b4bcfbc..4df2f6843 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,15 +1,15 @@ -(function(){var k=this,n=k._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=k._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var j=Object.prototype;b.VERSION="0.4.7";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&& -(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e< -f;){var g=e+f>>1;d(a[g])=0})})};b.zip=function(){for(var a= -b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||k,d.concat(b.toArray(arguments)))}};b.bindAll=function(){var a=Array.prototype.pop.call(arguments);b.each(arguments,function(c){a[c]=b.bind(a[c],a)})};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d= -[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);var c=[];for(var d in a)j.hasOwnProperty.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0); -return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(a.isEqual)return a.isEqual(c);if(b.isNaN(a)&&b.isNaN(c))return true;if(d!=="object")return false;if(!b.isUndefined(a.length)&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length== -0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return j.toString.call(a)=="[object Array]"};b.isFunction=function(a){return j.toString.call(a)=="[object Function]"};b.isString=function(a){return j.toString.call(a)=="[object String]"};b.isNumber=function(a){return j.toString.call(a)=="[object Number]"};b.isDate=function(a){return j.toString.call(a)=="[object Date]"};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined= -function(a){return typeof a=="undefined"};b.noConflict=function(){k._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var o=0;b.uniqueId=function(a){var c=o++;return a?a+c:c};b.functions=function(){return b.without(b.keys(b),"VERSION","prototype","noConflict").sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return l(b[a].apply(b,arguments),this._chain)}});b.each(["pop", -"push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]=function(){Array.prototype[a].apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return l(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); +(function(){var j=this,m=j._,i=function(a){this._wrapped=a},l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.5.0";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); +return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c= +b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)});return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d= +[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);var c=[];for(var d in a)Object.prototype.hasOwnProperty.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0); +return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c); +if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.each(["Array","Date","Function","Number","RegExp","String"],function(a){b["is"+a]=function(c){return Object.prototype.toString.call(c)=="[object "+a+"]"}}); +b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]= +function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index cf3c6c3f7..5d781fe6f 100644 --- a/underscore.js +++ b/underscore.js @@ -31,7 +31,7 @@ if (typeof exports !== 'undefined') exports._ = _; // Current version. - _.VERSION = '0.4.7'; + _.VERSION = '0.5.0'; /*------------------------ Collection Functions: ---------------------------*/ @@ -347,6 +347,7 @@ var funcs = _.rest(arguments); if (funcs.length == 0) funcs = _.functions(obj); _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; }; // Delays a function for the given number of milliseconds, and then calls From 21e0cbc229f3da0f2b10716be06752465fee7dfc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 7 Dec 2009 00:26:00 -0500 Subject: [PATCH 083/533] simplified the bindAll example --- index.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/index.html b/index.html index a881ae043..eacb7016e 100644 --- a/index.html +++ b/index.html @@ -646,11 +646,7 @@ var buttonView = { onClick : function(){ alert('clicked: ' + this.label); }, onHover : function(){ console.log('hovering: ' + this.label); } }; - -// In this case, the following two lines have the same effect. -_.bindAll(buttonView, 'onClick', 'onHover'); -_(buttonView).bindAll(); - +_.bindAll(buttonView); jQuery('#underscore_button').bind('click', buttonView.onClick); => When the button is clicked, this.label will have the correct value... From 2afcffb30a69d1a6f41f90a9214199f1c45a2516 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 7 Dec 2009 23:36:31 -0500 Subject: [PATCH 084/533] added guards to _.first and _.rest and tests, they now work as function parameters to _.map --- test/arrays.js | 4 ++++ underscore.js | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 3b0ad20a1..9eb8dac54 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -8,6 +8,8 @@ $(document).ready(function() { equals(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first'); var result = (function(){ return _.first(arguments); })(4, 3, 2, 1); equals(result, 4, 'works on an arguments object.'); + result = _.map([[1,2,3],[1,2,3]], _.first); + equals(result.join(','), '1,1', 'works well with _.map'); }); test("arrays: rest", function() { @@ -16,6 +18,8 @@ $(document).ready(function() { equals(_.rest(numbers, 2).join(', '), '3, 4', 'rest can take an index'); var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4); equals(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object'); + result = _.map([[1,2,3],[1,2,3]], _.rest); + equals(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map'); }); test("arrays: last", function() { diff --git a/underscore.js b/underscore.js index 5d781fe6f..2f3b025ea 100644 --- a/underscore.js +++ b/underscore.js @@ -229,16 +229,18 @@ /*-------------------------- Array Functions: ------------------------------*/ // Get the first element of an array. Passing "n" will return the first N - // values in the array. Aliased as "head". - _.first = function(array, n) { - return n ? Array.prototype.slice.call(array, 0, n) : array[0]; + // values in the array. Aliased as "head". The "guard" check allows it to work + // with _.map. + _.first = function(array, n, guard) { + return n && !guard ? Array.prototype.slice.call(array, 0, n) : array[0]; }; // Returns everything but the first entry of the array. Aliased as "tail". // Especially useful on the arguments object. Passing an "index" will return - // the rest of the values in the array from that index onward. - _.rest = function(array, index) { - return Array.prototype.slice.call(array, _.isUndefined(index) ? 1 : index); + // the rest of the values in the array from that index onward. The "guard" + //check allows it to work with _.map. + _.rest = function(array, index, guard) { + return Array.prototype.slice.call(array, _.isUndefined(index) || guard ? 1 : index); }; // Get the last element of an array. @@ -270,7 +272,7 @@ // been sorted, you have the option of using a faster algorithm. _.uniq = function(array, isSorted) { return _.reduce(array, [], function(memo, el, i) { - if (0 == i || (isSorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); + if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); return memo; }); }; From 30329c051bdee2ae771d7b4cb6552a1ea977a4f0 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 7 Dec 2009 23:41:55 -0500 Subject: [PATCH 085/533] dont try to look at the keys of zero-length arrays or arguments objects when calling each() --- underscore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 2f3b025ea..98d3a8fab 100644 --- a/underscore.js +++ b/underscore.js @@ -42,7 +42,7 @@ try { if (obj.forEach) { obj.forEach(iterator, context); - } else if (obj.length) { + } else if (obj.length || obj.length === 0) { for (var i=0, l=obj.length; i Date: Tue, 8 Dec 2009 00:06:34 -0500 Subject: [PATCH 086/533] adding pervasive safety checks for using Underscore functions on objects that jsut happen to have 'map', 'reduce', or 'filter' etc. properties that aren't functions. --- underscore.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/underscore.js b/underscore.js index 98d3a8fab..51398635d 100644 --- a/underscore.js +++ b/underscore.js @@ -42,7 +42,7 @@ try { if (obj.forEach) { obj.forEach(iterator, context); - } else if (obj.length || obj.length === 0) { + } else if (_.isNumber(obj.length)) { for (var i=0, l=obj.length; i Date: Tue, 8 Dec 2009 14:11:28 -0600 Subject: [PATCH 087/533] cache is string and toString to improve performance --- underscore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 51398635d..b94cc0f9b 100644 --- a/underscore.js +++ b/underscore.js @@ -478,8 +478,9 @@ // Define the isArray, isDate, isFunction, isNumber, isRegExp, and // isString functions based on their toString identifiers. _.each(['Array', 'Date', 'Function', 'Number', 'RegExp', 'String'], function(type) { + var toString = Object.prototype.toString, typeString = '[object ' + type + ']'; _['is' + type] = function(obj) { - return Object.prototype.toString.call(obj) == '[object ' + type + ']'; + return toString.call(obj) == typeString; }; }); From 38cae13d692bd57c23ff31604c433124f8125af9 Mon Sep 17 00:00:00 2001 From: Noah Sloan Date: Tue, 8 Dec 2009 14:21:05 -0600 Subject: [PATCH 088/533] cache wrapper methods --- underscore.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/underscore.js b/underscore.js index b94cc0f9b..81898d4af 100644 --- a/underscore.js +++ b/underscore.js @@ -555,24 +555,28 @@ // Add all of the Underscore functions to the wrapper object. _.each(_.functions(_), function(name) { + var unshift = Array.prototype.unshift, + method = _[name]; wrapper.prototype[name] = function() { - Array.prototype.unshift.call(arguments, this._wrapped); - return result(_[name].apply(_, arguments), this._chain); + unshift.call(arguments, this._wrapped); + return result(method.apply(_, arguments), this._chain); }; }); // Add all mutator Array functions to the wrapper. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = Array.prototype[name]; wrapper.prototype[name] = function() { - Array.prototype[name].apply(this._wrapped, arguments); + method.apply(this._wrapped, arguments); return result(this._wrapped, this._chain); }; }); // Add all accessor Array functions to the wrapper. _.each(['concat', 'join', 'slice'], function(name) { + var method = Array.prototype[name]; wrapper.prototype[name] = function() { - return result(Array.prototype[name].apply(this._wrapped, arguments), this._chain); + return result(method.apply(this._wrapped, arguments), this._chain); }; }); From a5454d6972063a65bfa0611a237663f19e509856 Mon Sep 17 00:00:00 2001 From: Noah Sloan Date: Tue, 8 Dec 2009 14:24:44 -0600 Subject: [PATCH 089/533] cache hasOwnProperty --- underscore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 81898d4af..da40afaa8 100644 --- a/underscore.js +++ b/underscore.js @@ -391,10 +391,11 @@ /* ------------------------- Object Functions: ---------------------------- */ // Retrieve the names of an object's properties. + var hasOwnProperty = Object.prototype.hasOwnProperty; _.keys = function(obj) { if(_.isArray(obj)) return _.range(0, obj.length); var keys = []; - for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key); + for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); return keys; }; From 6554c6d9762d71c056d573fabf0ead967a75b308 Mon Sep 17 00:00:00 2001 From: Noah Sloan Date: Tue, 8 Dec 2009 15:57:04 -0600 Subject: [PATCH 090/533] have to define isNumber before _.each will work in IE --- underscore.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/underscore.js b/underscore.js index da40afaa8..834ecac9a 100644 --- a/underscore.js +++ b/underscore.js @@ -475,10 +475,15 @@ _.isUndefined = function(obj) { return typeof obj == 'undefined'; }; + + // have to define isNumber before _.each will work in IE + _.isNumber = function(obj) { + return Object.prototype.toString == '[object Number]'; + }; - // Define the isArray, isDate, isFunction, isNumber, isRegExp, and + // Define the isArray, isDate, isFunction, isRegExp, and // isString functions based on their toString identifiers. - _.each(['Array', 'Date', 'Function', 'Number', 'RegExp', 'String'], function(type) { + _.each(['Array', 'Date', 'Function', 'RegExp', 'String'], function(type) { var toString = Object.prototype.toString, typeString = '[object ' + type + ']'; _['is' + type] = function(obj) { return toString.call(obj) == typeString; From 37930f92e0031bb0f5a591827ecef13863f19f9a Mon Sep 17 00:00:00 2001 From: Noah Sloan Date: Tue, 8 Dec 2009 16:03:53 -0600 Subject: [PATCH 091/533] doh. messed up isNumber fix --- underscore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 834ecac9a..017018790 100644 --- a/underscore.js +++ b/underscore.js @@ -478,7 +478,7 @@ // have to define isNumber before _.each will work in IE _.isNumber = function(obj) { - return Object.prototype.toString == '[object Number]'; + return Object.prototype.toString.call(obj) == '[object Number]'; }; // Define the isArray, isDate, isFunction, isRegExp, and From 3eb9c280397f70db24766a26ab072a23f0777c3e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 8 Dec 2009 17:31:01 -0500 Subject: [PATCH 092/533] Underscore shouldn't be able to iterate over the letters of a string cross-browser -- indexing into a string isn't supported in IE --- test/collections.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/collections.js b/test/collections.js index c0bfe1ee8..47ca9d430 100644 --- a/test/collections.js +++ b/test/collections.js @@ -15,10 +15,6 @@ $(document).ready(function() { _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); - answers = []; - _.each("moe", function(letter){ answers.push(letter); }); - equals(answers.join(', '), 'm, o, e', 'iterates over the letters in strings'); - answers = []; _.forEach([1, 2, 3], function(num){ answers.push(num); }); equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); From 99564138e8c745f71f5842871c0a6ce30b26bde7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 9 Dec 2009 11:17:30 -0500 Subject: [PATCH 093/533] added an extra check in isEqual to test for falsy against truthy values (so as to short circuit before trying to look for properties on null) --- underscore.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/underscore.js b/underscore.js index 51398635d..da098bc97 100644 --- a/underscore.js +++ b/underscore.js @@ -424,6 +424,8 @@ if (atype != btype) return false; // Basic equality test (watch out for coercions). if (a == b) return true; + // One is falsy and the other truthy. + if ((!a && b) || (a && !b)) return false; // One of them implements an isEqual()? if (a.isEqual) return a.isEqual(b); // Check dates' integer values. From 2ba87d6b44f6c302bbb87635a2c5164337d0fcad Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 9 Dec 2009 11:20:09 -0500 Subject: [PATCH 094/533] adding test case for previous commit --- test/objects.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/objects.js b/test/objects.js index 18c4b3c2b..8ba67858e 100644 --- a/test/objects.js +++ b/test/objects.js @@ -52,6 +52,7 @@ $(document).ready(function() { ok(_.isEqual(NaN, NaN), 'NaN is equal to NaN'); ok(_.isEqual(new Date(100), new Date(100)), 'identical dates are equal'); ok(_.isEqual((/hello/ig), (/hello/ig)), 'identical regexes are equal'); + ok(!_.isEqual(null, [1]), 'a falsy is never equal to a truthy'); }); test("objects: isEmpty", function() { From 76cccb6a2dc58b71811e4cc4ce5733a2b11e959f Mon Sep 17 00:00:00 2001 From: Noah Sloan Date: Wed, 9 Dec 2009 10:00:48 -0600 Subject: [PATCH 095/533] added JSLitmus tests for isType functions --- test/speed.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/speed.js b/test/speed.js index 86663a237..50fd3dc60 100644 --- a/test/speed.js +++ b/test/speed.js @@ -5,6 +5,31 @@ var objects = _.map(numbers, function(n){ return {num : n}; }); var randomized = _.sortBy(numbers, function(){ return Math.random(); }); + JSLitmus.test('_.isNumber()', function() { + _.isNumber(123); + _.isNumber(null); + _.isNumber(NaN); + _.isNumber(/foo/); + _.isNumber("abc"); + }); + + JSLitmus.test('_.isString()', function() { + _.isString(123); + _.isString(null); + _.isString(NaN); + _.isString(/foo/); + _.isString("abc"); + }); + + JSLitmus.test('_.isDate()', function() { + _.isDate(123); + _.isDate(null); + _.isDate(NaN); + _.isDate(/foo/); + _.isDate("abc"); + _.isDate(new Date()); + }); + JSLitmus.test('_.each()', function() { var timesTwo = []; _.each(numbers, function(num){ timesTwo.push(num * 2); }); From 225d795836d86bdc03518faa656f883e34075f3a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 9 Dec 2009 12:44:55 -0500 Subject: [PATCH 096/533] merging in iamnoah's optimizations for the isType family of functions, and other references to core prototoypes --- test/speed.js | 25 ------------------------- underscore.js | 22 ++++++++++++---------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/test/speed.js b/test/speed.js index 50fd3dc60..86663a237 100644 --- a/test/speed.js +++ b/test/speed.js @@ -5,31 +5,6 @@ var objects = _.map(numbers, function(n){ return {num : n}; }); var randomized = _.sortBy(numbers, function(){ return Math.random(); }); - JSLitmus.test('_.isNumber()', function() { - _.isNumber(123); - _.isNumber(null); - _.isNumber(NaN); - _.isNumber(/foo/); - _.isNumber("abc"); - }); - - JSLitmus.test('_.isString()', function() { - _.isString(123); - _.isString(null); - _.isString(NaN); - _.isString(/foo/); - _.isString("abc"); - }); - - JSLitmus.test('_.isDate()', function() { - _.isDate(123); - _.isDate(null); - _.isDate(NaN); - _.isDate(/foo/); - _.isDate("abc"); - _.isDate(new Date()); - }); - JSLitmus.test('_.each()', function() { var timesTwo = []; _.each(numbers, function(num){ timesTwo.push(num * 2); }); diff --git a/underscore.js b/underscore.js index 46df7455e..7ae664386 100644 --- a/underscore.js +++ b/underscore.js @@ -30,6 +30,9 @@ // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') exports._ = _; + // Create quick reference variables for speed access to Object.prototype. + var toString = Object.prototype.toString, hasOwnProperty = Object.prototype.hasOwnProperty; + // Current version. _.VERSION = '0.5.0'; @@ -391,7 +394,6 @@ /* ------------------------- Object Functions: ---------------------------- */ // Retrieve the names of an object's properties. - var hasOwnProperty = Object.prototype.hasOwnProperty; _.keys = function(obj) { if(_.isArray(obj)) return _.range(0, obj.length); var keys = []; @@ -477,18 +479,19 @@ _.isUndefined = function(obj) { return typeof obj == 'undefined'; }; - - // have to define isNumber before _.each will work in IE + + // isNumber needs to be defined before the rest of the isType functions, + // because _.each uses it in IE (and we want to use _.each for the closure). _.isNumber = function(obj) { - return Object.prototype.toString.call(obj) == '[object Number]'; + return toString.call(obj) == '[object Number]'; }; - // Define the isArray, isDate, isFunction, isRegExp, and - // isString functions based on their toString identifiers. + // Define the isArray, isDate, isFunction, isRegExp, and isString functions + // based on their toString identifiers. _.each(['Array', 'Date', 'Function', 'RegExp', 'String'], function(type) { - var toString = Object.prototype.toString, typeString = '[object ' + type + ']'; + var identifier = '[object ' + type + ']'; _['is' + type] = function(obj) { - return toString.call(obj) == typeString; + return toString.call(obj) == identifier; }; }); @@ -563,8 +566,7 @@ // Add all of the Underscore functions to the wrapper object. _.each(_.functions(_), function(name) { - var unshift = Array.prototype.unshift, - method = _[name]; + var method = _[name], unshift = Array.prototype.unshift; wrapper.prototype[name] = function() { unshift.call(arguments, this._wrapped); return result(method.apply(_, arguments), this._chain); From 5c314d206e2539d78b6e843e58e064e69145bad1 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 9 Dec 2009 13:41:19 -0500 Subject: [PATCH 097/533] adding an isArguments checker and enabling iteration (using each) over JS object hashes that have numeric length properties --- index.html | 12 ++++++++++++ test/collections.js | 4 ++++ test/objects.js | 9 ++++++++- underscore.js | 38 ++++++++++++++++++++++++-------------- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index eacb7016e..2eb2dbc31 100644 --- a/index.html +++ b/index.html @@ -813,6 +813,18 @@ _.isElement(jQuery('body')[0]); => false _.isArray([1,2,3]); => true + + +

          + isArguments_.isArguments(object) +
          + Returns true if object is an Arguments object. +

          +
          +(function(){ return _.isArguments(arguments); })(1, 2, 3);
          +=> true
          +_.isArguments([1,2,3]);
          +=> false
           

          diff --git a/test/collections.js b/test/collections.js index 47ca9d430..935edcb95 100644 --- a/test/collections.js +++ b/test/collections.js @@ -29,6 +29,10 @@ $(document).ready(function() { answer = null; _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); ok(answer, 'can reference the original collection from inside the iterator'); + + answers = []; + _.each({range : 1, speed : 2, length : 3}, function(v){ answers.push(v); }); + ok(answers.join(', '), '1, 2, 3', 'can iterate over objects with numeric length properties'); }); test('collections: map', function() { diff --git a/test/objects.js b/test/objects.js index 8ba67858e..cd45c0bc6 100644 --- a/test/objects.js +++ b/test/objects.js @@ -14,7 +14,7 @@ $(document).ready(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", + "indexOf", "inject", "intersect", "invoke", "isArguments", "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", @@ -71,6 +71,13 @@ $(document).ready(function() { ok(_.isElement($('html')[0]), 'the html tag is a DOM element'); }); + test("objects: isArguments", function() { + var args = (function(){ return arguments; })(1, 2, 3); + ok(_.isArguments(args), 'the arguments object is an arguments object'); + ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array'); + ok(!_.isArguments([1,2,3]), 'and not vanilla arrays.'); + }); + test("objects: isArray", function() { ok(!_.isArray(arguments), 'the arguments object is not an array'); ok(_.isArray([1, 2, 3]), 'but arrays are'); diff --git a/underscore.js b/underscore.js index 7ae664386..810531f72 100644 --- a/underscore.js +++ b/underscore.js @@ -30,8 +30,12 @@ // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') exports._ = _; - // Create quick reference variables for speed access to Object.prototype. - var toString = Object.prototype.toString, hasOwnProperty = Object.prototype.hasOwnProperty; + // Create quick reference variables for speed access to core prototypes. + var slice = Array.prototype.slice, + unshift = Array.prototype.unshift, + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + propertyIsEnumerable = Object.prototype.propertyIsEnumerable; // Current version. _.VERSION = '0.5.0'; @@ -45,7 +49,7 @@ try { if (obj.forEach) { obj.forEach(iterator, context); - } else if (_.isNumber(obj.length)) { + } else if (_.isArray(obj) || _.isArguments(obj)) { for (var i=0, l=obj.length; i Date: Wed, 9 Dec 2009 14:22:05 -0500 Subject: [PATCH 098/533] Underscore 0.5.1 --- index.html | 17 ++++++++++++----- underscore-min.js | 31 ++++++++++++++++--------------- underscore.js | 2 +- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/index.html b/index.html index 2eb2dbc31..eb23527c1 100644 --- a/index.html +++ b/index.html @@ -111,12 +111,12 @@

          - - + + - - + +
          Development Version (0.5.0)20kb, Uncompressed with CommentsDevelopment Version (0.5.1)21kb, Uncompressed with Comments
          Production Version (0.5.0)2kb, Packed and GzippedProduction Version (0.5.1)3kb, Packed and Gzipped

          @@ -206,7 +206,7 @@ _(lyrics).chain() keys, values, functions, extend, clone, isEqual, isEmpty, isElement, - isArray, isFunction, isString, + isArray, isArguments, isFunction, isString, isNumber, isDate, isRegExp isNaN, isNull, isUndefined @@ -1049,6 +1049,13 @@ _([1, 2, 3]).value();

          Change Log

          +

          + 0.5.1
          + Added an _.isArguments function. Lots of little safety checks + and optimizations contributed by + Noah Sloan and Andri Möll. +

          +

          0.5.0
          [API Changes] _.bindAll now takes the context object as diff --git a/underscore-min.js b/underscore-min.js index 4df2f6843..573c6042c 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,15 +1,16 @@ -(function(){var j=this,m=j._,i=function(a){this._wrapped=a},l=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;b.VERSION="0.5.0";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})}); -return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c= -b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)});return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d= -[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);var c=[];for(var d in a)Object.prototype.hasOwnProperty.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0); -return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c); -if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.each(["Array","Date","Function","Number","RegExp","String"],function(a){b["is"+a]=function(c){return Object.prototype.toString.call(c)=="[object "+a+"]"}}); -b.noConflict=function(){j._=m;return this};b.identity=function(a){return a};b.breakLoop=function(){throw l;};var n=0;b.uniqueId=function(a){var c=n++;return a?a+c:c};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){i.prototype[a]=function(){Array.prototype.unshift.call(arguments,this._wrapped);return k(b[a].apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){i.prototype[a]= -function(){Array.prototype[a].apply(this._wrapped,arguments);return k(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){i.prototype[a]=function(){return k(Array.prototype[a].apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); +(function(){var j=this,o=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,p=Array.prototype.unshift,n=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.1";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d], +a)});return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0, +a.length);var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false;if(a.isEqual)return a.isEqual(c); +if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==0};b.isElement= +function(a){return!!(a&&a.nodeType==1)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.isNumber=function(a){return n.call(a)=="[object Number]"};b.each(["Array","Date","Function","RegExp","String"],function(a){var c="[object "+a+"]";b["is"+a]=function(d){return n.call(d)==c}});b.noConflict=function(){j._= +o;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){p.call(arguments,this._wrapped);return l(c.apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments); +return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index 810531f72..d14f50a75 100644 --- a/underscore.js +++ b/underscore.js @@ -38,7 +38,7 @@ propertyIsEnumerable = Object.prototype.propertyIsEnumerable; // Current version. - _.VERSION = '0.5.0'; + _.VERSION = '0.5.1'; /*------------------------ Collection Functions: ---------------------------*/ From a418153800891ab7521f0559e995e0dbac6f2c9a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 9 Dec 2009 14:36:19 -0500 Subject: [PATCH 099/533] quick fix for 0.5.1 for IE -- need to define functions in the right order --- underscore-min.js | 18 +++++++++--------- underscore.js | 23 +++++++++-------------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/underscore-min.js b/underscore-min.js index 573c6042c..4360e355a 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,16 +1,16 @@ -(function(){var j=this,o=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,p=Array.prototype.unshift,n=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.1";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])f?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d], a)});return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0, -a.length);var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false;if(a.isEqual)return a.isEqual(c); +a.length);var c=[];for(var d in a)s.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false;if(a.isEqual)return a.isEqual(c); if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==0};b.isElement= -function(a){return!!(a&&a.nodeType==1)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.isNumber=function(a){return n.call(a)=="[object Number]"};b.each(["Array","Date","Function","RegExp","String"],function(a){var c="[object "+a+"]";b["is"+a]=function(d){return n.call(d)==c}});b.noConflict=function(){j._= -o;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.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 c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){p.call(arguments,this._wrapped);return l(c.apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments); -return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); +function(a){return!!(a&&a.nodeType==1)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!t.call(a,"length")};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};for(var m=["Array","Date","Function","Number","RegExp","String"],k=0,u=m.length;k)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c?a(c):a};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;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var n=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){q.call(arguments,this._wrapped);return n(c.apply(b,arguments),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return n(this._wrapped, +this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return n(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/underscore.js b/underscore.js index d14f50a75..047f01c59 100644 --- a/underscore.js +++ b/underscore.js @@ -495,20 +495,15 @@ return typeof obj == 'undefined'; }; - // isNumber needs to be defined before the rest of the isType functions, - // because _.each uses it in IE (and we want to use _.each for the closure). - _.isNumber = function(obj) { - return toString.call(obj) == '[object Number]'; - }; - - // Define the isArray, isDate, isFunction, isRegExp, and isString functions - // based on their toString identifiers. - _.each(['Array', 'Date', 'Function', 'RegExp', 'String'], function(type) { - var identifier = '[object ' + type + ']'; - _['is' + type] = function(obj) { - return toString.call(obj) == identifier; - }; - }); + // Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString + // functions based on their toString identifiers. + var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String']; + for (var i=0, l=types.length; i Date: Thu, 10 Dec 2009 14:14:48 -0500 Subject: [PATCH 100/533] utf8 meta tag --- index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.html b/index.html index eb23527c1..894ec25d7 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,8 @@ + + Underscore.js + @@ -141,7 +142,8 @@ any, include, invoke, pluck, max, min, - sortBy, groupBy, sortedIndex, + sortBy, groupBy, + sortedIndex, shuffle, toArray, size

          @@ -464,6 +466,25 @@ _.sortedIndex([10, 20, 30, 40, 50], 35); => 3 +

          + shuffle_.shuffle(list) +
          + Returns a shuffled list. +

          +
          +_.shuffle([1, 2, 3, 4, 5, 6]);
          +=> 
          +(reshuffle)
          +
          + +

          toArray_.toArray(list)
          diff --git a/test/collections.js b/test/collections.js index 005ee169e..9afe95849 100644 --- a/test/collections.js +++ b/test/collections.js @@ -204,6 +204,13 @@ $(document).ready(function() { equals(index, 3, '35 should be inserted at index 3'); }); + test('collections: shuffle', function() { + var numbers = _.range(10); + var shuffled = _.shuffle(numbers).sort(); + notStrictEqual(numbers, shuffled, 'original object is unmodified'); + equals(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle'); + }); + test('collections: toArray', function() { ok(!_.isArray(arguments), 'arguments object is not an array'); ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); diff --git a/underscore.js b/underscore.js index 0d587b41c..a19f3186e 100644 --- a/underscore.js +++ b/underscore.js @@ -239,6 +239,21 @@ return result.value; }; + // Shuffle an array. + _.shuffle = function(obj) { + var shuffled = [], rand; + each(obj, function(value, index, list) { + if (index == 0) { + shuffled[0] = value; + } else { + rand = Math.floor(Math.random() * (index + 1)); + shuffled[index] = shuffled[rand]; + shuffled[rand] = value; + } + }); + return shuffled; + }; + // Sort the object's values by a criterion produced by an iterator. _.sortBy = function(obj, iterator, context) { return _.pluck(_.map(obj, function(value, index, list) { From 9996ecae5c69eb4ba153edb271ca44a039c9f159 Mon Sep 17 00:00:00 2001 From: Ryan W Tenney Date: Thu, 25 Aug 2011 22:13:11 +0000 Subject: [PATCH 354/533] Remove dupe inclusion of underscore-min.js. Get rid of 'reshuffle' in index.html. --- index.html | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/index.html b/index.html index 3254edcfc..c33cc95d5 100644 --- a/index.html +++ b/index.html @@ -65,7 +65,6 @@ margin: 0px 0 30px; } - @@ -469,21 +468,12 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

          shuffle_.shuffle(list)
          - Returns a shuffled list. + Returns a shuffled copy of list.

           _.shuffle([1, 2, 3, 4, 5, 6]);
          -=> 
          -(reshuffle)
          +=> [4, 1, 6, 3, 5, 2]
           
          -

          toArray_.toArray(list) From a8f0445192aeac118b3f347a3abfc7bf5af51fd3 Mon Sep 17 00:00:00 2001 From: Malcolm Locke Date: Wed, 31 Aug 2011 22:39:05 +1200 Subject: [PATCH 355/533] Add an optional index argument to _.last() This makes _.last() behave the same as _.first(). Passing an optional second argument n will return the last n elements of the array. --- test/arrays.js | 2 ++ underscore.js | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 78cf098a0..bbf74e6da 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -26,6 +26,8 @@ $(document).ready(function() { test("arrays: last", function() { equals(_.last([1,2,3]), 3, 'can pull out the last element of an array'); + equals(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last'); + equals(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last'); var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4); equals(result, 4, 'works on an arguments object'); }); diff --git a/underscore.js b/underscore.js index 0d587b41c..7963394cb 100644 --- a/underscore.js +++ b/underscore.js @@ -306,9 +306,10 @@ return slice.call(array, (index == null) || guard ? 1 : index); }; - // Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + _.last = function(array, n) { + return (n != null) ? slice.call(array, array.length - n) : array[array.length - 1]; }; // Trim out all falsy values from an array. From e449b00a26d0bfc29c0be42f1ea98c7d8f5493b9 Mon Sep 17 00:00:00 2001 From: Malcolm Locke Date: Thu, 1 Sep 2011 01:10:10 +1200 Subject: [PATCH 356/533] Add guard check to _.last() Allows _.last() to work as expected with _.map(). --- test/arrays.js | 2 ++ underscore.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index bbf74e6da..950e8417f 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -30,6 +30,8 @@ $(document).ready(function() { equals(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last'); var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4); equals(result, 4, 'works on an arguments object'); + result = _.map([[1,2,3],[1,2,3]], _.last); + equals(result.join(','), '3,3', 'works well with _.map'); }); test("arrays: compact", function() { diff --git a/underscore.js b/underscore.js index 7963394cb..239d7dd34 100644 --- a/underscore.js +++ b/underscore.js @@ -307,9 +307,9 @@ }; // Get the last element of an array. Passing **n** will return the last N - // values in the array. - _.last = function(array, n) { - return (n != null) ? slice.call(array, array.length - n) : array[array.length - 1]; + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + return (n != null) && !guard ? slice.call(array, array.length - n) : array[array.length - 1]; }; // Trim out all falsy values from an array. From bf3aa97c3614f032983afe0740813021e70a06dd Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Sun, 4 Sep 2011 19:34:19 -0400 Subject: [PATCH 357/533] reverting some changes to isEqual that were a little too aggressive --- test/objects.js | 4 ++-- underscore.js | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/test/objects.js b/test/objects.js index 4234f0fad..bb0f98553 100644 --- a/test/objects.js +++ b/test/objects.js @@ -72,8 +72,8 @@ $(document).ready(function() { var moe = {name : 'moe', lucky : [13, 27, 34]}; var clone = {name : 'moe', lucky : [13, 27, 34]}; - var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}}; - var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}}; + var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}}; + var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}}; ok(moe != clone, 'basic equality between objects is false'); ok(_.isEqual(moe, clone), 'deep equality is true'); ok(_(moe).isEqual(clone), 'OO-style deep equality works'); diff --git a/underscore.js b/underscore.js index 3ad38311b..7615b5d5e 100644 --- a/underscore.js +++ b/underscore.js @@ -610,14 +610,16 @@ // `NaN` values are equal. if (_.isNaN(a)) return _.isNaN(b); // Compare dates by their millisecond values. - if (_.isDate(a)) return _.isDate(b) && a.getTime() == b.getTime(); + var isDateA = _.isDate(a), isDateB = _.isDate(b); + if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime(); // Compare RegExps by their source patterns and flags. - if (_.isRegExp(a)) - return _.isRegExp(b) && - a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b); + if (isRegExpA || isRegExpB) + return isRegExpA && isRegExpB && + a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; // Ensure that both values are objects. if (typeA != 'object') return false; // Unwrap any wrapped objects. @@ -625,6 +627,7 @@ if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. if (typeof a.isEqual == 'function') return a.isEqual(b); + if (typeof b.isEqual == 'function') return b.isEqual(a); // Compare array lengths to determine if a deep comparison is necessary. if ('length' in a && (a.length !== b.length)) return false; // Assume equality for cyclic structures. From 6f62f258cb4c1c45248c30c56cfd0ceea7051086 Mon Sep 17 00:00:00 2001 From: Kit Cambridge Date: Mon, 5 Sep 2011 12:25:59 -0600 Subject: [PATCH 358/533] Add support for comparing string, number, and boolean object wrappers. Ignore inherited properties when deep comparing objects. Use a more efficient `while` loop for comparing arrays and array-like objects. --- underscore.js | 75 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/underscore.js b/underscore.js index 7615b5d5e..825e9405d 100644 --- a/underscore.js +++ b/underscore.js @@ -603,23 +603,29 @@ // Compare object types. var typeA = typeof a; if (typeA != typeof b) return false; - // The type comparison above prevents unwanted type coercion. - if (a == b) return true; // Optimization; ensure that both values are truthy or falsy. if (!a != !b) return false; - // `NaN` values are equal. - if (_.isNaN(a)) return _.isNaN(b); + // Compare string objects by value. + var isStringA = _.isString(a), isStringB = _.isString(b); + if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b); + // Compare number objects by value. `NaN` values are equal. + var isNumberA = toString.call(a) == '[object Number]', isNumberB = toString.call(b) == '[object Number]'; + if (isNumberA || isNumberB) return isNumberA && isNumberB && (_.isNaN(a) ? _.isNaN(b) : +a == +b); + // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0. + var isBooleanA = toString.call(a) == '[object Boolean]', isBooleanB = toString.call(b) == '[object Boolean]'; + if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b; // Compare dates by their millisecond values. var isDateA = _.isDate(a), isDateB = _.isDate(b); if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime(); // Compare RegExps by their source patterns and flags. var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b); if (isRegExpA || isRegExpB) - return isRegExpA && isRegExpB && - a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + // Ensure commutative equality for RegExps. + return isRegExpA && isRegExpB && + a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; // Ensure that both values are objects. if (typeA != 'object') return false; // Unwrap any wrapped objects. @@ -627,30 +633,47 @@ if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. if (typeof a.isEqual == 'function') return a.isEqual(b); - if (typeof b.isEqual == 'function') return b.isEqual(a); - // Compare array lengths to determine if a deep comparison is necessary. - if ('length' in a && (a.length !== b.length)) return false; - // Assume equality for cyclic structures. + // If only `b` provides an `isEqual` method, `a` and `b` are not equal. + if (typeof b.isEqual == 'function') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic structures is + // adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = stack.length; while (length--) { + // Linear search. Performance is inversely proportional to the number of unique nested + // structures. if (stack[length] == a) return true; } // Add the first object to the stack of traversed objects. stack.push(a); - // Deep compare the two objects. - var size = 0, sizeRight = 0, result = true, key; - for (key in a) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = key in b && eq(a[key], b[key], stack))) break; - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (++sizeRight > size) break; + var size = 0, result = true; + if (a.length === +a.length || b.length === +b.length) { + // Compare object lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare array-like object contents, ignoring non-numeric properties. + while (size--) { + // Ensure commutative equality for sparse arrays. + if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; + } + } + } else { + // Deep compare objects. + for (var key in a) { + if (hasOwnProperty.call(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (hasOwnProperty.call(b, key) && !size--) break; + } + result = !size; } - result = size == sizeRight; } // Remove the first object from the stack of traversed objects. stack.pop(); From e9faa401082034b5ac6ce6dc3855db714451b747 Mon Sep 17 00:00:00 2001 From: Kit Cambridge Date: Mon, 5 Sep 2011 12:27:03 -0600 Subject: [PATCH 359/533] Add a comprehensive test suite for `isEqual`. --- test/objects.js | 290 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 222 insertions(+), 68 deletions(-) diff --git a/test/objects.js b/test/objects.js index bb0f98553..6fc60f1e4 100644 --- a/test/objects.js +++ b/test/objects.js @@ -65,78 +65,232 @@ $(document).ready(function() { }); test("objects: isEqual", function() { - function Foo(){ this.own = 'a'; } - function Bar(){ this.own = 'a'; } - Foo.prototype.inherited = 1; - Bar.prototype.inherited = 2; + function First() { + this.value = 1; + } + First.prototype.value = 1; + function Second() { + this.value = 1; + } + Second.prototype.value = 2; - var moe = {name : 'moe', lucky : [13, 27, 34]}; - var clone = {name : 'moe', lucky : [13, 27, 34]}; + // Basic equality and identity comparisons. + ok(_.isEqual(null, null), "`null` is equal to `null`"); + ok(_.isEqual(), "`undefined` is equal to `undefined`"); + + ok(!_.isEqual(0, -0), "`0` is not equal to `-0`"); + ok(!_.isEqual(-0, 0), "Commutative equality is implemented for `0` and `-0`"); + ok(!_.isEqual(null, undefined), "`null` is not equal to `undefined`"); + ok(!_.isEqual(undefined, null), "Commutative equality is implemented for `null` and `undefined`"); + + // String object and primitive comparisons. + ok(_.isEqual("Curly", "Curly"), "Identical string primitives are equal"); + ok(_.isEqual(new String("Curly"), new String("Curly")), "String objects with identical primitive values are equal"); + + ok(!_.isEqual("Curly", "Larry"), "String primitives with different values are not equal"); + ok(!_.isEqual(new String("Curly"), "Curly"), "String primitives and their corresponding object wrappers are not equal"); + ok(!_.isEqual("Curly", new String("Curly")), "Commutative equality is implemented for string objects and primitives"); + ok(!_.isEqual(new String("Curly"), new String("Larry")), "String objects with different primitive values are not equal"); + ok(!_.isEqual(new String("Curly"), {toString: function(){ return "Curly"; }}), "String objects and objects with a custom `toString` method are not equal"); + + // Number object and primitive comparisons. + ok(_.isEqual(75, 75), "Identical number primitives are equal"); + ok(_.isEqual(new Number(75), new Number(75)), "Number objects with identical primitive values are equal"); + + ok(!_.isEqual(75, new Number(75)), "Number primitives and their corresponding object wrappers are not equal"); + ok(!_.isEqual(new Number(75), 75), "Commutative equality is implemented for number objects and primitives"); + ok(!_.isEqual(new Number(75), new Number(63)), "Number objects with different primitive values are not equal"); + ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), "Number objects and objects with a `valueOf` method are not equal"); + + // Comparisons involving `NaN`. + ok(_.isEqual(NaN, NaN), "`NaN` is equal to `NaN`"); + ok(!_.isEqual(61, NaN), "A number primitive is not equal to `NaN`"); + ok(!_.isEqual(new Number(79), NaN), "A number object is not equal to `NaN`"); + ok(!_.isEqual(Infinity, NaN), "`Infinity` is not equal to `NaN`"); + + // Boolean object and primitive comparisons. + ok(_.isEqual(true, true), "Identical boolean primitives are equal"); + ok(_.isEqual(new Boolean, new Boolean), "Boolean objects with identical primitive values are equal"); + ok(!_.isEqual(true, new Boolean(true)), "Boolean primitives and their corresponding object wrappers are not equal"); + ok(!_.isEqual(new Boolean(true), true), "Commutative equality is implemented for booleans"); + ok(!_.isEqual(new Boolean(true), new Boolean), "Boolean objects with different primitive values are not equal"); + + // Common type coercions. + ok(!_.isEqual(true, new Boolean(false)), "Boolean objects are not equal to the boolean primitive `true`"); + ok(!_.isEqual("75", 75), "String and number primitives with like values are not equal"); + ok(!_.isEqual(new Number(63), new String(63)), "String and number objects with like values are not equal"); + ok(!_.isEqual(75, "75"), "Commutative equality is implemented for like string and number values"); + ok(!_.isEqual(0, ""), "Number and string primitives with like values are not equal"); + ok(!_.isEqual(1, true), "Number and boolean primitives with like values are not equal"); + ok(!_.isEqual(new Boolean(false), new Number(0)), "Boolean and number objects with like values are not equal"); + ok(!_.isEqual(false, new String("")), "Boolean primitives and string objects with like values are not equal"); + ok(!_.isEqual(12564504e5, new Date(2009, 9, 25)), "Dates and their corresponding numeric primitive values are not equal"); + + // Dates. + ok(_.isEqual(new Date(2009, 9, 25), new Date(2009, 9, 25)), "Date objects referencing identical times are equal"); + ok(!_.isEqual(new Date(2009, 9, 25), new Date(2009, 11, 13)), "Date objects referencing different times are not equal"); + ok(!_.isEqual(new Date(2009, 11, 13), { + getTime: function(){ + return 12606876e5; + } + }), "Date objects and objects with a `getTime` method are not equal"); + ok(!_.isEqual(new Date("Curly"), new Date("Curly")), "Invalid dates are not equal"); + + // Functions. + ok(!_.isEqual(First, Second), "Different functions with identical bodies and source code representations are not equal"); + + // RegExps. + ok(_.isEqual(/(?:)/gim, /(?:)/gim), "RegExps with equivalent patterns and flags are equal"); + ok(!_.isEqual(/(?:)/g, /(?:)/gi), "RegExps with equivalent patterns and different flags are not equal"); + ok(!_.isEqual(/Moe/gim, /Curly/gim), "RegExps with different patterns and equivalent flags are not equal"); + ok(!_.isEqual(/(?:)/gi, /(?:)/g), "Commutative equality is implemented for RegExps"); + ok(!_.isEqual(/Curly/g, {source: "Larry", global: true, ignoreCase: false, multiline: false}), "RegExps and RegExp-like objects are not equal"); + + // Empty arrays, array-like objects, and object literals. + ok(_.isEqual({}, {}), "Empty object literals are equal"); + ok(_.isEqual([], []), "Empty array literals are equal"); + ok(_.isEqual([{}], [{}]), "Empty nested arrays and objects are equal"); + ok(_.isEqual({length: 0}, []), "Array-like objects and arrays are equal"); + ok(_.isEqual([], {length: 0}), "Commutative equality is implemented for array-like objects"); + + ok(!_.isEqual({}, []), "Object literals and array literals are not equal"); + ok(!_.isEqual([], {}), "Commutative equality is implemented for objects and arrays"); + + // Arrays with primitive and object values. + ok(_.isEqual([1, "Larry", true], [1, "Larry", true]), "Arrays containing identical primitives are equal"); + ok(_.isEqual([/Moe/g, new Date(2009, 9, 25)], [/Moe/g, new Date(2009, 9, 25)]), "Arrays containing equivalent elements are equal"); + + // Multi-dimensional arrays. + var a = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}]; + var b = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}]; + ok(_.isEqual(a, b), "Arrays containing nested arrays and objects are recursively compared"); + + // Overwrite the methods defined in ES 5.1 section 15.4.4. + a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null; + b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null; + + // Array elements and properties. + ok(_.isEqual(a, b), "Arrays containing equivalent elements and different non-numeric properties are equal"); + a.push("White Rocks"); + ok(!_.isEqual(a, b), "Arrays of different lengths are not equal"); + a.push("East Boulder"); + b.push("Gunbarrel Ranch", "Teller Farm"); + ok(!_.isEqual(a, b), "Arrays of identical lengths containing different elements are not equal"); + + // Sparse arrays. + ok(_.isEqual(Array(3), Array(3)), "Sparse arrays of identical lengths are equal"); + ok(!_.isEqual(Array(3), Array(6)), "Sparse arrays of different lengths are not equal"); + + // According to the Microsoft deviations spec, section 2.1.26, JScript 5.x treats `undefined` + // elements in arrays as elisions. Thus, sparse arrays and dense arrays containing `undefined` + // values are equivalent. + if (0 in [undefined]) { + ok(!_.isEqual(Array(3), [undefined, undefined, undefined]), "Sparse and dense arrays are not equal"); + ok(!_.isEqual([undefined, undefined, undefined], Array(3)), "Commutative equality is implemented for sparse and dense arrays"); + } + + // Simple objects. + ok(_.isEqual({a: "Curly", b: 1, c: true}, {a: "Curly", b: 1, c: true}), "Objects containing identical primitives are equal"); + ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), "Objects containing equivalent members are equal"); + ok(!_.isEqual({a: 63, b: 75}, {a: 61, b: 55}), "Objects of identical sizes with different values are not equal"); + ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), "Objects of identical sizes with different property names are not equal"); + ok(!_.isEqual({a: 1, b: 2}, {a: 1}), "Objects of different sizes are not equal"); + ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects"); + ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent"); + + // `A` contains nested objects and arrays. + a = { + name: new String("Moe Howard"), + age: new Number(77), + stooge: true, + hobbies: ["acting"], + film: { + name: "Sing a Song of Six Pants", + release: new Date(1947, 9, 30), + stars: [new String("Larry Fine"), "Shemp Howard"], + minutes: new Number(16), + seconds: 54 + } + }; + + // `B` contains equivalent nested objects and arrays. + b = { + name: new String("Moe Howard"), + age: new Number(77), + stooge: true, + hobbies: ["acting"], + film: { + name: "Sing a Song of Six Pants", + release: new Date(1947, 9, 30), + stars: [new String("Larry Fine"), "Shemp Howard"], + minutes: new Number(16), + seconds: 54 + } + }; + ok(_.isEqual(a, b), "Objects with nested equivalent members are recursively compared"); + + // Instances. + ok(_.isEqual(new First, new First), "Object instances are equal"); + ok(_.isEqual(new First, new Second), "Objects with different constructors and identical own properties are equal"); + ok(_.isEqual({value: 1}, new First), "Object instances and objects sharing equivalent properties are identical"); + ok(!_.isEqual({value: 2}, new Second), "The prototype chain of objects should not be examined"); + + // Circular Arrays. + (a = []).push(a); + (b = []).push(b); + ok(_.isEqual(a, b), "Arrays containing circular references are equal"); + a.push(new String("Larry")); + b.push(new String("Larry")); + ok(_.isEqual(a, b), "Arrays containing circular references and equivalent properties are equal"); + a.push("Shemp"); + b.push("Curly"); + ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal"); + + // Circular Objects. + a = {abc: null}; + b = {abc: null}; + a.abc = a; + b.abc = b; + ok(_.isEqual(a, b), "Objects containing circular references are equal"); + a.def = 75; + b.def = 75; + ok(_.isEqual(a, b), "Objects containing circular references and equivalent properties are equal"); + a.def = new Number(75); + b.def = new Number(63); + ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal"); + + // Cyclic Structures. + a = [{abc: null}]; + b = [{abc: null}]; + (a[0].abc = a).push(a); + (b[0].abc = b).push(b); + ok(_.isEqual(a, b), "Cyclic structures are equal"); + a[0].def = "Larry"; + b[0].def = "Larry"; + ok(_.isEqual(a, b), "Cyclic structures containing equivalent properties are equal"); + a[0].def = new String("Larry"); + b[0].def = new String("Curly"); + ok(!_.isEqual(a, b), "Cyclic structures containing different properties are not equal"); + + // Complex Circular References. + a = {foo: {b: {foo: {c: {foo: null}}}}}; + b = {foo: {b: {foo: {c: {foo: null}}}}}; + a.foo.b.foo.c.foo = a; + b.foo.b.foo.c.foo = b; + ok(_.isEqual(a, b), "Cyclic structures with nested and identically-named properties are equal"); + + // Chaining. + ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal'); + equals(_({x: 1, y: 2}).chain().isEqual(_({x: 1, y: 2}).chain()).value(), true, '`isEqual` can be chained'); + + // Custom `isEqual` methods. var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}}; var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}}; - ok(moe != clone, 'basic equality between objects is false'); - ok(_.isEqual(moe, clone), 'deep equality is true'); - ok(_(moe).isEqual(clone), 'OO-style deep equality works'); - ok(!_.isEqual(5, NaN), '5 is not equal to NaN'); - ok(NaN != NaN, 'NaN is not equal to NaN (native equality)'); - ok(NaN !== NaN, 'NaN is not equal to NaN (native identity)'); - ok(_.isEqual(NaN, NaN), 'NaN is equal to NaN'); - ok(!_.isEqual(5, NaN), '`5` is not equal to `NaN`'); - ok(!_.isEqual(false, NaN), '`false` is not equal to `NaN`'); - ok(_.isEqual(new Date(100), new Date(100)), 'identical dates are equal'); - ok(_.isEqual((/hello/ig), (/hello/ig)), 'identical regexes are equal'); - ok(!_.isEqual({source: '(?:)', global: true, multiline: true, ignoreCase: true}, /(?:)/gim), 'RegExp-like objects and RegExps are not equal'); - ok(!_.isEqual(null, [1]), 'a falsy is never equal to a truthy'); - ok(!_.isEqual(undefined, null), '`undefined` is not equal to `null`'); - ok(_.isEqual(isEqualObj, isEqualObj), 'both objects implement `isEqual`, same objects'); - ok(_.isEqual(isEqualObj, isEqualObjClone), 'both objects implement `isEqual`, different objects'); - ok(_.isEqual(isEqualObjClone, isEqualObj), 'both objects implement `isEqual`, different objects, swapped'); - ok(!_.isEqual(isEqualObj, {}), 'first object implements `isEqual`'); - ok(!_.isEqual({}, isEqualObj), 'second object implements `isEqual`'); - ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), 'objects with the same number of undefined keys are not equal'); - ok(!_.isEqual(new Foo, new Bar), 'objects with different inherited properties are not equal'); - ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'wrapped objects are not equal'); - equals(_({x: 1, y: 2}).chain().isEqual(_({x: 1, y: 2}).chain()).value(), true, 'wrapped objects are equal'); - // Objects with circular references. - var circularA = {'abc': null}, circularB = {'abc': null}; - circularA.abc = circularA; - circularB.abc = circularB; - ok(_.isEqual(circularA, circularB), 'objects with a circular reference'); - circularA.def = 1; - circularB.def = 1; - ok(_.isEqual(circularA, circularB), 'objects with identical properties and a circular reference'); - circularA.def = 1; - circularB.def = 0; - ok(!_.isEqual(circularA, circularB), 'objects with different properties and a circular reference'); - - // Arrays with circular references. - circularA = []; - circularB = []; - circularA.push(circularA); - circularB.push(circularB); - ok(_.isEqual(circularA, circularB), 'arrays with a circular reference'); - circularA.push('abc'); - circularB.push('abc'); - ok(_.isEqual(circularA, circularB), 'arrays with identical indices and a circular reference'); - circularA.push('hello'); - circularB.push('goodbye'); - ok(!_.isEqual(circularA, circularB), 'arrays with different properties and a circular reference'); - - // Hybrid cyclic structures. - circularA = [{'abc': null}]; - circularB = [{'abc': null}]; - circularA[0].abc = circularA; - circularB[0].abc = circularB; - circularA.push(circularA); - circularB.push(circularB); - ok(_.isEqual(circularA, circularB), 'cyclic structure'); - circularA[0].def = 1; - circularB[0].def = 1; - ok(_.isEqual(circularA, circularB), 'cyclic structure with identical properties'); - circularA[0].def = 1; - circularB[0].def = 0; - ok(!_.isEqual(circularA, circularB), 'cyclic structure with different properties'); + ok(_.isEqual(isEqualObj, isEqualObjClone), 'Both objects implement identical `isEqual` methods'); + ok(_.isEqual(isEqualObjClone, isEqualObj), 'Commutative equality is implemented for objects with custom `isEqual` methods'); + ok(!_.isEqual(isEqualObj, {}), 'Objects that do not implement equivalent `isEqual` methods are not equal'); + ok(!_.isEqual({}, isEqualObj), 'Commutative equality is implemented for objects with different `isEqual` methods'); }); test("objects: isEmpty", function() { From 54245bc679d448427bebd71a8608283476b2d752 Mon Sep 17 00:00:00 2001 From: Kit Cambridge Date: Mon, 5 Sep 2011 12:34:09 -0600 Subject: [PATCH 360/533] `_.isEqual`: Add an early comparison for `NaN` values. --- underscore.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/underscore.js b/underscore.js index 825e9405d..46f0ef685 100644 --- a/underscore.js +++ b/underscore.js @@ -605,12 +605,14 @@ if (typeA != typeof b) return false; // Optimization; ensure that both values are truthy or falsy. if (!a != !b) return false; + // `NaN` values are equal. + if (_.isNaN(a)) return _.isNaN(b); // Compare string objects by value. var isStringA = _.isString(a), isStringB = _.isString(b); if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b); - // Compare number objects by value. `NaN` values are equal. - var isNumberA = toString.call(a) == '[object Number]', isNumberB = toString.call(b) == '[object Number]'; - if (isNumberA || isNumberB) return isNumberA && isNumberB && (_.isNaN(a) ? _.isNaN(b) : +a == +b); + // Compare number objects by value. + var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b); + if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b; // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0. var isBooleanA = toString.call(a) == '[object Boolean]', isBooleanB = toString.call(b) == '[object Boolean]'; if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b; From 4fa97eb2fa7c387913bead871767912ab22d5ee2 Mon Sep 17 00:00:00 2001 From: Kit Cambridge Date: Mon, 5 Sep 2011 15:51:09 -0600 Subject: [PATCH 361/533] `_.isBoolean` should return `true` for boolean object wrappers. --- test/objects.js | 6 +++--- underscore.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/objects.js b/test/objects.js index 6fc60f1e4..95e5ab829 100644 --- a/test/objects.js +++ b/test/objects.js @@ -318,14 +318,14 @@ $(document).ready(function() { parent.iElement = document.createElement('div');\ parent.iArguments = (function(){ return arguments; })(1, 2, 3);\ parent.iArray = [1, 2, 3];\ - parent.iString = 'hello';\ - parent.iNumber = 100;\ + parent.iString = new String('hello');\ + parent.iNumber = new Number(100);\ parent.iFunction = (function(){});\ parent.iDate = new Date();\ parent.iRegExp = /hi/;\ parent.iNaN = NaN;\ parent.iNull = null;\ - parent.iBoolean = false;\ + parent.iBoolean = new Boolean(false);\ parent.iUndefined = undefined;\ " ); diff --git a/underscore.js b/underscore.js index 46f0ef685..e2a2fb260 100644 --- a/underscore.js +++ b/underscore.js @@ -614,7 +614,7 @@ var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b); if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b; // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0. - var isBooleanA = toString.call(a) == '[object Boolean]', isBooleanB = toString.call(b) == '[object Boolean]'; + var isBooleanA = _.isBoolean(a), isBooleanB = _.isBoolean(b); if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b; // Compare dates by their millisecond values. var isDateA = _.isDate(a), isDateB = _.isDate(b); @@ -738,7 +738,7 @@ // Is a given value a boolean? _.isBoolean = function(obj) { - return obj === true || obj === false; + return obj === true || obj === false || typeof obj == 'object' && toString.call(obj) == '[object Boolean]'; }; // Is a given value a date? From 1facc0e4fee98df915d0ecd8f20ce56482ba6875 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 4 Oct 2011 15:56:26 -0400 Subject: [PATCH 362/533] merging in Tim Smart's gorgeous deep equality patch for _.isEqual --- underscore.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/underscore.js b/underscore.js index fbd8e423b..fcd3d09c9 100644 --- a/underscore.js +++ b/underscore.js @@ -631,22 +631,21 @@ if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime(); // Compare RegExps by their source patterns and flags. var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b); - if (isRegExpA || isRegExpB) + if (isRegExpA || isRegExpB) { // Ensure commutative equality for RegExps. return isRegExpA && isRegExpB && a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; + } // Ensure that both values are objects. if (typeA != 'object') return false; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. - if (typeof a.isEqual == 'function') return a.isEqual(b); - // If only `b` provides an `isEqual` method, `a` and `b` are not equal. - if (typeof b.isEqual == 'function') return false; + if (_.isFunction(a.isEqual)) return a.isEqual(b); // Assume equality for cyclic structures. The algorithm for detecting cyclic structures is // adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = stack.length; @@ -748,7 +747,7 @@ // Is a given value a boolean? _.isBoolean = function(obj) { - return obj === true || obj === false || typeof obj == 'object' && toString.call(obj) == '[object Boolean]'; + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; }; // Is a given value a date? From 7d0e4169a9453da3c664f82327840a861244c574 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 4 Oct 2011 15:56:32 -0400 Subject: [PATCH 363/533] shortening module names. --- test/arrays.js | 2 +- test/chaining.js | 2 +- test/collections.js | 2 +- test/functions.js | 2 +- test/objects.js | 2 +- test/utility.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 78cf098a0..2d69235c4 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Array-only functions (last, compact, uniq, and so on...)"); + module("Arrays"); test("arrays: first", function() { equals(_.first([1,2,3]), 1, 'can pull out the first element of an array'); diff --git a/test/chaining.js b/test/chaining.js index e633ba5ad..64b0500ef 100644 --- a/test/chaining.js +++ b/test/chaining.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Underscore chaining."); + module("Chaining"); test("chaining: map/flatten/reduce", function() { var lyrics = [ diff --git a/test/collections.js b/test/collections.js index 005ee169e..95e111591 100644 --- a/test/collections.js +++ b/test/collections.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Collection functions (each, any, select, and so on...)"); + module("Collections"); test("collections: each", function() { _.each([1, 2, 3], function(num, i) { diff --git a/test/functions.js b/test/functions.js index 2a1bd51bd..af35e5eff 100644 --- a/test/functions.js +++ b/test/functions.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Function functions (bind, bindAll, and so on...)"); + module("Functions"); test("functions: bind", function() { var context = {name : 'moe'}; diff --git a/test/objects.js b/test/objects.js index ded65c317..e05c0ddc5 100644 --- a/test/objects.js +++ b/test/objects.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Object functions (values, extend, isEqual, and so on...)"); + module("Objects"); test("objects: keys", function() { var exception = /object/; diff --git a/test/utility.js b/test/utility.js index 94252a654..58368a13e 100644 --- a/test/utility.js +++ b/test/utility.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Utility functions (uniqueId, template)"); + module("Utility"); test("utility: noConflict", function() { var underscore = _.noConflict(); From 348c93515cf56263828c683ebab055e6c800b63b Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 4 Oct 2011 17:23:55 -0400 Subject: [PATCH 364/533] Issue #272 ... min and max of empty objects. --- test/collections.js | 6 ++++++ underscore.js | 2 ++ 2 files changed, 8 insertions(+) diff --git a/test/collections.js b/test/collections.js index 90cdd3d87..cf1c815d6 100644 --- a/test/collections.js +++ b/test/collections.js @@ -177,6 +177,9 @@ $(document).ready(function() { var neg = _.max([1, 2, 3], function(num){ return -num; }); equals(neg, 1, 'can perform a computation-based max'); + + equals(-Infinity, _.max({}), 'Maximum value of an empty object'); + equals(-Infinity, _.max([]), 'Maximum value of an empty array'); }); test('collections: min', function() { @@ -184,6 +187,9 @@ $(document).ready(function() { var neg = _.min([1, 2, 3], function(num){ return -num; }); equals(neg, 3, 'can perform a computation-based min'); + + equals(Infinity, _.min({}), 'Minimum value of an empty object'); + equals(Infinity, _.min([]), 'Minimum value of an empty array'); }); test('collections: sortBy', function() { diff --git a/underscore.js b/underscore.js index f59d040c7..0db66d0b6 100644 --- a/underscore.js +++ b/underscore.js @@ -220,6 +220,7 @@ // Return the maximum element or (element-based computation). _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); + if (!iterator && _.isEmpty(obj)) return -Infinity; var result = {computed : -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; @@ -231,6 +232,7 @@ // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); + if (!iterator && _.isEmpty(obj)) return Infinity; var result = {computed : Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; From 221161ceccd224f9b0f871426ddd656f358a2f5b Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Tue, 4 Oct 2011 22:24:36 -0400 Subject: [PATCH 365/533] better isXXX checks; the current tests have too many false positives --- test/objects.js | 5 +++-- underscore.js | 17 +++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/test/objects.js b/test/objects.js index e05c0ddc5..0e1f3e4f3 100644 --- a/test/objects.js +++ b/test/objects.js @@ -383,14 +383,15 @@ $(document).ready(function() { ok(!_.isNumber(arguments), 'the arguments object is not a number'); ok(!_.isNumber(undefined), 'undefined is not a number'); ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are'); - ok(!_.isNumber(NaN), 'NaN is not a number'); + ok(_.isNumber(NaN), 'NaN *is* a number'); ok(_.isNumber(Infinity), 'Infinity is a number'); ok(_.isNumber(iNumber), 'even from another frame'); + ok(!_.isNumber('1'), 'numeric strings are not numbers'); }); test("objects: isBoolean", function() { ok(!_.isBoolean(2), 'a number is not a boolean'); - ok(!_.isBoolean("string"), 'a string is not a boolean'); + ok(!_.isBoolean("string"), 'a string is not a boolean'); ok(!_.isBoolean("false"), 'the string "false" is not a boolean'); ok(!_.isBoolean("true"), 'the string "true" is not a boolean'); ok(!_.isBoolean(arguments), 'the arguments object is not a boolean'); diff --git a/underscore.js b/underscore.js index 0db66d0b6..182070eb3 100644 --- a/underscore.js +++ b/underscore.js @@ -714,7 +714,8 @@ return eq(a, b, []); }; - // Is a given array or object empty? + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; @@ -729,7 +730,7 @@ // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) === '[object Array]'; + return toString.call(obj) == '[object Array]'; }; // Is a given variable an object? @@ -739,22 +740,22 @@ // Is a given variable an arguments object? _.isArguments = function(obj) { - return !!(obj && hasOwnProperty.call(obj, 'callee')); + return toString.call(obj) == '[object Arguments]' || !!(obj && hasOwnProperty.call(obj, 'callee')); }; // Is a given value a function? _.isFunction = function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); + return toString.call(obj) == '[object Function]'; }; // Is a given value a string? _.isString = function(obj) { - return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); + return toString.call(obj) == '[object String]'; }; // Is a given value a number? _.isNumber = function(obj) { - return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); + return toString.call(obj) == '[object Number]'; }; // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript @@ -770,12 +771,12 @@ // Is a given value a date? _.isDate = function(obj) { - return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); + return toString.call(obj) == '[object Date]'; }; // Is the given value a regular expression? _.isRegExp = function(obj) { - return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); + return toString.call(obj) == '[object RegExp]'; }; // Is a given value equal to null? From 6d9d071b2f3c0d375517163005ff27a04383c4d6 Mon Sep 17 00:00:00 2001 From: Pier Paolo Ramon Date: Wed, 5 Oct 2011 14:06:18 +0200 Subject: [PATCH 366/533] Implemented _.init as per #319 --- underscore.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/underscore.js b/underscore.js index 0db66d0b6..dfc875d3a 100644 --- a/underscore.js +++ b/underscore.js @@ -323,6 +323,14 @@ return slice.call(array, (index == null) || guard ? 1 : index); }; + // Returns everything but the last entry of the array. Especcialy useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.init = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + // Get the last element of an array. Passing **n** will return the last N // values in the array. The **guard** check allows it to work with `_.map`. _.last = function(array, n, guard) { From dcda142655619b92ba2f3cc8b904d473ad426e29 Mon Sep 17 00:00:00 2001 From: Pier Paolo Ramon Date: Wed, 5 Oct 2011 14:14:51 +0200 Subject: [PATCH 367/533] Tests for _.init --- test/arrays.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/arrays.js b/test/arrays.js index 02e282b8a..3d5cc3327 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -24,6 +24,15 @@ $(document).ready(function() { equals(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map'); }); + test("arrays: init", function() { + equals(_.init([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working init()'); + equals(_.init([1,2,3,4],2).join(", "), "1, 2", 'init can take an index'); + var result = (function(){ return _(arguments).init(); })(1, 2, 3, 4); + equals(result.join(", "), "1, 2, 3", 'init works on arguments object'); + result = _.map([[1,2,3],[1,2,3]], _.init); + equals(_.flatten(result).join(','), '1,2,1,2', 'init works with _.map'); + }); + test("arrays: last", function() { equals(_.last([1,2,3]), 3, 'can pull out the last element of an array'); equals(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last'); From ac191a28a54302613d706af1f04236af8f195e5b Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 5 Oct 2011 15:32:34 -0400 Subject: [PATCH 368/533] merging in #324 as _.initial --- test/arrays.js | 14 +++++++------- underscore.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 3d5cc3327..80da71f6f 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -24,13 +24,13 @@ $(document).ready(function() { equals(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map'); }); - test("arrays: init", function() { - equals(_.init([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working init()'); - equals(_.init([1,2,3,4],2).join(", "), "1, 2", 'init can take an index'); - var result = (function(){ return _(arguments).init(); })(1, 2, 3, 4); - equals(result.join(", "), "1, 2, 3", 'init works on arguments object'); - result = _.map([[1,2,3],[1,2,3]], _.init); - equals(_.flatten(result).join(','), '1,2,1,2', 'init works with _.map'); + test("arrays: initial", function() { + equals(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()'); + equals(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index'); + var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4); + equals(result.join(", "), "1, 2, 3", 'initial works on arguments object'); + result = _.map([[1,2,3],[1,2,3]], _.initial); + equals(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map'); }); test("arrays: last", function() { diff --git a/underscore.js b/underscore.js index dfc875d3a..e183d070a 100644 --- a/underscore.js +++ b/underscore.js @@ -327,7 +327,7 @@ // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. - _.init = function(array, n, guard) { + _.initial = function(array, n, guard) { return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); }; From cc6a9d494d77ff910064d17719848445c05642ee Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 5 Oct 2011 16:19:00 -0400 Subject: [PATCH 369/533] Merging in escaping for Underscore templates, using <%- syntax. Sorry Eco. --- test/utility.js | 4 ++++ underscore.js | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/utility.js b/test/utility.js index 58368a13e..976b3b996 100644 --- a/test/utility.js +++ b/test/utility.js @@ -81,6 +81,10 @@ $(document).ready(function() { var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.'); equals(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.'); + var template = _.template("<%- value %>"); + var result = template({value: "" ); iDoc.close(); diff --git a/underscore.js b/underscore.js index e535966b3..ef515d167 100644 --- a/underscore.js +++ b/underscore.js @@ -84,6 +84,8 @@ var fn = iterator; var i = -1; var l = obj.length; + + // We optimized for common use by only binding a context when it's passed if (context) { iterator = function() { return fn.call(context, obj[i], i, obj); }; } @@ -98,7 +100,7 @@ } }; - // A simple each, for when we know we're dealing with an array. + // A simple each, for dealing with non-sparse arrays and arguments objects var simpleEach = function(obj, iterator, index) { index || (index = 0); for (var l = obj.length; index < l; index++) { @@ -112,15 +114,15 @@ 'toLocaleString', 'toString', 'valueOf' ]; - // IE < 9 skips enumerable properties shadowing non-enumerable ones. + // IE < 9 makes properties, shadowing non-enumerable ones, non-enumerable too var forShadowed = !{valueOf:0}.propertyIsEnumerable('valueOf') && function(obj, iterator) { - // because IE < 9 can't set the `[[Enumerable]]` attribute of an existing + // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing // property and the `constructor` property of a prototype defaults to // non-enumerable, we manually skip the `constructor` property when we // think we are iterating over a `prototype` object. var ctor = obj.constructor; - var skipCtor = ctor && ctor.prototype && ctor.prototype.constructor == ctor; + var skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor; for (var key, i = 0; key = shadowed[i]; i++) { if (!(skipCtor && key == 'constructor') && hasOwnProperty.call(obj, key) && @@ -131,24 +133,25 @@ }; // Iterates over an object's properties, executing the `callback` for each. - var forProps = function(obj, iterator, ownOnly, context) { + var forProps = function(obj, iterator, ownOnly) { var done = !obj; var skipProto = typeof obj == 'function'; for (var key in obj) { - // Opera < 12 and Safari < 5.1 (if the prototype or a property on the prototype has been set) + // Firefox < 3.6, Opera > 9.50 - Opera < 12, and Safari < 5.1 + // (if the prototype or a property on the prototype has been set) // incorrectly set a function's `prototype` property [[Enumerable]] value // to true. Because of this we standardize on skipping the the `prototype` // property of functions regardless of their [[Enumerable]] value. if (done = !(skipProto && key == 'prototype') && (!ownOnly || ownOnly && hasOwnProperty.call(obj, key)) && - iterator.call(context, obj[key], key, obj) === breaker) { + iterator(obj[key], key, obj) === breaker) { break; } } if (!done && forShadowed) { - forShadowed(obj, iterator, context); + forShadowed(obj, iterator); } }; @@ -1016,9 +1019,8 @@ // A method to easily add functions to the OOP wrapper. var addToWrapper = function(name, func) { wrapper.prototype[name] = function() { - var args = slice.call(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); + unshift.call(arguments, this._wrapped); + return result(func.apply(_, arguments), this._chain); }; }; From 649f629bb172f73f771b4716ef9a469199a5d531 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sat, 3 Dec 2011 22:06:54 -0500 Subject: [PATCH 431/533] underscore: Allow _.reduce and _.reduceRight to have an explicitly undefined value. [jddalton] --- test/collections.js | 25 +++++++++++++++---------- test/objects.js | 13 ++++++------- underscore.js | 12 +++++++----- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/test/collections.js b/test/collections.js index 1726629eb..857ae3731 100644 --- a/test/collections.js +++ b/test/collections.js @@ -1,4 +1,4 @@ -$(document).ready(function() { +$(document).ready(function($, undefined) { module("Collections"); @@ -8,7 +8,7 @@ $(document).ready(function() { }); var answers = []; - _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); + _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier); }, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); answers = []; @@ -75,15 +75,14 @@ $(document).ready(function() { ifnull = ex; } ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); - ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); + equals(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); + raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); - // Sparse arrays: - var sparseArray = []; - sparseArray[100] = 10; - sparseArray[200] = 20; - - equals(_.reduce(sparseArray, function(a, b){ return a + b }), 30, 'initially-sparse arrays with no memo'); + var sparseArray = []; + sparseArray[0] = 20; + sparseArray[2] = -5; + equals(_.reduce(sparseArray, function(a, b){ return a - b }), 25, 'initially-sparse arrays with no memo'); }); test('collections: reduceRight', function() { @@ -103,8 +102,14 @@ $(document).ready(function() { ifnull = ex; } ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); - ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); + equals(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); + raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); + + var sparseArray = []; + sparseArray[0] = 20; + sparseArray[2] = -5; + equals(_.reduceRight(sparseArray, function(a, b){ return a - b }), -25, 'initially-sparse arrays with no memo'); }); test('collections: detect', function() { diff --git a/test/objects.js b/test/objects.js index 6d432033a..fbcc02a39 100644 --- a/test/objects.js +++ b/test/objects.js @@ -1,18 +1,17 @@ -$(document).ready(function() { +$(document).ready(function($, undefined) { module("Objects"); test("objects: keys", function() { - var exception = /object/; equals(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object'); // the test above is not safe because it relies on for-in enumeration order var a = []; a[1] = 0; equals(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95'); - raises(function() { _.keys(null); }, exception, 'throws an error for `null` values'); - raises(function() { _.keys(void 0); }, exception, 'throws an error for `undefined` values'); - raises(function() { _.keys(1); }, exception, 'throws an error for number primitives'); - raises(function() { _.keys('a'); }, exception, 'throws an error for string primitives'); - raises(function() { _.keys(true); }, exception, 'throws an error for boolean primitives'); + raises(function() { _.keys(null); }, TypeError, 'throws an error for `null` values'); + raises(function() { _.keys(void 0); }, TypeError, 'throws an error for `undefined` values'); + raises(function() { _.keys(1); }, TypeError, 'throws an error for number primitives'); + raises(function() { _.keys('a'); }, TypeError, 'throws an error for string primitives'); + raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives'); }); test("objects: values", function() { diff --git a/underscore.js b/underscore.js index e535966b3..19f374db9 100644 --- a/underscore.js +++ b/underscore.js @@ -167,7 +167,7 @@ // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = memo !== void 0; + var initial = arguments.length > 2; if (obj == null) obj = []; if (nativeReduce && obj.reduce === nativeReduce) { if (context) iterator = _.bind(iterator, context); @@ -181,20 +181,22 @@ memo = iterator.call(context, memo, value, index, list); } }); - if (!initial) throw new TypeError("Reduce of empty array with no initial value"); + if (!initial) throw new TypeError('Reduce of empty array with no initial value'); return memo; }; // The right-associative version of reduce, also known as `foldr`. // Delegates to **ECMAScript 5**'s native `reduceRight` if available. _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; if (obj == null) obj = []; if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (context) iterator = _.bind(iterator, context); - return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } - var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); - return _.reduce(reversed, iterator, memo, context); + var reversed = _.toArray(obj).reverse(); + if (context && !initial) iterator = _.bind(iterator, context); + return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); }; // Return the first value which passes a truth test. Aliased as `detect`. From 3df51e7004de9de948421ed4f253f13f98f4fb86 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 5 Dec 2011 11:45:03 -0500 Subject: [PATCH 432/533] fixing typo. --- underscore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index e535966b3..721722962 100644 --- a/underscore.js +++ b/underscore.js @@ -385,7 +385,7 @@ return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; - // Returns everything but the last entry of the array. Especcialy useful on + // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. From 2c5661ebb30461d7a004372318617ded7a332ae7 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Tue, 6 Dec 2011 01:16:56 -0500 Subject: [PATCH 433/533] underscore: Avoid regression and cleanup comments. [jddalton] --- test/objects.js | 2 +- underscore.js | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/test/objects.js b/test/objects.js index 5edea3710..73c56376d 100644 --- a/test/objects.js +++ b/test/objects.js @@ -262,7 +262,7 @@ $(document).ready(function($, undefined) { ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects"); ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent"); - // Objects with shadowing properties + // Objects with properties that shadow non-enumerable ones. ok(!_.isEqual({}, {toString: 1}), "Object with custom toString is not equal to {}"); ok(_.isEqual({toString: 1, valueOf: 2}, {toString: 1, valueOf: 2}), "Objects with equivalent shadow properties"); diff --git a/underscore.js b/underscore.js index c6587c99b..34fd319d5 100644 --- a/underscore.js +++ b/underscore.js @@ -25,8 +25,8 @@ // Create quick reference variables for speed access to core prototypes. var concat = ArrayProto.concat, + push = ArrayProto.push, slice = ArrayProto.slice, - unshift = ArrayProto.unshift, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; @@ -85,7 +85,7 @@ var i = -1; var l = obj.length; - // We optimized for common use by only binding a context when it's passed + // We optimize for common use by only binding a context when it's passed. if (context) { iterator = function() { return fn.call(context, obj[i], i, obj); }; } @@ -100,7 +100,7 @@ } }; - // A simple each, for dealing with non-sparse arrays and arguments objects + // A simple each, for dealing with non-sparse arrays and arguments objects. var simpleEach = function(obj, iterator, index) { index || (index = 0); for (var l = obj.length; index < l; index++) { @@ -114,7 +114,7 @@ 'toLocaleString', 'toString', 'valueOf' ]; - // IE < 9 makes properties, shadowing non-enumerable ones, non-enumerable too + // IE < 9 makes properties, shadowing non-enumerable ones, non-enumerable too. var forShadowed = !{valueOf:0}.propertyIsEnumerable('valueOf') && function(obj, iterator) { // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing @@ -1021,8 +1021,9 @@ // A method to easily add functions to the OOP wrapper. var addToWrapper = function(name, func) { wrapper.prototype[name] = function() { - unshift.call(arguments, this._wrapped); - return result(func.apply(_, arguments), this._chain); + var args = [this._wrapped]; + push.apply(args, arguments); + return result(func.apply(_, args), this._chain); }; }; From b3290c127afa939b70075735fdf714c0abb2be1c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 6 Dec 2011 14:08:47 -0500 Subject: [PATCH 434/533] style tweaks. --- underscore.js | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/underscore.js b/underscore.js index 34fd319d5..1640fecf8 100644 --- a/underscore.js +++ b/underscore.js @@ -21,7 +21,9 @@ var breaker = {}; // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + var ArrayProto = Array.prototype, + ObjProto = Object.prototype, + FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. var concat = ArrayProto.concat, @@ -46,6 +48,12 @@ nativeKeys = Object.keys, nativeBind = FuncProto.bind; + // List of possible shadowed properties on Object.prototype. + var shadowed = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' + ]; + // Create a safe reference to the Underscore object for use below. var _ = function(obj) { return new wrapper(obj); }; @@ -57,13 +65,11 @@ exports = module.exports = _; } exports._ = _; + // Register as a named module with AMD. } else if (typeof define === 'function' && define.amd) { - // Register as a named module with AMD. - define('underscore', function() { - return _; - }); + define('underscore', function() { return _; }); + // Exported as a string, for Closure Compiler "advanced" mode. } else { - // Exported as a string, for Closure Compiler "advanced" mode. root['_'] = _; } @@ -79,24 +85,23 @@ var each = _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); + return obj.forEach(iterator, context); + } + var fn = iterator; + var i = -1; + var l = obj.length; + // We optimize for common use by only binding a context when it's passed. + if (context) { + iterator = function() { return fn.call(context, obj[i], i, obj); }; + } + // If we're dealing with an array or array-like object... + if (l === l >>> 0) { + while (++i < l) { + if (i in obj && iterator(obj[i], i, obj) == breaker) return; + } + // Otherwise, delegate to `forProps` over an object's keys and values. } else { - var fn = iterator; - var i = -1; - var l = obj.length; - - // We optimize for common use by only binding a context when it's passed. - if (context) { - iterator = function() { return fn.call(context, obj[i], i, obj); }; - } - // If we're dealing with an array or array-like object... - if (l === l >>> 0) { - while (++i < l) { - if (i in obj && iterator(obj[i], i, obj) == breaker) return; - } - } else { - forProps(obj, iterator, true); - } + forProps(obj, iterator, true); } }; @@ -108,12 +113,6 @@ } }; - // List of possible shadowed properties on Object.prototype. - var shadowed = [ - 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', - 'toLocaleString', 'toString', 'valueOf' - ]; - // IE < 9 makes properties, shadowing non-enumerable ones, non-enumerable too. var forShadowed = !{valueOf:0}.propertyIsEnumerable('valueOf') && function(obj, iterator) { @@ -136,13 +135,12 @@ var forProps = function(obj, iterator, ownOnly) { var done = !obj; var skipProto = typeof obj == 'function'; - for (var key in obj) { // Firefox < 3.6, Opera > 9.50 - Opera < 12, and Safari < 5.1 // (if the prototype or a property on the prototype has been set) - // incorrectly set a function's `prototype` property [[Enumerable]] value + // incorrectly set a function's `prototype` property `[[Enumerable]]` value // to true. Because of this we standardize on skipping the the `prototype` - // property of functions regardless of their [[Enumerable]] value. + // property of functions regardless of their `[[Enumerable]]` value. if (done = !(skipProto && key == 'prototype') && (!ownOnly || ownOnly && hasOwnProperty.call(obj, key)) && From 9e4192729375c8eab68913d36de9fbb8ffbbdfcc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 6 Dec 2011 17:33:43 -0500 Subject: [PATCH 435/533] rolling back to the previous implementation of 'each' ... cancels out #385 --- test/arrays.js | 12 +-- test/collections.js | 33 +++--- test/objects.js | 86 +++------------- underscore.js | 242 ++++++++++++++++---------------------------- 4 files changed, 117 insertions(+), 256 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 7e2c7328b..1c910355f 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -127,12 +127,9 @@ $(document).ready(function() { }); test("arrays: indexOf", function() { - var numbers = [1]; - numbers[2] = 3; + var numbers = [1, 2, 3]; numbers.indexOf = null; - equals(_.indexOf(numbers, 3), 2, 'can compute indexOf, even without the native function'); - equals(_.indexOf(numbers, void 0), -1, 'handles sparse arrays correctly'); - + equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3); equals(result, 1, 'works on an arguments object'); equals(_.indexOf(null, 2), -1, 'handles nulls properly'); @@ -151,13 +148,10 @@ $(document).ready(function() { }); test("arrays: lastIndexOf", function() { - var numbers = [1, 0, 1, 0, 0, 1, 0]; - numbers[8] = 0; + var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; numbers.lastIndexOf = null; equals(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); - equals(_.lastIndexOf(numbers, void 0), -1, 'handles sparse arrays correctly'); - var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0); equals(result, 5, 'works on an arguments object'); equals(_.indexOf(null, 2), -1, 'handles nulls properly'); diff --git a/test/collections.js b/test/collections.js index a3c2886e3..1726629eb 100644 --- a/test/collections.js +++ b/test/collections.js @@ -1,4 +1,4 @@ -$(document).ready(function($, undefined) { +$(document).ready(function() { module("Collections"); @@ -8,7 +8,7 @@ $(document).ready(function($, undefined) { }); var answers = []; - _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier); }, {multiplier : 5}); + _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); answers = []; @@ -16,11 +16,11 @@ $(document).ready(function($, undefined) { equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); answers = []; - var obj = {one: 1, two: 2, three: 3, toString: 1}; + var obj = {one : 1, two : 2, three : 3}; obj.constructor.prototype.four = 4; _.each(obj, function(value, key){ answers.push(key); }); + equals(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.'); delete obj.constructor.prototype.four; - equals(answers.sort().join(', '), 'one, three, toString, two', 'iterating over objects works, and ignores the object prototype.'); answer = null; _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); @@ -75,14 +75,15 @@ $(document).ready(function($, undefined) { ifnull = ex; } ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); - ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); - equals(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); - raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); - var sparseArray = []; - sparseArray[0] = 20; - sparseArray[2] = -5; - equals(_.reduce(sparseArray, function(a, b){ return a - b }), 25, 'initially-sparse arrays with no memo'); + ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); + + // Sparse arrays: + var sparseArray = []; + sparseArray[100] = 10; + sparseArray[200] = 20; + + equals(_.reduce(sparseArray, function(a, b){ return a + b }), 30, 'initially-sparse arrays with no memo'); }); test('collections: reduceRight', function() { @@ -102,14 +103,8 @@ $(document).ready(function($, undefined) { ifnull = ex; } ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); - ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); - equals(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); - raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); - var sparseArray = []; - sparseArray[0] = 20; - sparseArray[2] = -5; - equals(_.reduceRight(sparseArray, function(a, b){ return a - b }), -25, 'initially-sparse arrays with no memo'); + ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); }); test('collections: detect', function() { @@ -238,7 +233,7 @@ $(document).ready(function($, undefined) { equals(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements'); var numbers = _.toArray({one : 1, two : 2, three : 3}); - equals(numbers.sort().join(', '), '1, 2, 3', 'object flattened into array'); + equals(numbers.join(', '), '1, 2, 3', 'object flattened into array'); }); test('collections: size', function() { diff --git a/test/objects.js b/test/objects.js index 73c56376d..6d432033a 100644 --- a/test/objects.js +++ b/test/objects.js @@ -1,43 +1,22 @@ -$(document).ready(function($, undefined) { +$(document).ready(function() { module("Objects"); test("objects: keys", function() { - equals( - _.keys({one: 1, two: 2}).sort().join(', '), - 'one, two', - 'can extract the keys from an object' - ); - - equals( - _.keys({ - constructor: 'a', - hasOwnProperty: 'b', - isPrototypeOf: 'c', - propertyIsEnumerable: 'd', - toLocaleString: 'e', - toString: 'f', - valueOf: 'g' - }).sort().join(', '), - 'constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, toString, valueOf', - 'keys of shadowing properties' - ); - - var fn = function(){}; - fn.x = fn.y = fn.z = fn.prototype.a = 1; - equals(_.keys(fn).sort().join(', '), 'x, y, z', 'keys of functions works, and ignores the prototype property'); - + var exception = /object/; + equals(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object'); + // the test above is not safe because it relies on for-in enumeration order var a = []; a[1] = 0; equals(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95'); - raises(function() { _.keys(null); }, TypeError, 'throws an error for `null` values'); - raises(function() { _.keys(void 0); }, TypeError, 'throws an error for `undefined` values'); - raises(function() { _.keys(1); }, TypeError, 'throws an error for number primitives'); - raises(function() { _.keys('a'); }, TypeError, 'throws an error for string primitives'); - raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives'); + raises(function() { _.keys(null); }, exception, 'throws an error for `null` values'); + raises(function() { _.keys(void 0); }, exception, 'throws an error for `undefined` values'); + raises(function() { _.keys(1); }, exception, 'throws an error for number primitives'); + raises(function() { _.keys('a'); }, exception, 'throws an error for string primitives'); + raises(function() { _.keys(true); }, exception, 'throws an error for boolean primitives'); }); test("objects: values", function() { - equals(_.values({one : 1, two : 2}).sort().join(', '), '1, 2', 'can extract the values from an object'); + equals(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object'); }); test("objects: functions", function() { @@ -51,38 +30,20 @@ $(document).ready(function($, undefined) { test("objects: extend", function() { var result; - var expected = { - constructor: 'a', - hasOwnProperty: 'b', - isPrototypeOf: 'c', - propertyIsEnumerable: 'd', - toLocaleString: 'e', - toString: 'f', - valueOf: 'g' - }; - equals(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another'); equals(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination'); equals(_.extend({x:'x'}, {a:'b'}).x, 'x', 'properties not in source dont get overriden'); - - result = _.extend({}, expected); - ok(_.isEqual(result, expected), 'extend with shadow properties'); - result = _.extend({x:'x'}, {a:'a'}, {b:'b'}); ok(_.isEqual(result, {x:'x', a:'a', b:'b'}), 'can extend from multiple source objects'); - result = _.extend({x:'x'}, {a:'a', x:2}, {a:'b'}); ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps'); - result = _.extend({}, {a: void 0, b: null}); equals(_.keys(result).join(''), 'b', 'extend does not copy undefined values'); }); test("objects: defaults", function() { var result; - var Options = function() { this.empty = ''; this.string = 'string'; this.zero = 0; }; - _.extend(Options.prototype, {nan: NaN, one:1}); - var options = new Options; + var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"}; _.defaults(options, {zero: 1, one: 10, twenty: 20}); equals(options.zero, 0, 'value exists'); @@ -96,14 +57,9 @@ $(document).ready(function($, undefined) { }); test("objects: clone", function() { - var toString = function() { return this.name; }; - var valueOf = function() { return this.name; }; - var moe = {name : 'moe', lucky : [13, 27, 34], toString: toString, valueOf: valueOf}; - + var moe = {name : 'moe', lucky : [13, 27, 34]}; var clone = _.clone(moe); equals(clone.name, 'moe', 'the clone as the attributes of the original'); - equals(clone.toString, toString, 'cloned own toString method'); - equals(clone.valueOf, valueOf, 'cloned own valueOf method'); clone.name = 'curly'; ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original'); @@ -126,16 +82,6 @@ $(document).ready(function($, undefined) { } Second.prototype.value = 2; - var obj = { - constructor: 'a', - hasOwnProperty: 'b', - isPrototypeOf: 'c', - propertyIsEnumerable: 'd', - toLocaleString: 'e', - toString: 'f', - valueOf: 'g' - }; - // Basic equality and identity comparisons. ok(_.isEqual(null, null), "`null` is equal to `null`"); ok(_.isEqual(), "`undefined` is equal to `undefined`"); @@ -262,10 +208,6 @@ $(document).ready(function($, undefined) { ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects"); ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent"); - // Objects with properties that shadow non-enumerable ones. - ok(!_.isEqual({}, {toString: 1}), "Object with custom toString is not equal to {}"); - ok(_.isEqual({toString: 1, valueOf: 2}, {toString: 1, valueOf: 2}), "Objects with equivalent shadow properties"); - // `A` contains nested objects and arrays. a = { name: new String("Moe Howard"), @@ -416,7 +358,7 @@ $(document).ready(function($, undefined) { test("objects: isEmpty", function() { ok(!_([1]).isEmpty(), '[1] is not empty'); ok(_.isEmpty([]), '[] is empty'); - ok(!_.isEmpty({valueOf: 1}), '{valueOf: 1} is not empty'); + ok(!_.isEmpty({one : 1}), '{one : 1} is not empty'); ok(_.isEmpty({}), '{} is empty'); ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty'); ok(_.isEmpty(null), 'null is empty'); @@ -446,7 +388,7 @@ $(document).ready(function($, undefined) { parent.iNaN = NaN;\ parent.iNull = null;\ parent.iBoolean = new Boolean(false);\ - parent.iUndefined = void 0;\ + parent.iUndefined = undefined;\ " ); iDoc.close(); diff --git a/underscore.js b/underscore.js index 1640fecf8..86e262343 100644 --- a/underscore.js +++ b/underscore.js @@ -21,14 +21,11 @@ var breaker = {}; // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, - ObjProto = Object.prototype, - FuncProto = Function.prototype; + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. - var concat = ArrayProto.concat, - push = ArrayProto.push, - slice = ArrayProto.slice, + var slice = ArrayProto.slice, + unshift = ArrayProto.unshift, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; @@ -48,12 +45,6 @@ nativeKeys = Object.keys, nativeBind = FuncProto.bind; - // List of possible shadowed properties on Object.prototype. - var shadowed = [ - 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', - 'toLocaleString', 'toString', 'valueOf' - ]; - // Create a safe reference to the Underscore object for use below. var _ = function(obj) { return new wrapper(obj); }; @@ -65,11 +56,13 @@ exports = module.exports = _; } exports._ = _; - // Register as a named module with AMD. } else if (typeof define === 'function' && define.amd) { - define('underscore', function() { return _; }); - // Exported as a string, for Closure Compiler "advanced" mode. + // Register as a named module with AMD. + define('underscore', function() { + return _; + }); } else { + // Exported as a string, for Closure Compiler "advanced" mode. root['_'] = _; } @@ -85,71 +78,17 @@ var each = _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { - return obj.forEach(iterator, context); - } - var fn = iterator; - var i = -1; - var l = obj.length; - // We optimize for common use by only binding a context when it's passed. - if (context) { - iterator = function() { return fn.call(context, obj[i], i, obj); }; - } - // If we're dealing with an array or array-like object... - if (l === l >>> 0) { - while (++i < l) { - if (i in obj && iterator(obj[i], i, obj) == breaker) return; + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; } - // Otherwise, delegate to `forProps` over an object's keys and values. } else { - forProps(obj, iterator, true); - } - }; - - // A simple each, for dealing with non-sparse arrays and arguments objects. - var simpleEach = function(obj, iterator, index) { - index || (index = 0); - for (var l = obj.length; index < l; index++) { - iterator(obj[index]); - } - }; - - // IE < 9 makes properties, shadowing non-enumerable ones, non-enumerable too. - var forShadowed = !{valueOf:0}.propertyIsEnumerable('valueOf') && - function(obj, iterator) { - // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing - // property and the `constructor` property of a prototype defaults to - // non-enumerable, we manually skip the `constructor` property when we - // think we are iterating over a `prototype` object. - var ctor = obj.constructor; - var skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor; - for (var key, i = 0; key = shadowed[i]; i++) { - if (!(skipCtor && key == 'constructor') && - hasOwnProperty.call(obj, key) && - iterator(obj[key], key, obj) === breaker) { - break; + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; } } - }; - - // Iterates over an object's properties, executing the `callback` for each. - var forProps = function(obj, iterator, ownOnly) { - var done = !obj; - var skipProto = typeof obj == 'function'; - for (var key in obj) { - // Firefox < 3.6, Opera > 9.50 - Opera < 12, and Safari < 5.1 - // (if the prototype or a property on the prototype has been set) - // incorrectly set a function's `prototype` property `[[Enumerable]]` value - // to true. Because of this we standardize on skipping the the `prototype` - // property of functions regardless of their `[[Enumerable]]` value. - if (done = - !(skipProto && key == 'prototype') && - (!ownOnly || ownOnly && hasOwnProperty.call(obj, key)) && - iterator(obj[key], key, obj) === breaker) { - break; - } - } - if (!done && forShadowed) { - forShadowed(obj, iterator); } }; @@ -168,7 +107,7 @@ // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; + var initial = memo !== void 0; if (obj == null) obj = []; if (nativeReduce && obj.reduce === nativeReduce) { if (context) iterator = _.bind(iterator, context); @@ -182,22 +121,20 @@ memo = iterator.call(context, memo, value, index, list); } }); - if (!initial) throw new TypeError('Reduce of empty array with no initial value'); + if (!initial) throw new TypeError("Reduce of empty array with no initial value"); return memo; }; // The right-associative version of reduce, also known as `foldr`. // Delegates to **ECMAScript 5**'s native `reduceRight` if available. _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; if (obj == null) obj = []; if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } - var reversed = _.toArray(obj).reverse(); - if (context && !initial) iterator = _.bind(iterator, context); - return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); + var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); + return _.reduce(reversed, iterator, memo, context); }; // Return the first value which passes a truth test. Aliased as `detect`. @@ -252,7 +189,7 @@ // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); + iterator = iterator || _.identity; var result = false; if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); @@ -334,8 +271,7 @@ criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; + var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }), 'value'); }; @@ -356,8 +292,7 @@ // be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator) { iterator || (iterator = _.identity); - var low = 0; - var high = array.length; + var low = 0, high = array.length; while (low < high) { var mid = (low + high) >> 1; iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; @@ -367,9 +302,10 @@ // Safely convert anything iterable into a real, live array. _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable) || _.isArguments(iterable)) return slice.call(iterable); + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + if (_.isArray(iterable)) return slice.call(iterable); + if (_.isArguments(iterable)) return slice.call(iterable); return _.values(iterable); }; @@ -388,7 +324,7 @@ return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; - // Returns everything but the last entry of the array. Especially useful on + // Returns everything but the last entry of the array. Especcialy useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. @@ -484,9 +420,10 @@ // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { - var length = _.max(_.pluck(arguments, 'length')); + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(arguments, i); + for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); return results; }; @@ -535,6 +472,7 @@ range[idx++] = start; start += step; } + return range; }; @@ -554,10 +492,10 @@ if (!_.isFunction(func)) throw new TypeError; args = slice.call(arguments, 2); return bound = function() { - if (!(this instanceof bound)) return func.apply(context, concat.apply(args, arguments)); + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); ctor.prototype = func.prototype; var self = new ctor; - var result = func.apply(self, concat.apply(args, arguments)); + var result = func.apply(self, args.concat(slice.call(arguments))); if (Object(result) === result) return result; return self; }; @@ -566,20 +504,16 @@ // 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(obj) { - var i = 1; - var funcs = arguments; - if (funcs.length < 2) { - i = 0; - funcs = _.functions(obj); - } - simpleEach(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }, i); + var funcs = slice.call(arguments, 1); + if (funcs.length == 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { - hasher || (hasher = _.identity); var memo = {}; + hasher || (hasher = _.identity); return function() { var key = hasher.apply(this, arguments); return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); @@ -654,7 +588,7 @@ // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { - var args = concat.apply([func], arguments); + var args = [func].concat(slice.call(arguments)); return wrapper.apply(this, args); }; }; @@ -662,9 +596,9 @@ // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { - var funcs = arguments; + var funcs = slice.call(arguments); return function() { - var args = arguments; + var args = slice.call(arguments); for (var i = funcs.length - 1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } @@ -688,9 +622,7 @@ _.keys = nativeKeys || function(obj) { if (obj !== Object(obj)) throw new TypeError('Invalid object'); var keys = []; - forProps(obj, function(value, key) { - keys[keys.length] = key; - }, true); + for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; return keys; }; @@ -703,29 +635,29 @@ // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; - forProps(obj, function(value, key) { - if (_.isFunction(value)) names[names.length] = key; - }); + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { - simpleEach(arguments, function(source) { - forProps(source, function(value, key) { - if (value !== void 0) obj[key] = value; - }); - }, 1); + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (source[prop] !== void 0) obj[prop] = source[prop]; + } + }); return obj; }; // Fill in a given object with default properties. _.defaults = function(obj) { - simpleEach(arguments, function(source) { - forProps(source, function(value, key) { - if (obj[key] == null) obj[key] = value; - }); - }, 1); + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + }); return obj; }; @@ -754,8 +686,8 @@ if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. - if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); - if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); + if (_.isFunction(a.isEqual)) return a.isEqual(b); + if (_.isFunction(b.isEqual)) return b.isEqual(a); // Compare `[[Class]]` names. var className = toString.call(a); if (className != toString.call(b)) return false; @@ -764,11 +696,13 @@ case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. - return a == String(b); + return String(a) == String(b); case '[object Number]': + a = +a; + b = +b; // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b); case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their @@ -808,19 +742,21 @@ } } else { // Objects with different constructors are not equivalent. - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; + if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false; // Deep compare objects. - forProps(a, function(value, key) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = hasOwnProperty.call(b, key) && eq(value, b[key], stack))) return breaker; - }, true); + for (var key in a) { + if (hasOwnProperty.call(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; + } + } // Ensure that both objects contain the same number of properties. if (result) { - forProps(b, function() { - return !(size--) && breaker; - }, true); + for (key in b) { + if (hasOwnProperty.call(b, key) && !(size--)) break; + } result = !size; } } @@ -837,15 +773,9 @@ // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { - var result = toString.call(obj); - if (result == '[object Array]' || result == '[object String]') { - return !obj.length; - } - forProps(obj, function() { - result = false; - return breaker; - }, true); - return !!result; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; + return true; }; // Is a given value a DOM element? @@ -865,11 +795,11 @@ }; // Is a given variable an arguments object? - _.isArguments = function(obj) { - return toString.call(obj) == '[object Arguments]'; - }; - - if (!_.isArguments(arguments)) { + if (toString.call(arguments) == '[object Arguments]') { + _.isArguments = function(obj) { + return toString.call(obj) == '[object Arguments]'; + }; + } else { _.isArguments = function(obj) { return !!(obj && hasOwnProperty.call(obj, 'callee')); }; @@ -949,7 +879,7 @@ // Add your own custom functions to the Underscore object, ensuring that // they're correctly added to the OOP wrapper as well. _.mixin = function(obj) { - simpleEach(_.functions(obj), function(name){ + each(_.functions(obj), function(name){ addToWrapper(name, _[name] = obj[name]); }); }; @@ -1019,8 +949,8 @@ // A method to easily add functions to the OOP wrapper. var addToWrapper = function(name, func) { wrapper.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); + var args = slice.call(arguments); + unshift.call(args, this._wrapped); return result(func.apply(_, args), this._chain); }; }; @@ -1029,7 +959,7 @@ _.mixin(_); // Add all mutator Array functions to the wrapper. - simpleEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { method.apply(this._wrapped, arguments); @@ -1038,7 +968,7 @@ }); // Add all accessor Array functions to the wrapper. - simpleEach(['concat', 'join', 'slice'], function(name) { + each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { return result(method.apply(this._wrapped, arguments), this._chain); From c67eaf672b57ce60625837131d7ac925cfc9d835 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 7 Dec 2011 09:29:11 -0500 Subject: [PATCH 436/533] adding back style and implementation tweaks from #385 --- underscore.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/underscore.js b/underscore.js index 86e262343..6c06acbab 100644 --- a/underscore.js +++ b/underscore.js @@ -189,7 +189,7 @@ // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. var any = _.some = _.any = function(obj, iterator, context) { - iterator = iterator || _.identity; + iterator || (iterator = _.identity); var result = false; if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); @@ -588,7 +588,7 @@ // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { - var args = [func].concat(slice.call(arguments)); + var args = concat.apply([func], arguments); return wrapper.apply(this, args); }; }; @@ -596,9 +596,9 @@ // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { - var funcs = slice.call(arguments); + var funcs = arguments; return function() { - var args = slice.call(arguments); + var args = arguments; for (var i = funcs.length - 1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } @@ -686,8 +686,8 @@ if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. - if (_.isFunction(a.isEqual)) return a.isEqual(b); - if (_.isFunction(b.isEqual)) return b.isEqual(a); + if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); + if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); // Compare `[[Class]]` names. var className = toString.call(a); if (className != toString.call(b)) return false; @@ -696,13 +696,11 @@ case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. - return String(a) == String(b); + return a == String(b); case '[object Number]': - a = +a; - b = +b; // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. - return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b); + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their @@ -742,7 +740,7 @@ } } else { // Objects with different constructors are not equivalent. - if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false; + if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; // Deep compare objects. for (var key in a) { if (hasOwnProperty.call(a, key)) { @@ -795,11 +793,10 @@ }; // Is a given variable an arguments object? - if (toString.call(arguments) == '[object Arguments]') { - _.isArguments = function(obj) { - return toString.call(obj) == '[object Arguments]'; - }; - } else { + _.isArguments = function(obj) { + return toString.call(obj) == '[object Arguments]'; + }; + if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return !!(obj && hasOwnProperty.call(obj, 'callee')); }; From cf1d69227b3a7ccba6cea46a9a5b8e4b3cb10f6e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 7 Dec 2011 09:30:55 -0500 Subject: [PATCH 437/533] adding concat reference --- underscore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/underscore.js b/underscore.js index 6c06acbab..6e01bbad1 100644 --- a/underscore.js +++ b/underscore.js @@ -25,6 +25,7 @@ // Create quick reference variables for speed access to core prototypes. var slice = ArrayProto.slice, + concat = ArrayProto.concat, unshift = ArrayProto.unshift, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; From 770f0876b360c69f519c726dc4c55b4821067c6a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 7 Dec 2011 09:35:07 -0500 Subject: [PATCH 438/533] re-adding 649f62 --- test/collections.js | 20 ++++++++++++++------ underscore.js | 12 +++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/test/collections.js b/test/collections.js index 1726629eb..499432ac1 100644 --- a/test/collections.js +++ b/test/collections.js @@ -77,13 +77,13 @@ $(document).ready(function() { ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); + equals(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); + raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); - // Sparse arrays: - var sparseArray = []; - sparseArray[100] = 10; - sparseArray[200] = 20; - - equals(_.reduce(sparseArray, function(a, b){ return a + b }), 30, 'initially-sparse arrays with no memo'); + var sparseArray = []; + sparseArray[0] = 20; + sparseArray[2] = -5; + equals(_.reduce(sparseArray, function(a, b){ return a - b }), 25, 'initially-sparse arrays with no memo'); }); test('collections: reduceRight', function() { @@ -105,6 +105,14 @@ $(document).ready(function() { ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); + + equals(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); + raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); + + var sparseArray = []; + sparseArray[0] = 20; + sparseArray[2] = -5; + equals(_.reduceRight(sparseArray, function(a, b){ return a - b }), -25, 'initially-sparse arrays with no memo'); }); test('collections: detect', function() { diff --git a/underscore.js b/underscore.js index 6e01bbad1..22446234d 100644 --- a/underscore.js +++ b/underscore.js @@ -108,7 +108,7 @@ // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = memo !== void 0; + var initial = arguments.length > 2; if (obj == null) obj = []; if (nativeReduce && obj.reduce === nativeReduce) { if (context) iterator = _.bind(iterator, context); @@ -122,20 +122,22 @@ memo = iterator.call(context, memo, value, index, list); } }); - if (!initial) throw new TypeError("Reduce of empty array with no initial value"); + if (!initial) throw new TypeError('Reduce of empty array with no initial value'); return memo; }; // The right-associative version of reduce, also known as `foldr`. // Delegates to **ECMAScript 5**'s native `reduceRight` if available. _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; if (obj == null) obj = []; if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (context) iterator = _.bind(iterator, context); - return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } - var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); - return _.reduce(reversed, iterator, memo, context); + var reversed = _.toArray(obj).reverse(); + if (context && !initial) iterator = _.bind(iterator, context); + return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); }; // Return the first value which passes a truth test. Aliased as `detect`. From 813bb57bf07d507790c6d0957dd113f0cbff0f5e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 7 Dec 2011 10:12:08 -0500 Subject: [PATCH 439/533] Underscore.js 1.2.3 --- docs/underscore.html | 66 +++++++++++++++++++++++--------------------- index.html | 28 +++++++++++++++---- package.json | 2 +- test/arrays.js | 5 ---- underscore-min.js | 48 ++++++++++++++++---------------- underscore.js | 14 ++-------- 6 files changed, 86 insertions(+), 77 deletions(-) diff --git a/docs/underscore.html b/docs/underscore.html index d2dafbd59..fffbe9cbb 100644 --- a/docs/underscore.html +++ b/docs/underscore.html @@ -1,11 +1,12 @@ - underscore.js

          underscore.js

          Underscore.js 1.2.2
          -(c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
          +      underscore.js           
          return_;});}else{return_.indexOf(other,item)>=0;});}); - };returnarray[i]===item?i:-1;}if(nativeIndexOf&&array.indexOf===nativeIndexOf)returnarray.indexOf(item); - for(i=0,l=array.length;i<l;i++)if(array[i]===item)returni; + for(i=0,l=array.length;i<l;i++)if(iinarray&&array[i]===item)returni;return-1;}; allowing you to adjust arguments, run code before and after, and conditionally execute the original function.

          .replace(/\t/g,'\\t')+"');}return __p.join('');";varfunc=newFunction('obj','_',tmpl); - returndata?func(data,_):function(data){returnfunc(data,_)}; + if(data)returnfunc(data,_); + returnfunction(data){ + returnfunc.call(this,data,_); + };};

          underscore.js

          Underscore.js 1.2.3
          +(c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc.
           Underscore is freely distributable under the MIT license.
           Portions of Underscore are inspired or borrowed from Prototype,
           Oliver Steele's Functional, and John Resig's Micro-Templating.
           For all details and documentation:
           http://documentcloud.github.com/underscore
           
          (function() {

          Baseline setup

          Establish the root object, window in the browser, or global on the server.

            var root = this;

          Save the previous value of the _ variable.

            var previousUnderscore = root._;

          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, FuncProto = Function.prototype;

          Create quick reference variables for speed access to core prototypes.

            var slice            = ArrayProto.slice,
          +      concat           = ArrayProto.concat,
                 unshift          = ArrayProto.unshift,
                 toString         = ObjProto.toString,
                 hasOwnProperty   = ObjProto.hasOwnProperty;

          All ECMAScript 5 native function implementations that we hope to use @@ -32,7 +33,7 @@ CommonJS, add _ to the global object.

          Exported as a string, for Closure Compiler "advanced" mode.

              root['_'] = _;
          -  }

          Current version.

            _.VERSION = '1.2.2';

          Collection Functions

          The cornerstone, an each implementation, aka forEach. + }

          Current version.

            _.VERSION = '1.2.3';

          Collection Functions

          The cornerstone, an each implementation, aka forEach. Handles objects with the built-in forEach, arrays, and raw objects. Delegates to ECMAScript 5's native forEach if available.

            var each = _.each = _.forEach = function(obj, iterator, context) {
               if (obj == null) return;
          @@ -60,7 +61,7 @@ Delegates to ECMAScript 5's native map if availabl
               return results;
             };

          Reduce builds up a single result from a list of values, aka inject, or foldl. Delegates to ECMAScript 5's native reduce if available.

            _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
          -    var initial = memo !== void 0;
          +    var initial = arguments.length > 2;
               if (obj == null) obj = [];
               if (nativeReduce && obj.reduce === nativeReduce) {
                 if (context) iterator = _.bind(iterator, context);
          @@ -74,17 +75,19 @@ or foldl. Delegates to ECMAScript 5's native memo = iterator.call(context, memo, value, index, list);
                 }
               });
          -    if (!initial) throw new TypeError("Reduce of empty array with no initial value");
          +    if (!initial) throw new TypeError('Reduce of empty array with no initial value');
               return memo;
             };

          The right-associative version of reduce, also known as foldr. Delegates to ECMAScript 5's native reduceRight if available.

            _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
          +    var initial = arguments.length > 2;
               if (obj == null) obj = [];
               if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
                 if (context) iterator = _.bind(iterator, context);
          -      return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
          +      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
               }
          -    var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
          -    return _.reduce(reversed, iterator, memo, context);
          +    var reversed = _.toArray(obj).reverse();
          +    if (context && !initial) iterator = _.bind(iterator, context);
          +    return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
             };

          Return the first value which passes a truth test. Aliased as detect.

            _.find = _.detect = function(obj, iterator, context) {
               var result;
               any(obj, function(value, index, list) {
          @@ -124,7 +127,7 @@ Aliased as all.

          };

          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.

            var any = _.some = _.any = function(obj, iterator, context) {
          -    iterator = iterator || _.identity;
          +    iterator || (iterator = _.identity);
               var result = false;
               if (obj == null) return result;
               if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
          @@ -269,9 +272,10 @@ passed-in arrays. (Aliased as "intersect" for back-compat.)

          Take the difference between one array and another. -Only the elements present in just the first array will remain.

            _.difference = function(array, other) {
          -    return _.filter(array, function(value){ return !_.include(other, value); });
          +  };

          Take the difference between one array and a number of other arrays. +Only the elements present in just the first array will remain.

            _.difference = function(array) {
          +    var rest = _.flatten(slice.call(arguments, 1));
          +    return _.filter(array, function(value){ return !_.include(rest, value); });
             };

          Zip together multiple lists into a single array -- elements that share an index go together.

            _.zip = function() {
               var args = slice.call(arguments);
          @@ -292,13 +296,13 @@ for isSorted to use binary search.

          Delegates to ECMAScript 5's native lastIndexOf if available.

            _.lastIndexOf = function(array, item) {
               if (array == null) return -1;
               if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
               var i = array.length;
          -    while (i--) if (array[i] === item) return i;
          +    while (i--) if (i in array && array[i] === item) return i;
               return -1;
             };

          Generate an integer Array containing an arithmetic progression. A port of the native Python range() function. See @@ -400,14 +404,14 @@ often you call it. Useful for lazy initialization.

            _.wrap = function(func, wrapper) {
               return function() {
          -      var args = [func].concat(slice.call(arguments));
          +      var args = concat.apply([func], arguments);
                 return wrapper.apply(this, args);
               };
             };

          Returns a function that is the composition of a list of functions, each consuming the return value of the function that follows.

            _.compose = function() {
          -    var funcs = slice.call(arguments);
          +    var funcs = arguments;
               return function() {
          -      var args = slice.call(arguments);
          +      var args = arguments;
                 for (var i = funcs.length - 1; i >= 0; i--) {
                   args = [funcs[i].apply(this, args)];
                 }
          @@ -457,15 +461,13 @@ order to perform operations on intermediate results within the chain.

          return obj; };

          Internal recursive comparison function.

            function eq(a, b, stack) {

          Identical objects are equal. 0 === -0, but they aren't identical. See the Harmony egal proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.

              if (a === b) return a !== 0 || 1 / a == 1 / b;

          A strict comparison is necessary because null == undefined.

              if (a == null || b == null) return a === b;

          Unwrap any wrapped objects.

              if (a._chain) a = a._wrapped;
          -    if (b._chain) b = b._wrapped;

          Invoke a custom isEqual method if one is provided.

              if (_.isFunction(a.isEqual)) return a.isEqual(b);
          -    if (_.isFunction(b.isEqual)) return b.isEqual(a);

          Compare [[Class]] names.

              var className = toString.call(a);
          +    if (b._chain) b = b._wrapped;

          Invoke a custom isEqual method if one is provided.

              if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
          +    if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);

          Compare [[Class]] names.

              var className = toString.call(a);
               if (className != toString.call(b)) return false;
               switch (className) {

          Strings, numbers, dates, and booleans are compared by value.

                case '[object String]':

          Primitives and their corresponding object wrappers are equivalent; thus, "5" is -equivalent to new String("5").

                  return String(a) == String(b);
          -      case '[object Number]':
          -        a = +a;
          -        b = +b;

          NaNs are equivalent, but non-reflexive. An egal comparison is performed for -other numeric values.

                  return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b);
          +equivalent to new String("5").

                  return a == String(b);
          +      case '[object Number]':

          NaNs are equivalent, but non-reflexive. An egal comparison is performed for +other numeric values.

                  return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
                 case '[object Date]':
                 case '[object Boolean]':

          Coerce dates and booleans to numeric primitive values. Dates are compared by their millisecond representations. Note that invalid dates with millisecond representations @@ -485,7 +487,7 @@ unique nested structures.

          if (result) {

          Deep compare the contents, ignoring non-numeric properties.

                  while (size--) {

          Ensure commutative equality for sparse arrays.

                    if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
                   }
                 }
          -    } else {

          Objects with different constructors are not equivalent.

                if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false;

          Deep compare objects.

                for (var key in a) {
          +    } else {

          Objects with different constructors are not equivalent.

                if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;

          Deep compare objects.

                for (var key in a) {
                   if (hasOwnProperty.call(a, key)) {

          Count the expected number of properties.

                    size++;

          Deep compare each member.

                    if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
                   }
                 }

          Ensure that both objects contain the same number of properties.

                if (result) {
          @@ -510,11 +512,10 @@ Delegates to ECMA5's native Array.isArray

          return toString.call(obj) == '[object Array]'; };

          Is a given variable an object?

            _.isObject = function(obj) {
               return obj === Object(obj);
          -  };

          Is a given variable an arguments object?

            if (toString.call(arguments) == '[object Arguments]') {
          -    _.isArguments = function(obj) {
          -      return toString.call(obj) == '[object Arguments]';
          -    };
          -  } else {
          +  };

          Is a given variable an arguments object?

            _.isArguments = function(obj) {
          +    return toString.call(obj) == '[object Arguments]';
          +  };
          +  if (!_.isArguments(arguments)) {
               _.isArguments = function(obj) {
                 return !!(obj && hasOwnProperty.call(obj, 'callee'));
               };
          @@ -583,7 +584,10 @@ and correctly escapes quotes within interpolated code.

          The OOP Wrapper

          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. Wrapped objects may be chained.

            var wrapper = function(obj) { this._wrapped = obj; };

          Expose wrapper.prototype as _.prototype

            _.prototype = wrapper.prototype;

          Helper function to continue chaining intermediate results.

            var result = function(obj, chain) {
          diff --git a/index.html b/index.html
          index fd00cec9e..745311283 100644
          --- a/index.html
          +++ b/index.html
          @@ -126,11 +126,11 @@
           
               
          -        
          +        
          -        
          +        
          Development Version (1.2.2)Development Version (1.2.3) 34kb, Uncompressed with Comments
          Production Version (1.2.2)Production Version (1.2.3) < 4kb, Minified and Gzipped
          @@ -159,7 +159,7 @@
          first, initial, last, rest, compact, flatten, without, - union, intersection, difference, + union, intersection, difference, uniq, zip, indexOf, lastIndexOf, range

          @@ -620,10 +620,10 @@ _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);

          - difference_.difference(array, other) + difference_.difference(array, *others)
          Similar to without, but returns the values from array that - are not present in other. + are not present in the other arrays.

           _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
          @@ -1338,6 +1338,24 @@ _([1, 2, 3]).value();
           
                 

          Change Log

          +

          + 1.2.3Dec. 7, 2011
          +

            +
          • + Dynamic scope is now preserved for compiled _.template functions, + so you can use the value of this if you like. +
          • +
          • + Sparse array support of _.indexOf, _.lastIndexOf. +
          • +
          • + Both _.reduce and _.reduceRight can now be passed an + explicitly undefined value. (There's no reason why you'd + want to do this.) +
          • +
          +

          +

          1.2.2Nov. 14, 2011