38

I have added a simple .js file to my page that has some pretty mundane common-task sort of functions added to the Object and Array prototypes.

Through trial and error, I've figured out that adding any function to Object.prototype, no matter it's name or what it does causes Javascript errors in jQuery:

The culprit?

Object.prototype.foo = function() {
    /*do nothing and break jQuery*/
};

The error I'm getting line 1056 of jquery-1.3.2.js, in the attr:function { } declaration:

/*Object doesn't support this property or method*/
name = name.replace(/-([a-z])/ig, function(all, letter) {
            return letter.toUpperCase();
        });

Apparently G.replace is undefined.

While it's obvious that there's something I'm just not wrapping my head around with prototyping, I'm failing miserably to figure out what it is.

To be clear, I'm not looking for a workaround, I have that handled... what I'm looking for is an answer to Why?. Why does adding a function to Object.prototype break this bit of code?

Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 3
    You might get a more meaningful error if you switch over to the full jquery file (non-minified). That way you'll be able to see more clearly what code is having problems. – Frank DeRosa Dec 01 '09 at 16:53
  • @CrescentFresh's link is outdated. Update: https://bugs.jquery.com/ticket/2721 – WoodrowShigeru Aug 19 '17 at 10:41

5 Answers5

47

If it's simply a case of messing up for...in loops, can't you use Object.defineProperty to add your fn without making it enumerable?

So:

Object.defineProperty(Object.prototype, "foo", { 
    value: function() {
        // do stuff
    },
    enumerable : false
});

Seems to work for me. Would this still be considered bad form?

