Alternatively, is there a way to test whether attribute b is defined as undefined versus simply not being defined?
Yes, there are lots of ways to distinguish these cases.
"b" in left; // true
"b" in right; // false
left.hasOwnProperty("b"); // true
right.hasOwnProperty("b"); // false
left.propertyIsEnumerable('b'); // true
right.propertyIsEnumerable('b'); // false
Object.keys(left).includes("b"); // true
Object.keys(right).includes("b"); // false
Object.getOwnPropertyNames(left).includes("b"); // true
Object.getOwnPropertyNames(right).includes("b"); // false
Reflect.ownKeys(left).includes("b"); // true
Reflect.ownKeys(right).includes("b"); // false
!!Object.getOwnPropertyDescriptor(left, 'b'); // true
!!Object.getOwnPropertyDescriptor(right, 'b'); // false
!!Reflect.getOwnPropertyDescriptor(left, 'b'); // true
!!Reflect.getOwnPropertyDescriptor(right, 'b'); // false
!!(o => {for(var p in o) if(p==='b') return 1; return 0;})(left); // true
!!(o => {for(var p in o) if(p==='b') return 1; return 0;})(right); // false
Looking at the annotated source, I think this will be enough:
_.myEqual = function(a,b) {
var keys = _.keys;
_.keys = function(obj) { /* Hijack _.keys to filter out undefined properties */
return _.filter(keys(obj), function(key){ return obj[key] !== void 0; });
};
var ret = _.isEqual(a,b); /* Call usual comparator, will use modified _.keys */
_.keys = keys; /* Restore usual _.keys */
return ret;
}
_.myEqual = function(a,b) {
var keys = _.keys;
_.keys = function(obj) {
return _.filter(keys(obj), key => obj[key] !== void 0);
};
var ret = _.isEqual(a,b);
_.keys = keys;
return ret;
}
console.log(_.myEqual({a: true, b: undefined}, {a: true})); // true
console.log(_.myEqual({a: true, b: true}, {a: true})); // false
<script src="https://raw.githubusercontent.com/jashkenas/underscore/master/underscore.js"></script>