0

I'm relatively new to Javascript and all of its infinite greatness. I wanted to practice defining my own functions for object prototypes, so I practiced writing a contains() function for Array:

if (!Array.prototype.contains){
    Array.prototype.contains = function(target){
        for (var i in this){
            console.log(i);
            if (this[i] == target) return true;
        }
        return false;
    }
}

Essentially, contains() iterates through each element inside this to look for the target. However, I'm noticing that console.log(i) returns the index number (ie. 0, 1, 2, etc.) as expected depending on the number of elements inside the array. However, it also always prints out contains! Here's the output from my console.log with only one element in the array:

0
contains

However, if I change the way I iterate the for loop, I don't get the contains output:

if (!Array.prototype.contains){
    Array.prototype.contains = function(target){
        for (var i = 0; i < this.length; i++){
            console.log(i);
            if (this[i] == target) return true;
        }
        return false;
    }
}

Output:

0

Now I've read through this StackOverflow post explaining the dangers of using for... in with array iteration, but I'm at a loss to explain programmatically why contains is appear in my first code snippet. If I console.log(this) on my Firefox developer console, I receive as expected, an Array object:

Array [ "scrubBackButton2", "scrubNextButton1" ]

Is the last element of an array object always the name of the function being called with?

I'm learning JS mostly through trial and error, so please definitely do refer me to a good piece of documentation or prior SO post if I've somehow missed a good answer to this!

Yu Chen
  • 6,540
  • 6
  • 51
  • 86
  • 1
    You'll want to have a look at [How to define method in javascript on Array.prototype and Object.prototype so that it doesn't appear in for in loop](https://stackoverflow.com/q/13296340/1048572) – Bergi Oct 04 '17 at 01:24

1 Answers1

2

A for ... in loop will go through every one of the object's enumerable properties. So it will go through the ['1'] property, and the ['2'] property, etc. Among the properties that the array has is a function named ['contains']. Why does it have that? Because you put it there! :)

Or more accurately, you put it on its prototype. But that's pretty much the same as far as for ... in is concerned. It will loop through all the enumerable properties on the object, and also the enumerable properties it inherits from its prototype, and from its prototype's prototype, etc. There are plenty of properties on Array.prototype, but they're not enumerable (which is why it's not also logging out 'push', 'pop', 'map', etc). The one you put there is enumerable, because that's the default nature of properties.

As you pointed out, for ... in is not appropriate for traversing an array, so i would recommend changing the code to do something else. Could be a manual for loop, or this.forEach, or a for ... of for example.

Below this line are some other options, but they get a bit into the weeds with code that you'll rarely ever write (but then again, modifying Array.prototype is something you'll rarely ever do).

========

If you wanted to keep the for ... in loop, one option would be to explicitly check to make sure that each property you're looking at is from the object itself, not from its prototype. To do that, you can use hasOwnProperty. If the property came from the prototype, hasOwnProperty will return false. If the property is of this specific object, it returns true;

if (!Array.prototype.contains){
    Array.prototype.contains = function(target){
        for (var i in this){
            if (!this.hasOwnProperty(i)) continue;
            console.log(i);
            if (this[i] == target) return true;
        }
        return false;
    }
}

[1, 2].contains(5)

Another possibility would be to make it so that contains isn't enumerable. By doing that, the for ... in loop would skip over it (and so would Object.keys())

if (!Array.prototype.contains){
    function contains(target){
        for (var i in this){
            console.log(i);
            if (this[i] == target) return true;
        }
        return false;
    }

    Object.defineProperty(Array.prototype, 'contains', {
        enumerable: false,
        configurable: false,
        writeable: false,
        value: contains
    });
}

[1, 2].contains(5);

For more information on defining properties, see this page: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
  • Great explanation. One questions- why is modifying the prototype of an object not encouraged? It seems like one of the most powerful ways to extend functionality- and I’ve seen several SO posts that use prototype modification as the solution, for example, if older versions of IE don’t support indexOf() to write your own indexOf()... – Yu Chen Oct 04 '17 at 01:06
  • If you're creating your own new types of objects, by all means give them a prototype. That's how JS does inheritence. But create the prototype once, and then don't change it afterwards (changing on the fly leads to bugs/performance problems). For the built in types, it's mostly because you're risking creating bugs. People have assumptions about what arrays do, and modifying that throws people off. Backwards compatability is also a problem if the language gets updated. The one exception is the example you gave: Polyfills. This lets you write newish code and run in old browsers. – Nicholas Tower Oct 04 '17 at 01:10
  • That makes sense. Got it. – Yu Chen Oct 04 '17 at 01:11
  • 1
    (ran out of space) As for why polyfills are ok, it's because it avoids both those problems i mentioned. There's no chance the language will get unexpectedly updated to add an indexOf function, because since we're living in the future you already have the definition in hand and you're just conforming to that. And the chance of bugs is quite low, because people who know the modern language are already quite familiar with the feature, and you're just bringing it up to their expectations. – Nicholas Tower Oct 04 '17 at 01:14
  • and FYI to anyone else who encounters this as they learn: I did some research on polyfills and there’s an exact example of my question / the accepted answer here: https://www.codeproject.com/Articles/369858/Writing-polyfills-in-Javascript – Yu Chen Oct 04 '17 at 01:19
  • @NicholasTower There are no performance problems with amending prototype objects – Bergi Oct 04 '17 at 01:25
  • @Bergi Yeah sorry, i was thinking of replacing the prototype (ie, setPrototypeOf) – Nicholas Tower Oct 04 '17 at 01:27