50

So lets say I've added some prototype methods to the Array class:



Array.prototype.containsKey = function(obj) {
    for(var key in this)
        if (key == obj) return true;
    return false;
}

Array.prototype.containsValue = function(obj) {
    for(var key in this)
        if (this[key] == obj) return true;
    return false;
}

then I create an associative array and attempt to loop through it's keys:



var arr = new Array();
arr['One'] = 1;
arr['Two'] = 2;
arr['Three'] = 3;

for(var key in arr)
   alert(key);

this returns five items:

  -One
  -Two
  -Three
  -containsKey
  -containsValue

but I want (expect?) only three. Am I approaching this wrong? is there a way to "hide" the prototype methods? or should I be doing something differently?

Nicholas Franceschina
  • 6,009
  • 6
  • 36
  • 51

7 Answers7

63

You can achieve desired outcome from the other end by making the prototype methods not enumerable:

Object.defineProperty(Array.prototype, "containsKey", {
  enumerable: false,
  value: function(obj) {
      for(var key in this)
        if (key == obj) return true;
      return false;
    }
});

This usually works better if you have control over method definitions, and in particular if you have no control over how your code will be called by other people, which is a common assumption in library code development.

abbr
  • 5,361
  • 6
  • 30
  • 44
  • 4
    This is particularly useful when you have no control over the code that is looping. – Aurimas May 19 '14 at 04:04
  • unfortunately defineProperty only works for DOM elements for IE8 http://kangax.github.io/compat-table/es5/#define-property-ie-note – Camilo Sanchez May 28 '14 at 18:38
  • 4
    This is so much more elegant then checking hasOwnProperty in every loop. For older browsers you can roll your own implementation, like this one for example: https://github.com/inexorabletash/polyfill/blob/master/es5.js#L71 – ZolaKt Jan 07 '15 at 13:28
  • yes this is the way.. but more often than not, browsers lacking basic Array methods also lack Object.defineProperty, so it's a race condition ;) – rupps Apr 26 '15 at 10:16
  • This is useful if one wants to apply jQuery's $.param method on custom object with methods. Naive implemented `add` function https://github.com/jquery/jquery/blob/1.12-stable/src/serialize.js#L58 just runs ones methods blindly, and it causes `Uncaught TypeError: Cannot read property of undefined`, This solution is also elegant! – Marecky Jan 12 '18 at 21:40
57

You can use JavaScript's hasOwnProperty method to achieve this in the loop, like this:

for(var key in arr) {
    if (arr.hasOwnProperty(key)) {
        ...
    }
}

Reference: This YUI blog article.

Kirtan
  • 21,295
  • 6
  • 46
  • 61
  • 15
    Ironicly I ran into this problem by trying to create a shorter version of hasOwnProperty `Object.prototype.has = Object.prototype.hasOwnProperty` – Moss Nov 09 '11 at 09:06
  • 1
    Apparently calling hasOwnProperty directly on the object is considered unsafe ([reference](https://eslint.org/docs/rules/no-prototype-builtins)). The safe way would be `Object.prototype.hasOwnProperty.call(foo, "bar")` – Thibaut Jan 12 '21 at 12:20
4

Javascript doesn't support associative arrays the way you think they do. http://ajaxian.com/archives/javascript-associative-arrays-considered-harmful

for (var i in .. gets all of the properties of an object (an array is just another object) which is why you're seeing the other objects you've prototyped to it.

As the article suggests you should use an object:


var assoc = {'One' : 1, 'Two' : 2};
assoc['Three'] = 3;

for(var key in assoc)
   alert(key+' => '+assoc[key]);
rezzif
  • 460
  • 2
  • 10
3

you could do this:

for(var key in arr)
{
   if (typeof(arr[key]) == "function")
      continue;
   alert(key);
}

But that's a shoddy workaround

1

Method 1: use Object.keys (which doesn't return prototype properties) & loop

Object.keys(arr); // ['One', 'Two', 'Three']
Object.keys(arr).forEach(key => console.log(key))

Method 2: hasOwnProperty inside a for-loop.

 for(var key in arr) {
   if (arr.hasOwnProperty(key)) {
     ...
   }
 }
Clifford Fajardo
  • 1,357
  • 1
  • 17
  • 28
1

You can hide methods that added to prototype from for-in loops like this:

Object.defineProperty(Array.prototype, "containsKey", { enumerable: false });

Just after you add method. where the "containsKey" is your added method.

0

For high-performance iteration over JavaScript arrays, use either a for or while loop. Nicholas Zakas discusses the most-performant options for iterating over arrays in his Tech Talk Speed Up Your JavaScript.

Your best bet is probably something like this:

for (var i = collection.length - 1; i >= 0; i--) {
  if (obj == collection[i]) return true;
}

This approach will be peform best for a few reasons:

  • Only a single local variable is allocated
  • The collection's length property is only accessed once, at the initialization of the loop
  • Each iteration, a local is compared to a constant (i >= 0) instead of to another variable
Duncan Beevers
  • 1,830
  • 11
  • 17
  • 1
    You can't iterate like that given the way he has used the array as he hasn't used numbers as the keys so collection[1] won't exist when he has called it collection['one'] – rezzif Jul 10 '09 at 05:27
  • This goes backwards, which v8 can not optimize. You should always iterate the right way, forwards. –  Sep 08 '18 at 14:07