For an exercise, I've written a function that, given an array of objects and a key (2nd argument), returns a new array of objects that contain all properties from a key. How'd I do?
function where(collection, source) {
var arr = [];
var keys = Object.keys(source);
var countMatchingProps = 0;
var currentProp;
for (var i = 0; i < collection.length; i++) {
countMatchingProps = 0;
for(var j = 0; j < keys.length; j++){
// assigned to variable for a bit of readability.
currentProp = keys[j];
// if object contains key ->
if(collection[i].hasOwnProperty(currentProp)){
// -> then compare their values nad increment counter
if(collection[i][currentProp] === source[currentProp]){
countMatchingProps++;
}
}
// if number of matched properties are
// equal to keys we can push current object to array
if (countMatchingProps === keys.length) arr.push(collection[i]);
}
}
return arr;
}
where([{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 3 }], { "a": 1});
where([{ "a": 1, "b": 2 }, { "a": 1 }, { "a": 1, "b": 2, "c": 2 }], { "a": 1, "b": 2 })
2 Answers 2
Better readability could be achieved using array.filter
, since it creates the array for you and all you have to do is return true
or false
. No need to create an array, do the comparison and push yourself.
In the same way when checking for the values, Object.keys
can be used with array.every
to iterate through your constraints and see if each of the current item's keys match the constraint.
Also, I wouldn't call it source
. It's not a source of anything. It's more of a "constraint" for your collection. So I'd call it that way instead.
In terms of performance, array iteration functions are slower than your average for
loop (older APIs will tend to be more optimized). However, in terms of readability, these array APIs really shorten your code.
function where(collection, constraint){
// filter creates an array of items whose callback returns true for them
return collection.filter(function(collectionItem){
// every returns true when every item's callback returns true
return Object.keys(constraint).every(function(key){
return collectionItem.hasOwnProperty(key) && constraint[key] === collectionItem[key];
});
});
}
var a = where([{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 3 }], { "a": 1});
var b = where([{ "a": 1, "b": 2 }, { "a": 1 }, { "a": 1, "b": 2, "c": 2 }], { "a": 1, "b": 2 })
document.write(JSON.stringify(a));
document.write('<br>');
document.write(JSON.stringify(b));
The code can further be simplified by taking advantage of ES6 arrow functions. This removes the brackets (with one arg, the parens are optional), and the body can be an expression which would then be an implicit return value, eliminating return
.
function where(collection, constraint){
return collection.filter(collectionItem =>
Object.keys(constraint).every(key =>
collectionItem.hasOwnProperty(key) && constraint[key] === collectionItem[key]));
}
var a = where([{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 3 }], { "a": 1});
var b = where([{ "a": 1, "b": 2 }, { "a": 1 }, { "a": 1, "b": 2, "c": 2 }], { "a": 1, "b": 2 })
document.write(JSON.stringify(a));
document.write('<br>');
document.write(JSON.stringify(b));
-
1\$\begingroup\$ Nice. But maybe add a
collectionItem.hasOwnProperty
check to match OP's code \$\endgroup\$Flambino– Flambino2015年11月02日 23:25:57 +00:00Commented Nov 2, 2015 at 23:25 -
\$\begingroup\$ Thanks, great answer! Question though: in terms of performance, when you deal with huge collections (big json file or something) would you use loops like i did or it wouldn't have that much impact and stick with more readable way? \$\endgroup\$Ketus– Ketus2015年11月03日 11:18:08 +00:00Commented Nov 3, 2015 at 11:18
-
1\$\begingroup\$ @MaciejKetus I'd stick with the more readable way until I can prove that it's causing a bottleneck. Use a profiler to prove it. \$\endgroup\$Joseph– Joseph2015年11月03日 11:34:45 +00:00Commented Nov 3, 2015 at 11:34
You should consider changing your function into an Object.prototype
, this also removes the need for a collection
parameter:
Which would then become this
.
Array.prototype.where(source) {
var arr = [];
var keys = Object.keys(source);
var countMatchingProps = 0;
var currentProp;
for (var i = 0; i < this.length; i++) {
countMatchingProps = 0;
for(var j = 0; j < keys.length; j++){
// assigned to variable for a bit of readability.
currentProp = keys[j];
// if object contains key ->
if(this[i].hasOwnProperty(currentProp)){
// -> then compare their values and increment counter
if(this[i][currentProp] === source[currentProp]){
countMatchingProps++;
}
}
// if number of matched properties are
// equal to keys we can push current object to array
if (countMatchingProps === keys.length) arr.push(this[i]);
}
}
return arr;
}
[{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 3 }].where({ "a": 1 });
[{ "a": 1, "b": 2 }, { "a": 1 }, { "a": 1, "b": 2, "c": 2 }].where({ "a": 1, "b": 2 })
countMatchingProps
should be declared in thefor
looparr
: don't sacrifice a few characters for readability:array
is good.