Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

This seems a bit redundant to me, but I'm not sure of how else I might be able to write this. Normally I'd use a switch statement, but I don't think that'll work here. I realize I could also do this in a for loop, but the main heart of the question is what's inside the loop itself. "arr" is an array, just in case it's not obvious.

var i = 0, x = arr.length;

while (i < x) {
    if (typeof arr[i] === 'object' && !Array.isArray(arr[i])) {
        attr = arr[i];
    }
    else if (typeof arr[i] === 'string') {
        text = arr[i];
    }
    else if (typeof arr[i] === 'object' && Array.isArray(arr[i])) {
        child = arr[i];
    }
    i++;
}
share|improve this question
1  
Array.isArray will also work with non-objects. You don't need to check if it's an object. –  Prinzhorn 8 hours ago
 
I'd remove the x variable altogether; since arr.length is short and clear enough by itself. –  Zar 44 mins ago
add comment

7 Answers

+1 to @Jerry and three minor notes:

  1. var i = 0, x = arr.length;
    

    I'd put the variable declarations to separate lines. From Code Complete 2nd Edition, p759:

    With statements on their own lines, the code reads from top to bottom, instead of top to bottom and left to right. When you’re looking for a specific line of code, your eye should be able to follow the left margin of the code. It shouldn’t have to dip into each and every line just because a single line might contain two statements.

  2. I would use longer variable names than x. Longer names would make the code more readable since readers don't have to decode the abbreviations every time when they write/maintain the code and don't have to guess which abbreviation the author uses.

    So, arrayLength would be a readable name here.

    (Clean Code by Robert C. Martin, Avoid Mental Mapping, p25)

  3. I'd have started the refactoring with creating a local variable for the result of typeof arr[i] === 'object'. It's used twice.

    (Clean Code by Robert C. Martin, G19: Use Explanatory Variables; Refactoring: Improving the Design of Existing Code by Martin Fowler, Introduce Explaining Variable)

share|improve this answer
add comment

I think at the very least I'd rearrange the cases to make the dichotomy between Array.isArray and !Array.isArray more direct and apparent:

while (i < x) {
    if (typeof arr[i] === 'string') {
        text = arr[i];
    }
    else if (typeof arr[i] === 'object') {
        if (Array.isArray(arr[i])) {
            child = arr[i];
        }
        else {
            attr = arr[i];
        }
    }
    i++;
}

That's not really a lot shorter but I think it makes the intent rather more clear (at least assuming I've divined the real intent correctly).

share|improve this answer
add comment

Along with @jerrycoffin's suggestion I would write this as a for loop 10 times out of 10 (this is the conventional for loop case afterall). I tend to use for loops whenever I'm iterating every item of a single collection and a while loop for any other purpose (but either can always be written as the other so w/e floats your boat).

I would also probably cache the result of typeof arr[i] and if I were using arr[i] anywhere else in the loop I would probably make a variable for it as well. I usually consider using a switch if I use 3 or more typeof item so if you extend further that may be something to consider

Also x is a cryptic name for a loop control variable. Use l, len or length - don't worry about variable name length as you'll usually end up minifying your code if you're writing anything substantial.

That said

var type, current;

for (var i = 0, length = arr.length; i < length; i++) {
    current = arr[i];
    type = typeof current;
    if (type === 'string') {
        text = current;
    }
    else if (type === 'object') {
        if (Array.isArray(current)) {
            child = current;
        }
        else {
            attr = current;
        }
    }
}
share|improve this answer
add comment

I would do it this way:

arr.forEach(function (item) {
    if (Array.isArray(item)) {
        attr = item;
    }
    else if (typeof item === 'object') {
        child = item;
    }
    else if (typeof item === 'string') {
        text = item;
    }
});

By using arr.forEach there is no need to subscript (arr[i]). You just get passed the item.

Array.isArray works just fine with anything you pass it, so there is no need to check before if the thing is or isn't an object.

By checking “arrayness” first, then you know for sure that if typeof item === 'object' it is not an array. It simplifies the logic.

