6

Question:

As soon as I add the below code to my html page, I get:

Line: 4
Error: Object doesn't support the property or method "exec".

This is the prototype that causes the bug:

    Object.prototype.allKeys = function () {
        var keys = [];
        for (var key in this) 
        {
            // Very important to check for dictionary.hasOwnProperty(key) 
            // otherwise you may end up with methods from the prototype chain.. 
            if (this.hasOwnProperty(key)) 
            {
                keys.push(key);
                //alert(key);
            } // End if (dict.hasOwnProperty(key)) 

        } // Next key

        keys.sort();
        return keys;
    }; // End Extension Function allKeys

And this is the minimum code required to reproduce the error (Browser in question: IE9):

<!DOCTYPE html>
<html>
<head>
    <title>TestPage</title>

    <script type="text/javascript" src="jquery-1.9.1.min.js"></script>


    <script type="text/javascript">
        /*
        Object.prototype.getName111 = function () {
            var funcNameRegex = /function (.{1,})\(/;
            var results = (funcNameRegex).exec((this).constructor.toString());
            return (results && results.length > 1) ? results[1] : "";
        }; // End Function getName
        */

        Object.prototype.allKeys = function () {
            var keys = [];
            for (var key in this) 
            {
                // Very important to check for dictionary.hasOwnProperty(key) 
                // otherwise you may end up with methods from the prototype chain.. 
                if (this.hasOwnProperty(key)) 
                {
                    keys.push(key);
                    //alert(key);
                } // End if (dict.hasOwnProperty(key)) 

            } // Next key

            keys.sort();
            return keys;
        }; // End Extension Function allKeys

    </script>

</head>
<body>

    <select id="selLayers" name="myddl">
      <option value="1">One</option>
      <option value="2">Twooo</option>
      <option value="3">Three</option>
      <option value="4">Text1</option>
      <option value="5">Text2</option>
    </select>


    <script type="text/javascript">

        //var dict = { "de": { "Text1": "Ersetzung 1", "Text2": "Ersetzung 2" }, "fr": { "Text1": "Replacement 1", "Text2": "Réplacement 2" }, "it": { "Text1": "Replacemente 1", "Text2": "Replacemente 2" }, "en": { "Text1": "Replacement 1", "Text2": "Replacement 2"} };
        /*
        var languages = dict.allKeys();


        for (var j = 0; j < languages.length; ++j) 
        {
            var strCurrentLanguage = languages[j];
            var dictReplacements = dict[strCurrentLanguage]
            var keys = dictReplacements.allKeys();

            //alert(JSON.stringify(dictReplacements));
            //alert(JSON.stringify(keys));


            for (var i = 0; i < keys.length; ++i) {
                var strKey = keys[i];
                var strReplacement = dictReplacements[strKey];

                alert(strKey + " ==> " + strReplacement);
                //alert('#selLayers option:contains("' + strKey + '")');
                //$('#selLayers option:contains("' + strKey + '")').html(strReplacement);
                //$('#selLayers option:contains("Text1")').html("foobar");


            }    
        }
        */


        $('#selLayers option:contains("Twooo")').text('Fish');


        //alert(dict.allKeys());        
        //alert(dict["de"]["abc"]);




        /*

        $('#selLayers option[value=2]').text('Fish');
        $('#selLayers option:contains("Twooo")').text('Fish');
        $('#selLayers option:contains("Twooo")').html('&Eacute;tage');
        // http://stackoverflow.com/questions/7344220/jquery-selector-contains-to-equals

        $("#list option[value=2]").text();

        $("#list option:selected").each(function () {
            alert($(this).text());
        });  

        $("#list").change(function() {
            alert($(this).find("option:selected").text()+' clicked!');
        });

        */

    </script>

</body>
</html>

I tried renaming the prototype function, just in case it conflicts with any jquery prototype, but that doesn't help at all.

2
  • Possible dupe stackoverflow.com/questions/1827458/… Commented Feb 18, 2013 at 17:20
  • Your question has been sufficiently answered. Feel free to accept one of the answers :) Commented Feb 18, 2013 at 17:59

3 Answers 3

6

