17

This would be a very easy question for all you javascript/jquery guys.

I've been programming javascript from past 2 years and today I faced a strange problem.

I am fetching a JSONarray from my C# Webmethod and calling it by jQuery.ajax()

When I do

for (i in JSONRaw) {
    Index = Index + 1;
    $('#bottomGridDashboard').append('<tr> <td>' + Index + '</td> <td>' + JSONRaw[i].DisplayName + '</td> <td>' + JSONRaw[i].SpeedInKPH + '</td> <td><img src="' + JSONRaw[i].ImageURL + '" height="20px" alt="" /></td> <td>' + JSONRaw[i].DepotID + '</td> <td>' + JSONRaw[i].RouteID + '/' + JSONRaw[i].ScheduleID + '/' + JSONRaw[i].TripID + '</td> <td>' + JSONRaw[i].Direction + '</td> <td>' + JSONRaw[i].LastStop + '</td> <td> ' + JSONRaw[i].ETAMinutes + ' </td> </tr>');
}

the appended table adds two extra rows putting every field as 'undefined'. See this Image

However if i replace the loop with

for (var i = 0; i < JSONRaw.length;i++ ) {
    Index = Index + 1;
    $('#bottomGridDashboard').append('<tr> <td>' + Index + '</td> <td>' + JSONRaw[i].DisplayName + '</td> <td>' + JSONRaw[i].SpeedInKPH + '</td> <td><img src="' + JSONRaw[i].ImageURL + '" height="20px" alt="" /></td> <td>' + JSONRaw[i].DepotID + '</td> <td>' + JSONRaw[i].RouteID + '/' + JSONRaw[i].ScheduleID + '/' + JSONRaw[i].TripID + '</td> <td>' + JSONRaw[i].Direction + '</td> <td>' + JSONRaw[i].LastStop + '</td> <td> ' + JSONRaw[i].ETAMinutes + ' </td> </tr>');
}

the undefined rows disappear.. See the image

I apologize for my stupidity but I have never faced any such problem before. What could be the reason??

EDIT:

The array is perfectly fine. And there are no holes in it. Another observation is that the undefined properties appear only at the bottom of my grid. I think it is processing two extra indexes more than the array length.

EDIT-2

My console.log showed me the following elements in my array.

https://i.stack.imgur.com/jw8Sy.jpg

I've declared the prototypes in my Master page.

Array.prototype.inArray = function (comparer) {
    for (var i = 0; i < this.length; i++) {
        if (comparer(this[i])) return true;
    }
    return false;
};


Array.prototype.pushIfNotExist = function (element, comparer) {
    if (!this.inArray(comparer)) {
        this.unshift(element);
    }
};

Is it increasing the array length. ? ?

writeToBhuwan
  • 3,233
  • 11
  • 39
  • 67

3 Answers3

7

JavaScript's for-in and C#'s foreach are quite different things. Don't use for-in to loop through arrays unless you use the appropriate safeguards, that's not what it's for. It loops through enumerable properties of objects, not array indexes or entries.

Either use the necessary safeguards, or use a generic for like your second example, or (if you can rely on having it) use Array#forEach.

More in this SO answer and on my blog.


Separately: Be sure you're declaring i somewhere (you don't seem to be in your first example). If you don't, you fall prey to The Horror of Implicit Globals.


I've declared the prototypes in my Master page.

Array.prototype.inArray = function ...

This is why you don't use for-in for looping through arrays without safeguards. Again, see the links above. Your for-in loop will loop through the array index properties ("0", "1", and so on -- yes, they're really strings) and also the names of the functions you've added to the Array.prototype (inArray and such).

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • No such global variable. I can post my whole javascript if you want me to – writeToBhuwan Nov 23 '13 at 09:28
  • I've been using i(without declaration) for a long time and it has always worked fine. (Not trying to offend, just expecting some clarity) – writeToBhuwan Nov 23 '13 at 09:28
  • @writeToBhuwan: Unless you are declaring `i` somewhere, using it the way you do *implicitly* creates a global. See the link for details. This means if you do it in two functions, and one calls the other, they'll interfere with each other. – T.J. Crowder Nov 23 '13 at 09:29
  • Should I declare "i" as a global varible and setting the value to 0? – writeToBhuwan Nov 23 '13 at 09:29
  • @writeToBhuwan: No. You should declare it within the function in which you're using it. – T.J. Crowder Nov 23 '13 at 09:29
  • See the edit.. My console.log of array elements – writeToBhuwan Nov 23 '13 at 09:38
  • @writeToBhuwan: It's your `Array.prototype` extensions. That's *why* you don't use `for-in` to loop through arrays without safeguards, see edit at end above. – T.J. Crowder Nov 23 '13 at 09:43
  • +1 Though I've marked another answer as correct(because he told me the right way to debug). But your answer has fully satisfied me. Thank you for being such a good helper. – writeToBhuwan Nov 23 '13 at 09:45
5

There are a few ways those two loops are different.

(1) The first should be for (var i in JSONRaw). Setting the property i on window may have non-obvious consequences.

(2) JSONRaw is not an array, but an object which happens to have a ill-defined length property.

(3) There have been other properties set on JSONRaw that appear in the the first loop.

(4) The array could have "holes". For example, the array below has a hole at index 1.

var a = [];
a[0] = 0;
a[2] = 2;

The first way would omit the 1. The second way would include the index 1 (with a[1] being undefined).

A similar situation can happen as follows:

var a = [0, 1];
a.length = 3;

Personally, I suspect (3).

Doing a console.log in the first loop may reveal the problem.

EDIT: From your debugging output, it seems (3) was the culprit. inArray and pushIfNotExist are keys on all arrays, so the first for loop iterated over them.

If you are going to to use the first loop, you have three options:

(1) Don't add those functions on Array.prototype. Adding to built-in prototypes is generally discouraged.

(2) Use Object.defineProperty (won't work in IE8):

Object.defineProperty(Array.prototype, 'inArray', {
    enumerable: false,   //this makes it not show up in the for loop
    value: function (comparer) {
        for (var i = 0; i < this.length; i++) {
            if (comparer(this[i])) return true;
        }
        return false;
    }
});

(3) Check to see if the key was defined on the prototype.

for(var i in JSONRaw) {
    if(JSONRaw.hasOwnProperty(i)) {
        //do something
    }
}

Though most people just use the second loop because of the potential pitfalls (and faster performance).

Paul Draper
  • 78,542
  • 46
  • 206
  • 285
2

In a few words, for ... in cycle iterates through keys of object, while c-like for cycle iterates through array indexes. JS is not so strongly typed, so it allowed you to get both ways, BUT proper ways for array is only second or 'forEach' method. You can read more on MDN.

Tommi
  • 3,199
  • 1
  • 24
  • 38
  • *"BUT proper way for array is only second"* Not at all. `Array#forEach` is another proper way; and there are appropriate uses for `for-in` with arrays (particularly sparse arrays), you just have to use correct safeguards. – T.J. Crowder Nov 23 '13 at 09:35
  • Agreed, forEach is totally proper, but I don't use it, because some times I nailed to IE8 support, and I need to write polyfill in this case. In case you don't need to support such archaic soft you can use it safely. It still needs [].prototype trick for usage with live collections, but it's good that you mention it. I'll add it to my answer. – Tommi Nov 24 '13 at 08:00