nicholas
  • 14,184
  • 22
  • 82
  • 138
  • 1
    I don't know... It would be nice to know if it **is** bad form, but this is working great for me. – Paul Carlton Aug 05 '11 at 23:59
  • 2
    You could, but for..in loops were *designed* to enumerate prototype extensions. That's how you explore the prototype chain. It doesn't break for..in loops it breaks buggy code that blindy assumes the iterated values will always be of a certain type whereas, because the Object.prototype chain can include functions, the code between the curly brackets can throw an exception when they expect only scalars and objects. – Marcus Pope Feb 02 '12 at 19:22
  • It works without defining enumerable: `Object.defineProperty(Object.prototype, "foo", { value: function() { } });` – Junior Mayhé Sep 23 '12 at 14:34
  • @MarcusPope can you elaborate or provide a source for your statement that "for...in" loops were designed to explore the prototype chain? I'm just interested because I've never personally used for...in other than to enumerate through ownproperties. Your comment would lead me to believe that defining non-enumerable properties is in reality just a hack to fix bad coding. – Nick Manning Mar 24 '15 at 22:37
  • @NickManning the ecmascript spec has specified this reflection behavior along with hasOwnProperty since v1.5 circa 1995, despite some browser caveats thru the 90's. Reflection isn't a common use pattern and the internet kind of banned object.prototype extensions because some people weren't familiar enough with JavaScript and got confused when they tried to use for..in as you would in other languages, but JS: The Definitive Guide covers it. And non-enumerable wasn't made to fix bad enumeration, though some use it for that purpose, it's just there to handle edge cases in reflection patterns. – Marcus Pope Mar 31 '15 at 20:58
  • Wondering around the web, it seems to me that JavaScript has confused even its most advanced programmers on how it should be used and why it is the way it is. And speaking about hacks and bad coding, the fact that extending the prototype of Object is any different that extending ANY other prototype (as suggested here: http://stackoverflow.com/a/1827611/964053), is a hack and a bad coding/design in itself (whose bad design, I don't know). – NoOne Jun 17 '15 at 21:24
  • 1
    @NoOne I don't think extending the Object prototype is any different than any other prototype. It just has to be done responsibly. The open-source nature of the web, with all the frameworks and plug-ins and tools available, means your project is likely more third-party code than your own. A core tenant of JavaScript is to not modify anything you don't own. If you just extend Object without thinking, you might break something that expects it to behave a certain way. – nicholas Jun 18 '15 at 19:36
  • @nicholas The thing is that, no matter how responsively you try to extend Object, you are breaking JQuery (and who knows what else). And its author refuses to change that. Not extending Object and other classes like String is NOT an option in my view because JS lacks even the most basic functionalities a developer needs every day. If I cannot safely extend Object and String which are used so often, I do not see what good there is in this language. I mean even in C# that is not dynamic, you can take a foreign class and write extension methods for it! – NoOne Jun 19 '15 at 19:48
  • @nicholas What should one do? Write his own classes for everything? Not use OOP? Your solution is very good and thank you for it (I am currently using it), but it has its own issues. E.g. I do not like the fact that one cannot enumerate the added method, as it is possible with pre-existing methods. I think that if one builds a third party component, **HE** should go into trouble writing his own classes from scratch, so that he won't mess the main project's code base. If he can't do that, perhaps he should not start writing the component in the 1st place. – NoOne Jun 19 '15 at 19:49
  • 1
    @NoOne You application is probably going to look something like: Core Javascript (owned by the browser) -> toolkits (like jquery, underscore, etc.) -> framework (backbone, angular, react, etc) -> your application code. Each layer relies on the layers before it behaving a certain way. Reaching backwards and changing the way core data types work is always going to be a problem, in any language. – nicholas Jun 19 '15 at 20:26
  • 1
    @NoOne There are plenty of ways you can get the functionality you want in just your application code. You can use libraries like underscore.string, use Object.create(), mixins or factory methods to build functionality into your objects. A good book that covers a lot of techniques for this is JavaScript Patterns (http://www.amazon.com/JavaScript-Patterns-Stoyan-Stefanov/dp/0596806752). – nicholas Jun 19 '15 at 20:26
20

You should never extend Object.prototype. It does far more than break jQuery; it completely breaks the "object-as-hashtables" feature of Javascript. Don't do it.

You can ask John Resig, and he'll tell you the same thing.

Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
Josh Stodola
  • 81,538
  • 47
  • 180
  • 227
  • 20
    Extending `Object.prototype` is fine. The caveat is to use `hasOwnProperty` in `for..in` loops. It's supported in every major browser including Safari since 2.0. It's just laziness that jQuery does not do it in its `for..in` loops. The performance impact is negligable, and Resig knows this: http://osdir.com/ml/jquery-dev/2009-02/msg00543.html Just my opinion however. – Crescent Fresh Dec 01 '09 at 17:28
  • Interesting stuff. Thanks. I wonder why it breaks the "objects-as-hashtables" thing.... hmmm. – Ben Lesh Dec 01 '09 at 17:43
  • 3
    @Crescent It's much deeper than that. Sure you can work around the problem with `for...in` loops like that, but having object-as-hashtables in Javascript does lots of other things. For example, `toString`, `valueOf` and others are not enumerated. This *does* have an impact. Also, when you are the lead dev of a library used by tons of people, you can't blame his decision on laziness. I think a better word would be cautious. – Josh Stodola Dec 01 '09 at 18:04
  • @Josh: perhaps lazy is a bit harsh, you're right. I don't get your point about `toString` and `valueOf`. Yes they are marked `DontEnum`, so they don't show up in `for..in`. How does that invalidate the effectiveness of `hadOwnProperty`? – Crescent Fresh Dec 01 '09 at 21:17
  • 3
    @JoshStodola - actually it wasn't laziness it was ignorance (most people didn't know to use hasOwnProperty...) in the early days of jQuery Resig simply didn't know to do so. Soon after realizing the flaw he had always accepted the fix as a needed future implement but alas it never made it into the core. – Marcus Pope Feb 02 '12 at 19:15
  • 1
    @CrescentFresh THANK YOU SOOOO MUCH!!! I've been looking for that post for months now! (I'm the author of that post/code diff and the one who worked with Resig to get the bug fixed in jQuery.) I thought it was lost to the ether forever. As an update, I'm sad to report that a few months ago Resig officially closed the ticket as won't fix. The flaw was too pervasive in UI libs and the bad coding practice is too prevalent in the community. It's why I don't use jQuery anymore :( but Sizzle is compatible with Object.prototype extensions and that's my favorite part of jQuery really. – Marcus Pope Feb 02 '12 at 19:20
  • Any links to info pages on this "object-as-hashtables" feature that you mention? Is it this: http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/ ? And won't the case of NOT extending `Object.prototype` completely eliminate a more mainstream feature of JS, the prototype extensibility? I mean, come on! The Object and String classes beg for people to extend them. Their built in methods seem totally useless to me. If one wants to use objects as hash-tables he might just prepend a prefix to his keys in order to be sure of their validity. And of course there is the `hasOwnProperty` method. – NoOne Jun 19 '15 at 20:46
  • 4
    IMHO this answer is now incorrect, and out-of-date. In ES5 you _can_ extend `Object.prototype` safely using `Object.defineProperty` with the default settings that create a _non-enumerable_ property. – Alnitak Jan 09 '17 at 00:43
4

I agree, adding something to Object.prototype demands caution and should be avoided. Look for other solutions such as:

Adding it to Object and then accessing it with a call or apply, as needed. For example:

Object.foo = function () { return this.whatever()}

Then call that on an object by:

Object.foo.call(Objname);  // this invokes the function as though it were a
                           // method of Objname.  That is, its like Objname.foo()

For fun, you can add the following (yes, I know its a bit dangerous...):

Function.using = Function.call; // syntactic sugar

Now you can write Object.foo.using(Objname) and it reads like a sentance.

But as a rule, stay away from altering any of the big prototypes.

sth
  • 222,467
  • 53
  • 283
  • 367
davehamptonusa
  • 109
  • 1
  • 5
  • Downvoting, as this does not explain why extending the object prototype is problematic (which is the question) plus the suggested solution is a somewhat hacky (you can't call `arbitraryObject.foo()`, for example, which is the main thing that the OP is trying to achieve here). – HappyDog Apr 01 '22 at 21:11
2

I banged my head around this problem as I wished to implement "real" object orientation in all of my objects, like this:

interface Object
{
    GetType: () => string;
    ToString: () => string;
    GetHashcode: () => number;
    Equals: (obj: any) => boolean;
}

Since Object.prototype breaks JQuery, I defaulted to the abovementioned solution to make use of defineProperty but that does not take any arguments.

The good news is that you can hack into defineProperty and actually accept parameters. Here is my implementation:

Object.defineProperty(Object.prototype, "Equals",
    {
        value: function (obj: any)
        {
            return obj == null && this == null
                    ? true
                    : obj != null && this == null
                        ? false
                        : obj == null && this != null
                            ? false
                            : this.GetHashcode() == obj.GetHashcode();
        },
        enumerable: false
    });

This works and does not clash with JQuery.

Miguel
  • 21
  • 1
  • This seems to be the best answer. Does not seem to clash with Google Maps API either. – vaid Jan 09 '19 at 11:19
-1

I doubt adding a function to Object.prototype breaks jQuery directly. Just make sure each for..in loop you have throughout your site is wrapped in a hasOwnProperty check, since you've add the function globally and the result of iterating over it can be unpredictable:

Object.prototype.foo = function() {};    
var myObject = {m1: "one", m2: "two" };

for(var i in myObject) { if(myObject.hasOwnProperty(i)) {
   // Do stuff... but not to Object.prototype.foo
}}
jshalvi
  • 72
  • 3
  • Well if I comment out the Object.prototype.foo declaration, everything works just fine. Also, at the point it's breaking, it hasn't even reached any of my code beyond that foo declaration. – Ben Lesh Dec 01 '09 at 17:04
  • 1
    You're right it does not break jQuery directly, it breaks Javascript! – Josh Stodola Dec 01 '09 at 17:16
  • Depends on how you look at it. Ideally you should be able to extend Object with no problem, but in reality, yeah it's a bad idea and there's rarely a good reason for it. Crockford sees enumerating over functions added to the prototype as a "mistake in the language" and so the best practice is to be defensive and ALWAYS add hasOwnProperty to for..in loops. It sucks, but I do it religiously ;) – jshalvi Dec 01 '09 at 18:25
  • 3
    Yes, it breaks jQuery, jQuery will iterate over your Object.prototype extension but assume that it's not a function. When that function appears in the iteration it throws an exception. It doesn't break JavaScript, it's very integral to the design of JavaScript. @jshalvi - you could always create an interator function like jQuery's $.each and just write it once. Crockford has a few misinterpretations about the language but it's not really his fault he pioneered the wild western JavaScript frontier and he only got a few things wrong in the journey. :D – Marcus Pope Feb 02 '12 at 19:28