Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: jashkenas/underscore
base: 1.0.0
...
head fork: jashkenas/underscore
compare: 1.0.1
Checking mergeability… Don't worry, you can still create the pull request.
  • 1 commit
  • 5 files changed
  • 9 commit comments
  • 1 contributor
View
10 index.html
@@ -111,11 +111,11 @@
<p>
<table>
<tr>
- <td><a href="underscore.js">Development Version (1.0.0)</a></td>
+ <td><a href="underscore.js">Development Version (1.0.1)</a></td>
<td><i>25kb, Uncompressed with Comments</i></td>
</tr>
<tr>
- <td><a href="underscore-min.js">Production Version (1.0.0)</a></td>
+ <td><a href="underscore-min.js">Production Version (1.0.1)</a></td>
<td><i>2.5kb, Packed and Gzipped</i></td>
</tr>
</table>
@@ -1137,6 +1137,12 @@ <h2 id="duck_typing">Duck Typing</h2>
<h2>Change Log</h2>
<p>
+ <b class="header">1.0.1</b><br />
+ Bugfix for <tt>_.isEqual</tt>, when comparing two objects with the same
+ number of undefined keys, but with different names.
+ </p>
+
+ <p>
<b class="header">1.0.0</b><br />
Things have been stable for many months now, so Underscore is now
considered to be out of beta, at <b>1.0</b>. Improvements since <b>0.6</b>
View
2  package.json
@@ -7,5 +7,5 @@
"contributors" : [],
"dependencies" : [],
"lib" : ".",
- "version" : "1.0.0"
+ "version" : "1.0.1"
}
View
1  test/objects.js
@@ -58,6 +58,7 @@ $(document).ready(function() {
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');
+ ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), 'object with the same number of undefined keys are not equal');
});
test("objects: isEmpty", function() {
View
4 underscore-min.js
@@ -1,5 +1,5 @@
(function(){var n=this,A=n._,r=typeof StopIteration!=="undefined"?StopIteration:"__break__",B=function(a){return a.replace(/([.*+?^${}()|[\]\/\\])/g,"\\$1")},j=Array.prototype,l=Object.prototype,o=j.slice,C=j.unshift,D=l.toString,p=l.hasOwnProperty,E=l.propertyIsEnumerable,s=j.forEach,t=j.map,u=j.reduce,v=j.reduceRight,w=j.filter,x=j.every,y=j.some,m=j.indexOf,z=j.lastIndexOf;l=Array.isArray;var F=Object.keys,b=function(a){return new k(a)};if(typeof exports!=="undefined")exports._=b;n._=b;b.VERSION=
-"1.0.0";var i=b.forEach=function(a,c,d){try{if(s&&a.forEach===s)a.forEach(c,d);else if(b.isNumber(a.length))for(var e=0,f=a.length;e<f;e++)c.call(d,a[e],e,a);else for(e in a)p.call(a,e)&&c.call(d,a[e],e,a)}catch(g){if(g!=r)throw g;}return a};b.map=function(a,c,d){if(t&&a.map===t)return a.map(c,d);var e=[];i(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(u&&a.reduce===u)return a.reduce(b.bind(d,e),c);i(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=
+"1.0.1";var i=b.forEach=function(a,c,d){try{if(s&&a.forEach===s)a.forEach(c,d);else if(b.isNumber(a.length))for(var e=0,f=a.length;e<f;e++)c.call(d,a[e],e,a);else for(e in a)p.call(a,e)&&c.call(d,a[e],e,a)}catch(g){if(g!=r)throw g;}return a};b.map=function(a,c,d){if(t&&a.map===t)return a.map(c,d);var e=[];i(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(u&&a.reduce===u)return a.reduce(b.bind(d,e),c);i(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=
function(a,c,d,e){if(v&&a.reduceRight===v)return a.reduceRight(b.bind(d,e),c);a=b.clone(b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.detect=function(a,c,d){var e;i(a,function(f,g,h){if(c.call(d,f,g,h)){e=f;b.breakLoop()}});return e};b.filter=function(a,c,d){if(w&&a.filter===w)return a.filter(c,d);var e=[];i(a,function(f,g,h){c.call(d,f,g,h)&&e.push(f)});return e};b.reject=function(a,c,d){var e=[];i(a,function(f,g,h){!c.call(d,f,g,h)&&e.push(f)});return e};b.every=function(a,c,d){c=c||b.identity;
if(x&&a.every===x)return a.every(c,d);var e=true;i(a,function(f,g,h){(e=e&&c.call(d,f,g,h))||b.breakLoop()});return e};b.some=function(a,c,d){c=c||b.identity;if(y&&a.some===y)return a.some(c,d);var e=false;i(a,function(f,g,h){if(e=c.call(d,f,g,h))b.breakLoop()});return e};b.include=function(a,c){if(m&&a.indexOf===m)return a.indexOf(c)!=-1;var d=false;i(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};i(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};i(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.sortBy=function(a,c,d){return b.pluck(b.map(a,
@@ -9,7 +9,7 @@ e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return
for(var d=a.length;d--;)if(a[d]===c)return d;return-1};b.range=function(a,c,d){var e=b.toArray(arguments),f=e.length<=1;a=f?0:e[0];c=f?e[0]:e[1];d=e[2]||1;e=Math.ceil((c-a)/d);if(e<=0)return[];e=new Array(e);f=a;for(var g=0;;f+=d){if((d>0?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||{},d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);i(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=F||function(a){if(b.isArray(a))return b.range(0,
a.length);var c=[];for(var d in a)p.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){i(b.rest(arguments),function(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.tap=function(a,c){c(a);return 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;
+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(!(f in c)||!b.isEqual(a[f],c[f]))return false;
return true};b.isEmpty=function(a){if(b.isArray(a))return a.length===0;for(var c in a)if(p.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=l||function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!a.concat&&!a.substr&&!a.apply&&!E.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return a===
+a||D.call(a)==="[object Number]"};b.isBoolean=function(a){return a===true||a===false};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&&a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};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(){n._=A;return this};b.identity=function(a){return a};b.times=function(a,c,d){for(var e=
0;e<a;e++)c.call(d,e)};b.breakLoop=function(){throw r;};b.mixin=function(a){i(b.functions(a),function(c){G(c,b[c]=a[c])})};var H=0;b.uniqueId=function(a){var c=H++;return a?a+c:c};b.templateSettings={start:"<%",end:"%>",interpolate:/<%=(.+?)%>/g};b.template=function(a,c){var d=b.templateSettings,e=new RegExp("'(?=[^"+d.end.substr(0,1)+"]*"+B(d.end)+")","g");a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").replace(e,"\t").split("'").join("\\'").split("\t").join("'").replace(d.interpolate,
View
4 underscore.js
@@ -55,7 +55,7 @@
root._ = _;
// Current version.
- _.VERSION = '1.0.0';
+ _.VERSION = '1.0.1';
// ------------------------ Collection Functions: ---------------------------
@@ -485,7 +485,7 @@
// 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;
+ for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
return true;
};

Showing you all comments on commits in this comparison.

@wlangstroth

I don't know if this is too old-school, but couldn't you wrap isEqual in a var retval = false and only set it to true when the condition arises? That is,

_.isEqual = function(a, b) {
    var retval = false;
    // Check object identity.
    if (a === b) retval = true;
    // Basic equality test (watch out for coercions).
    if (a == b) retval =  true;
    // One of them implements an isEqual()?
    if (a.isEqual) retval = a.isEqual(b);
    // Check dates' integer values.
    if (_.isDate(a) && _.isDate(b)) retval = a.getTime() === b.getTime();
    // Both are NaN?
    if (_.isNaN(a) && _.isNaN(b)) retval = true;
    // Compare regular expressions.
    if (_.isRegExp(a) && _.isRegExp(b))
    retval = a.source     === b.source &&
         a.global     === b.global &&
         a.ignoreCase === b.ignoreCase &&
         a.multiline  === b.multiline;
    return retval;
  };
@jashkenas
Owner

You certainly could -- but the idea here is to attempt comparisons from least-expensive to most-expensive, bailing early if any of them succeed or fail. To accomplish this, we have to return early and return often. As soon as you know the answer, you don't want to continue testing.

@wlangstroth

Okay, then

_.isEqual = function(a, b) {
    var retval = false;
    // Check object identity.
    if (a === b) return retval = true;
    // Basic equality test (watch out for coercions).
    if (a == b) return retval =  true;
    // One of them implements an isEqual()?
    if (a.isEqual) return retval = a.isEqual(b);
    // Check dates' integer values.
    if (_.isDate(a) && _.isDate(b)) return retval = a.getTime() === b.getTime();
    // Both are NaN?
    if (_.isNaN(a) && _.isNaN(b)) return retval = true;
    // Compare regular expressions.
    if (_.isRegExp(a) && _.isRegExp(b))
    return retval = a.source     === b.source &&
         a.global     === b.global &&
         a.ignoreCase === b.ignoreCase &&
         a.multiline  === b.multiline;
    return retval;
  };

That way you're returning often, but you cut your conditionals in half. That's assuming, of course, that you prefer to have a bias toward "guilty until proven innocent", which is my tendency, but might not be yours.

@jashkenas
Owner

Wait, I'm confused -- if you're returning after every test, then what's the point of setting retval? -- It'll never be used. At that point, you might as well remove it, and then you're back to the code we started with (the current implementation of isEqual).

I also don't understand how you're "cutting the conditionals in half" ... please enlighten me.

@wlangstroth

return retval = true is strictly for readability, you're right that you don't need to do it.

If you look at the current implementation of isEqual, and the one I sketched there, mine has fewer lines. (I don't know if it's exactly half.) I took out the tests returning false, because you don't need them: retval is already false. That is, if it doesn't trip any of the conditions returning true, or the ones that are doubtful either way. Instead of those tests resulting in false, return retval.

@jashkenas
Owner

I see. I didn't realize that you were proposing to remove all of the false checks from isEqual -- that won't work at all. The reason why they exist is to disambiguate and bail early with inexpensive tests. We want to avoid reaching the bottom and having to do a recursive comparison of object properties (something I see you also omitted from your version).

But don't take my word for it -- If you drop in your proposal, you'll see a number of tests fail the test suite.

@wlangstroth

No, you're absolutely right. Sorry for speaking up with a half-baked idea. A false-biased approach is definitely wrong, especially in the case of comparing larger objects, so I'm not sure what I was thinking. My mistake.

@jashkenas
Owner

No worries. This sort of discussion is great to have out in public, and is instructive for folks who are looking through the source. So thanks for bringing it up.

@wlangstroth

You're very kind. To anyone who is reading this: sleep deprivation and coding do not mix.

Something went wrong with that request. Please try again.