share|improve this answer
add comment

I'm going to suggest something a bit different.

Lets step back and look at what you are trying to do. You seem to want to extract the elements of the array according to their type.

Now your code will only find the last element in the array that is of the given type. If there are two strings in the array then the last one will be ignored. This may of course be what you want, but it doesn't provide for a good reusable solution. Sometimes you may want the first in the list, other times all of them.

I would suggest creating a reusable groupBy function

function groupBy(arr, fn) {
  return arr.reduce(function(result, item) {
     var group = fn(item);
     result[group] = result[group] || [];
     result[group].push(item);
     return result;
  }, {});
}

Then you can use this function to group your array by type :

function getType(item) {
    var type = typeof item,
        isArray = Array.isArray(item);

    return (type === 'object' && !isArray) ? 'obj' :
           (type === 'string') ? 'string' : 
           (type === 'object' && isArray) ? 'array' :
             'none';
}

var result = groupBy(arr, getType); 

You can then access the types you need through the object. If you want the first item in the list of that particular type :

text = result.string[0];
child = result.array[0];
attr = result.obj[0];

If you want the last item :

text = result.string[result.string.length - 1];
child = result.array[result.child.length - 1];
attr = result.obj[result.attr.length - 1];
share|improve this answer
 
I was going to write something along these lines when I saw your answer. The fact that the code as written only returns the "last of each type" really bothered me too. This seems like a big improvement. –  Floris 27 mins ago
add comment

It seems to me a switch statement would work here.

for (i=0;arr[i];i++) {
  switch(typeof arr[i]{
    case 'string':
      text = arr[i];
    break;
   case 'object':
     if (Array.isArray(arr[i])) {
      child = arr[i];
     } else {
       attr = arr[i];
     }
    break;
  }
}

This construct of for initializes i once, checks if there is an element arr[i] in the while section and sets the incrementation.

Switch checks the type of arr[i] only once. This seems to me to be succinct and will probably prove to be faster.

share|improve this answer
 
You have some syntax errors –  megawac 8 hours ago
add comment

By using the prototype name string instead of the typeof function we can get a more accurate check of the variable type. For instance:

variable["__proto__"]["constructor"]["name"]

var Array = []; //returns "Array"
var Object = {}; //returns "Object"
var Array = new Array(); // returns "Array"

With this we no longer have to have an additional logical statement to check if our our objects are arrays in disguise or actually an object.

Using a reverse while loop means that array.length doesn't have to be evaluated each increment and reduces character wastage. It does make the logic more confusing to write in controlled sequential cases, but in this case is fine

var i=arr.length;
while(i--){switch(arr[i].__proto__.constructor.name;){
        case'Object':{attr = arr[i]; break;}
        case'String':{text = arr[i]; break;}
        case'Array':{child = arr[i]; break;}
        default:{break;}}}

or a more readable version:

var myArray = [150,"Entry",{"x":15,"y":26}]; //input array
var index = myArray.length; //evaluate the length of the array

while(index--) //decrement the index while it's greater than 0
{
   var type = myArray[index]["__proto__"]["constructor"]["name"];//what type is this variable
   switch(type)
   {
      case:'Object': //if object
      {
         attr = myArray[index]; //set attribute to field
         break;
      }
      case:'String': //if string
      {
         text = myArray[index]; //set text to field
         break;
      }
      case:'Array': //if array
      {
         child = myArray[index]; //set child to field
         break;
      }
      default:
      {
         console.log("Error, entry does not match requested types");
         break;
      }
   }
}

Using a quasi-referance object array to start with would be a lot more obvious way to do this though, it seems a rather backwards way to extract relevant information from an unsorted array than to just pick items out of your choosing.

var myArray = 
{
   "values":[150,160,170],
   "name":"entry",
   "coord":{"x":15,"y":26}
}

//then

attr = myArray["coord"]; 
text = myArray["name"]; 
child = myArray["values"];

//or

attr = myArray.coord; 
text = myArray.name; 
child = myArray.values; 
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.