tags:

views:

157

answers:

4

I've written two Array methods that I think will be pretty useful for the work I'm doing. Modeled after Ruby's array methods, here are my find and find_all methods. Just thought I'd toss them to the community and get some feedback. I'm pretty new to JS programming so I'm probably not using enough defensive mechanisms and maybe there are some optimizations to be made. Any thoughts?

Array.prototype.find = function(attrs){

    // If an object wasn't passed in return false, maybe throw format exception? 
    // or do primitive type search, but that's not really useful as you're passing in the very value you're looking for
    if (typeof attrs != 'object'){
     return false;
    }else{
     for(var i=0; i < this.length; i++){
      if (typeof this[i] != 'object'){
       return false;
      }else{
       var match = true;
       // Loop through all attributes of the parameters object and test for existence and match
       for(item in attrs){
        match = match && (this[i][item] && (this[i][item] === attrs[item]) );
       }
       if(match){
        return this[i];
       }
      }
     }
     // default return if no items found
     return false;
    } 
};

// find_all behaves similarly to find only returns all matched objects
// See ruby's find_all method on arrays
Array.prototype.find_all = function(attrs){
    // If an object wasn't passed in return false, maybe throw format exception? 
    // or do primitive type search, but that's not really useful as you're passing in the very value you're looking for
    if (typeof attrs != 'object'){
     return false;
    }else{
     var valid_items = [];
     for(var i=0; i < this.length; i++){
      if (typeof this[i] != 'object'){
       return false;
      }else{
       var match = true;
       // Loop through all attributes of the parameters object and test for existence and match
       for(item in attrs){
        match = match && (this[i][item] && (this[i][item] === attrs[item]) );
       }
       if(match){
        valid_items.push(this[i]);
       }
      }
     }
     return valid_items;
    } 
}

Some examples:

var a={id:1,parent_id:2}
var b={id:2,parent_id:3}
var c={id:3,parent_id:2}
var arr = [a,b,c]

arr.find({parent_id:2}) 
// Object id: 1 parent_id: 2

arr.find_all({parent_id:2})
// [Object id: 1 parent_id: 2, Object id: 3 parent_id: 2 ]
+1  A: 

Instead of "typeof attrs != 'object'", it is much more common to see:

if (attrs == null){
    return false;
}

(You could also use "if (attrs == undefined)").

John Fisher
well i was also guarding against the possibility of just passing in a value, like arr.find(1) I'd like to be searching only for object attributes
brad
shouldn't it be (!attrs) or (attrs !== null) ? equality in javascript is so slippery.... http://rayfd.wordpress.com/2007/03/18/really-understanding-javascripts-equality-and-identity/
Michael Paulukonis
OtherMichael, (!attrs) would be very dangerous if the user had passed a boolean value. That would end up comparing the value instead of checking to see if a value existed.
John Fisher
So the user passed in a boolean value. (! false) enters the conditional body, and returns false. (! true) does not enter the conditional body, and proceeds to the rest of the code...
Michael Paulukonis
A: 

These might be useful functions for you, but I would reconsider adding them to the Array prototype -- they just don't seem general-purpose enough for that since they only look for object property values. I would just use them as utility functions (passing in the array) or add them to some utility class. When I think of an array find method, I think of something more like the example here: http://www.hunlock.com/blogs/Mastering_Javascript_Arrays (but then I'm not familiar with Ruby's version).

bmoeskau
+3  A: 

Your function modifies the prototype Array. Some libraries feel this is okay (Prototype). Others are opposed (jQuery) to this global modification.

See this page at the Prototype website. You'll see why they feel that it's ok to modify the Array prototype but only if you're not using for .. in loops to iterate [edit: over arrays (which you're right, you're not doing)].

I actually prefer jQuery's decision to leave it alone given that you want your code to be reusable and to play nice with other code.

Also taking a look at the find algorithm, if this[i] has more properties than attrs, as long as the properties that they do have in common match, it will return true, but it would be false.

To do a complete comparison, you would have to check the length also, but objects don't have a length property, so you would have to count this[i] in one loop and attrs length separately.

Edit: Just looked at your examples. It looks like you are aware of the two objects not being entirely equal. Are you sure that you want {id:1,parent_id:2} to be found when searching for {parent_id:2}?? It seems more like that you're asking if the obj param is equal to a subset of properties of any object in an array of objects. You want to call this "find"? Is that really want you want to do? I would think find should only return if they are entirely equal or call it something else.

Edit 2: Another thought. I would definitely have find_all call find as its underlying mechanism. That way you don't repeat your code, and you'll more likely guarantee that anything find would match, find_all would match. You don't want differences to crop up between their underlying search algorithm. Although to do that you may have to refactor find slightly to return the index of the object rather than the object, which shouldn't be a problem.

Keith Bentrup
Interesting point about for.. in however I'm not using for .. in on any array such as your example. The attrs param is intended to be an object, so for .. in is suitable (is there some other way to iterate through attrs of an object?). Also you're mistaken with your comment that the match would return true if attrs contains more attributes than the object. "match = match " this[i][item] returns undefined if item isn't a member of the object in question
brad
It was a typo. I edited a few minutes ago. I meant if this[i] has more properties than attrs. You can see this in your example. You have {parent_id:2} find {id:1,parent_id:2}.
Keith Bentrup
Well actually I do want it to return the object if ANY of the params match, not necessarily exactly. From the ruby world, find works like this:[{:id=>1, parent_id => 2,{:id=>2,:parent_id=>2}].find{|x| x[:parent_id] == 2} returns both items
brad
It's your prerogative. I just wouldn't do something like: [array of objects].find(object) return true unless array[i] object _exactly_ matched object, but that's me.
Keith Bentrup
Prototype was overriding prototype methods long before other "real" JS libraries popped up and started colliding with each other, so it wasn't a big issue when they started. Nowadays, I think it's commonly accepted that you should not generally be doing that. Especially as an app developer, I would think you wouldn't want to potentially write code that could later conflict with other libs or with JS itself (as has happened with some libs in the past).
bmoeskau
+1  A: 

In addition to what the other answers have said, here are some non-JS specific things about your code:

When you have an if-clause which returns, you don't need an else.

if(foo) {
    return false;
}

//other code here

This will make your code more readable as there will be less indentation/levels to keep track of inside your head.

Your functions don't follow the usual JavaScript naming convention. If you look at any JS built in methods, they use camelCase and not underscore_separated. Following the conventions of a language is always a good idea.

Jani Hartikainen
good call, again I'm coming from ruby (and rails) which uses underscore notation for a lot of things but I'll make those changes
brad