Because this is going to add an enumerable item to every single object. Sizzle (which jQuery uses) uses object literals to configure their selector parsing. When it loops these config objects to get all tokens, it doesn't expect your function. In this case, it's probably trying to use your function as a RegExp.

Imagine this scenario:

var obj = { a: 1, b: 2, c: 3 };
var result = 0;
for (var prop in obj) {
  // On one of these iterations, `prop` will be "allKeys".
  // In this case, `obj[prop]` will be a function instead of a number.
  result += obj[prop] * 2;
}
console.log(result);

If you have added anything to Object's prototype that can't be used as a number, you will get NaN for your result.

A good solution to this problem is to add the allKeys function to Object instead of Object.prototype. This mimics Object.keys:

Object.allKeys = function (obj) {
    var keys = [];
    for (var key in obj) 
    {
        // Very important to check for dictionary.hasOwnProperty(key) 
        // otherwise you may end up with methods from the prototype chain.. 
        if (obj.hasOwnProperty(key)) 
        {
            keys.push(key);
            //alert(key);
        } // End if (dict.hasOwnProperty(key)) 

    } // Next key

    keys.sort();
    return keys;
}; // End Extension Function allKeys
Sign up to request clarification or add additional context in comments.

2 Comments

I'm going to to with the system on this one. It is a bad idea to add properties to any native prototype. jQuery isn't breaking prototype functions as much as javascript's implementation of for...in loops is goofy.
Yep, for in loops are goofy, I realized that when I wrote this code. That's incidentially why I came up with the allKeys method. Well yea, if others simply don't check for hasOwnProperty, then it truly isn't a good idea. I'm giving you an upvote and accept the answer :)
3

You may be able to overcome this side-effect by using defineProperty which allows for setting descriptors.

Object.defineProperty(Object.prototype, 'propName', {value: 'your value', enumerable: false});

2 Comments

True. Introduced in JavaScript 1.8.5.
+1 - Nice find. In my case however, if I was using ONLY newer browsers, I could use the object.keys function anyway. But I ain't ONLY using newer browsers [not that I wouldn't like to] :)
3

Because jQuery doesn't bog down its code with checks for .hasOwnProperty() when enumerating objects.

To do so, is to place guards against bad coding practices. Rather than weigh down their code to accommodate such practices, they require that their users adhere to good practices, like never putting enumerable properties on Object.prototype.

In other words... Don't add enumerable properties to Object.prototype unless you want all your code to run guards against those properties, and you never want to enumerate inherited properties.


FWIW, if you really want to call methods on plain objects, just make a constructor so that you have an intermediate prototype object that can be safely extended.

function O(o) {
    if (!(this instanceof O))
        return new O(o)
    for (var p in o)
        this[p] = o[p]
}

O.prototype.allKeys = function() {
    // ...
};

Now you create your objects like this:

var foo = O({
    foo: "foo",
    bar: "bar", 
    baz: "baz"
});

...and the Object.prototype remains untouched, so plain objects are still safe. You'll just need to use the .hasOwnProperty() guard when enumerating your O objects.

for (var p in foo) {
    if (foo.hasOwnProperty(p)) {
        // do stuff
    }
}

And with respect to JSON data being parsed, you can use a reviver function to swap out the plain objects with your O object.

var parsed = JSON.parse(jsondata, function(k, v) {
    if (v && typeof v === "object" && !(v instanceof Array)) {
        return O(v)
    }
    return v
});

4 Comments

Fine, but the type of my enumerable associative array produced via JSON seems to be object.
@Quandary: I'm not sure what you mean. When you cause all objects in the environment to inherit a property (which is what happens when you extend Object.prototype), then all iterations of inherited properties will bump into it.
Yes, that's the sense of prototype. But what you are essentially saying is, that I have to write a static method, pass object as variable to it, and therby adopt bad practices, just because jQuery uses bad coding practices, and not the other way round.
@Quandary: How on earth is passing an object to a function a bad practice? And how would you consider it a good thing to expect all code to be required to test every property of every enumeration of every object to see if the property is directly on the object? And how is it a good thing to adopt practices that effectively eliminate the possibility of inherited enumeration, which can be very useful? All of these things are remedied by adopting good practices, like not causing all objects in an environment to inherit an enumerable property.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.