I find the default Javascript extremely poor on useful functions. There are some nice libraries, but somehow I always need something I can't find. Currently, I'd need a method removing all matching properties from an object. And I really mean removing, not creating a filtered copy. I've found similar methods in both jquery and underscore, but no exact match.
That's a long introduction, why I want to give it a try. As both $
and _
are taken, I'm extending the Object.prototype
which should be fine. So this is the code:
function removeMatching(predicate) {
for (var key in this) {
if (!Object.prototype.hasOwnProperty.call(this, key)) continue;
if (predicate(key, this[key])) delete this[key];
};
};
Object.defineProperty(Object.prototype, 'removeMatching', {
value: removeMatching,
writable: false,
configurable: false,
enumerable: false
});
It's all gets enclosed in a closure to prevent namespace pollution. I don't care much about speed as this can be improved later (but tell me). I don't care about style (it's fixed) not about compatibility with old crap. I do care about all problems possible in modern browsers.
It's to be used like
var o = {a: 1, b: 2, c: 3};
o.removeMatching(function(k, v) {return k === "a" || v === 2});
// now o is {c: 3}
3 Answers 3
There are extraneous semicolons (null statements) in your code. Semicolons do not belong after function declarations and for
loops.
I'm extending the
Object.prototype
which should be fine.
I would argue that it's not fine. While it's unlikely to cause undesired side effects, breaking encapsulation and polluting all objects just for the sake of being able to write foo.removeMatching(bar)
instead of removeMatching(foo, bar)
seems like bad form.
if (!Object.prototype.hasOwnProperty.call(this, key)) continue;
Since this
is an Object by definition, because you are attaching this to Object.prototype
, there is no need to write it like that. This would achieve the same thing (unless you are shadowing hasOwnProperty
in which case you have bigger problems):
if (!this.hasOwnProperty(key)) continue;
However there's no need to write this line in the first place. If the object doesn't have a property as its own property, attempting to delete it will do nothing.
It's all gets enclosed in a closure to prevent namespace pollution.
This also seems unnecessary, why not just write it like this:
Object.defineProperty(Object.prototype, 'removeMatching', {
value: function (predicate) {
for (var key in this) {
if (predicate(key, this[key])) {
delete this[key];
}
}
},
writable: false,
configurable: false,
enumerable: false
});
-
\$\begingroup\$ Nice, thanks! Just a comment: "instead of removeMatching(foo, bar)" - no, I'd have to write
someGoddamnPrefix.removeMatching(foo, bar)
and make sure that it's in scope. And it's not writing, what takes time, it's reading. \$\endgroup\$maaartinus– maaartinus2014年06月24日 22:45:07 +00:00Commented Jun 24, 2014 at 22:45 -
2\$\begingroup\$ @maaartinus If this is intended to be generic library code for anyone to use, I would strongly urge you not to modify
Object.prototype
. It may be excusable in application code, but not in library code. You don't necessarily need to pollute the global scope withsomeGoddamnPrefix
; check for the presence of AMD-styledefine
and CJS-stlyeexports
and use one of them if available, and fall back to your "namespace" as a last resort. I'd also suggest simply using the name of your library as that namespace; it's much more meaningful and straightforward than something like$
or_
. \$\endgroup\$Dagg– Dagg2014年06月25日 06:12:31 +00:00Commented Jun 25, 2014 at 6:12
You could define it on Object
itself instead of adding it to the prototype and exposing it to every object. This would parallel how Object.keys
works:
Object.defineProperty(Object, 'removeMatching', { ... });
Since you're already requiring ECMAScript 5 you may as well use Object.keys
and Array.prototype.forEach
to perform the iteration. This removes the need for hasOwnProperty
(which @Dagg points out isn't necessary anyway), (削除) but it avoids the problem of modifying the underlying object's shape while iterating over it. I do not know which browsers, if any, would be affected by it. (削除ここまで)
Combining this with the above yields
Object.defineProperty(Object, 'removeMatching', {
value: function(obj, predicate) {
Object.keys(obj).forEach(function (key) {
if (predicate(key, obj[key])) {
delete obj[key];
}
};
},
writable: false,
configurable: false,
enumerable: false
});
var o = {a: 1, b: 2, c: 3};
Object.removeMatching(o, function(k, v) {return k === "a" || v === 2});
-
\$\begingroup\$ +2 if I could.. \$\endgroup\$konijn– konijn2014年06月26日 18:57:03 +00:00Commented Jun 26, 2014 at 18:57
-
\$\begingroup\$ @konijn, what in particular do you like about this answer? As is, your comment doesn't add much. IMO, adding properties to
Object
still breaks encapsulation (why not just store stuff in our own namespace?) and suggesting that there could be problems "modifying the underlying object's shape while iterating over it" in this case is misleading. It's not clear to me why you think this answer is worth double upvotes. \$\endgroup\$Dagg– Dagg2014年06月26日 21:57:17 +00:00Commented Jun 26, 2014 at 21:57 -
\$\begingroup\$ @Dagg Thx for the link. As I said, I wasn't sure how browsers treat this and didn't know it was in the spec. Luckily, every browser precisely adheres to the spec. ;) \$\endgroup\$David Harkness– David Harkness2014年06月27日 00:10:35 +00:00Commented Jun 27, 2014 at 0:10
-
1\$\begingroup\$ @Dagg, I am not sure what you mean with breaks encapsulation. +2 because I dont like modifying
Object.prototype
, I also dont like just stuffing in my own namespace, adding it to Object itself is an elegant lesser of evils for me. I will actually use this approach for my own code, which is kind of rare for answers on CR. \$\endgroup\$konijn– konijn2014年06月27日 12:59:38 +00:00Commented Jun 27, 2014 at 12:59 -
1\$\begingroup\$ I see... I was only thinking about conflicts between my own functions and a library or a new JS version. JS is just hopeless. \$\endgroup\$maaartinus– maaartinus2014年07月28日 22:41:16 +00:00Commented Jul 28, 2014 at 22:41
Put it on Function.prototype, makes more sense that way anyhow, and enhance it a bit more by offering two more lists -- (nature does this double-negative type of mechanism all the time). You could also get fancy with the return value.
filter.removePropertiesFrom(obj);// simple form
filter.removePropertiesFrom(obj,options);
// where
var options={
except:[names]||filterFn||RegEx
,always:[names]||filterFn||RegEx
};
// chaining, etc /////////////////////
// meh, pass forward options via a temp attached to a dynamic and()?
filter.removePropertiesFrom(obj).and(obj2);
filter.removePropertiesFrom(obj).fn;// hand back filter()
filter.removePropertiesFrom(obj).removed;// [names]
filter.removePropertiesFrom(obj).items;// new object with the removed
//inline call style
(function(k, v) {return k === "a" || v === 2}).removePropertiesFrom(obj);
Object.defineProperty()
only exists in modern browsers, but I assume you already know that. \$\endgroup\$if (Object.prototype.removeMatching === undefined) { ... }
\$\endgroup\$