diff --git a/README.md b/README.md index b502b2d..7303642 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,23 @@ Returns whether two objects are equal. Returns true if the given object has no properties and false otherwise. This is O(1) (unlike `Object.keys(obj).length === 0`, which is O(N)). +### hasKey(obj, key) + +Returns true if the given object has an enumerable, non-inherited property +called `key`. [For information on enumerability and ownership of properties, see +the MDN +documentation.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties) ### forEachKey(obj, callback) -Like Array.forEach, but iterates properties of an object rather than elements -of an array. Equivalent to: +Like Array.forEach, but iterates enumerable, owned properties of an object +rather than elements of an array. Equivalent to: - for (var key in obj) - callback(key, obj[key]); + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + callback(key, obj[key]); + } + } ### flattenObject(obj, depth) diff --git a/lib/jsprim.js b/lib/jsprim.js index 5ee4688..26c3ba1 100644 --- a/lib/jsprim.js +++ b/lib/jsprim.js @@ -15,6 +15,7 @@ var mod_jsonschema = require('json-schema'); exports.deepCopy = deepCopy; exports.deepEqual = deepEqual; exports.isEmpty = isEmpty; +exports.hasKey = hasKey; exports.forEachKey = forEachKey; exports.pluck = pluck; exports.flattenObject = flattenObject; @@ -122,10 +123,19 @@ function isEmpty(obj) return (true); } +function hasKey(obj, key) +{ + mod_assert.equal(typeof (key), 'string'); + return (Object.prototype.hasOwnProperty.call(obj, key)); +} + function forEachKey(obj, callback) { - for (var key in obj) - callback(key, obj[key]); + for (var key in obj) { + if (hasKey(obj, key)) { + callback(key, obj[key]); + } + } } function pluck(obj, key) diff --git a/test/basic.js b/test/basic.js index e64c4fc..92df8f1 100644 --- a/test/basic.js +++ b/test/basic.js @@ -60,6 +60,24 @@ mod_assert.ok(!jsprim.deepEqual(NaN, NaN)); mod_assert.ok(jsprim.isEmpty({})); mod_assert.ok(!jsprim.isEmpty({ 'foo': 'bar' })); +/* hasKey */ +mod_assert.ok(jsprim.hasKey(obj, 'family')); +mod_assert.ok(jsprim.hasKey(obj, 'children')); +mod_assert.ok(jsprim.hasKey(obj, 'home')); +mod_assert.ok(jsprim.hasKey(obj, 'income')); +mod_assert.ok(jsprim.hasKey(obj, 'dignity')); +mod_assert.ok(jsprim.hasKey(obj, 'nhomes')); +mod_assert.ok(obj.hasOwnProperty); +mod_assert.ok(!jsprim.hasKey(obj, 'hasOwnProperty')); +copy = Object.create(obj); +copy.aprop = 'avalue'; +mod_assert.ok(jsprim.hasKey(copy, 'aprop')); +mod_assert.equal(copy.nhomes, 1); +mod_assert.ok(!jsprim.hasKey(copy, 'nhomes')); +copy.hasOwnProperty = null; +mod_assert.ok(jsprim.hasKey(copy, 'aprop')); +mod_assert.ok(!jsprim.hasKey(copy, 'nhomes')); + /* forEachKey */ var keys = []; jsprim.forEachKey(obj, function (key, val) { @@ -70,6 +88,25 @@ keys.sort(); mod_assert.deepEqual(keys, [ 'children', 'dignity', 'family', 'home', 'income', 'nhomes' ]); +/* + * forEachKey skips non-own properties and works even on objects with a + * hasOwnProperty key overridden. + */ +copy = jsprim.deepCopy(obj); +Object.prototype.aprop = 'avalue'; +mod_assert.equal(copy.aprop, 'avalue'); +copy.hasOwnProperty = null; +keys = []; +jsprim.forEachKey(copy, function (key, val) { + mod_assert.deepEqual(copy[key], val); + keys.push(key); +}); +keys.sort(); +mod_assert.deepEqual(keys, [ 'children', 'dignity', 'family', + 'hasOwnProperty', 'home', 'income', 'nhomes' ]); +delete (Object.prototype.aprop); + + /* startsWith */ mod_assert.ok(jsprim.startsWith('foobar', 'f')); mod_assert.ok(jsprim.startsWith('foobar', 'foo'));