Why Were Es5 Object Methods Not Added to Object.Prototype

Why were ES5 Object methods not added to Object.prototype?

This is all explained very nicely in "Proposed ECMAScript 3.1 Static Object Functions: Use Cases and Rationale" document (pdf) by Allen Wirfs-Brock himself (editor of ES5 spec, and a member of TC39).

I would suggest to read all of it. It's pretty short, easily digestible, and gives a nice glimpse of the thought process behind these ES5 additions.

But to quote relevant section (emphasis mine):

A number of alternatives API designs were considered before the
proposed API was chosen. In the course of considering alternatives we
developed a set of informal guidelines that we applied when
considering the alternatives. These guidelines are:

  • Cleanly separate the meta and application layers.
  • Try to minimize the API surface area (i.e., the number of methods and the complexity of their arguments).
  • Focus on usability in naming and parameter design.
  • Try to repeatedly apply basic elements of a design.
  • If possible, enable programmers or implementations to statically optimize uses of the API.

[...]

Here are some of the alternatives that were considered that lead to
the selected design.

The obvious initial idea, following the example of the already
existing standard method Object.prototype.propertyIsEnumerable, was to
add additional “propertyIs...” query methods on Object.prototype for
the other attributes and a parallel set of attribute changing methods.

[...]

As we considered this approach there were a number of things about it
that we didn’t like and that seemed contrary to the above API design
guidelines:

  • It merges rather than separates the meta and application layers. As methods on Object.prototype the methods would be part of the public
    interface of every application object
    in a program. As such, they need
    to be understood by every developer, not just library designers.

[...]

Why Object.getPrototypeOf is static and not in Object's prototype

Putting anything into Object's prototype means adding it to all the objects ever created in all existing and future JS code on earth. Consequences would be very unpredictable. These extra 7 chars - not too high price to pay for stability.

Well, this method implemented in Object.prototype would be called just getPrototype (as far as getPrototypeOf with no arguments looks a bit strange). It is a very common name and indeed someone already has such method in one of his objects with a totally different meaning. Okay, adding global getPrototype won't break his existing code, but one day I will try to call hisObj.getPrototype assuming the new meaning, and I will get something wrong.

By the way, the isPrototypeOf method added in 3rd edition of ES is 12 years older than getPrototypeOf defined by ES 5.1.

Can't add method prototype to JavaScript object

Returning an object circumvents the usual return value of a constructor, which is the this variable. Instead of returning this, you're returning some other object, and that object doesn't have a username property or a method_name method. This is roughly what happens at each point in the code:

function User(un) {
this.username = un; // puts username on the 'this' object

// returns an entirely different, unrelated object that doesn't use User's prototype
return{
getUsername: function (){
return un;
},
setUsername: function(username) {
un = username;
}
};
}

// sets method_name on the prototype of the 'this' object for User
User.prototype.method_name = function() {
return this.username;
};

var user = new User("Michael"); // passes a new User.prototype as the implicit 'this' in the User function
console.log(user.method_name());

Instead, try this:

function User(un) {
this.username = un;
this.getUsername = function (){
return un;
};
this.setUsername = function(username) {
un = username;
};
}
User.prototype.method_name = function() {
return this.username;
};

Why do built-in functions not have a prototype property?

It's not the .prototype that allows a function to be used as a constructor, but the presence of the [[Construct]] internal method. See this section, step 4.

Normal functions created by the user script automatically have this internal property set, so all user functions can be called as constructors. This is because the interpreter can't know how the user intends to use that method.

For native functions the intended usage is known in advance, so the javascript engine can decide which native functions should be callable as constructors. Does it make sense to invoke new [].push?

It is mentioned in the introductory part to built-in objects that:

None of the built-in functions described in this clause that are not constructors shall implement the [[Construct]] internal method unless otherwise specified in the description of a particular function. None of the built-in functions described in this clause shall have a prototype property unless otherwise specified in the description of a particular function.

And the reason, IMHO, is that there is no valid real use case that would need that. There's no good explanation why push should be instantiable: what's the difference between a new push and a new generic object? So, allowing the instantiation of those functions doesn't bring any value to the developer, but it will raise lots of WTFs from others reading the code.

Object.prototype is Verboten?

The fact is, it's fine as long as you know what you're doing and what the costs are. But it's a big "if". Some examples of the costs:

  • You'll need to do extensive testing with any library you choose to use with an environment that augments Object.prototype, because the overwhelming convention is that a blank object will have no enumerable properties. By adding an enumerable property to Object.prototype, you're making that convention false. E.g., this is quite common:

    var obj = {"a": 1, "b": 2};
    var name;
    for (name in obj) {
    console.log(name);
    }

    ...with the overwhelming convention being that only "a" and "b" will show up, not "getProperties".

  • Anyone working on the code will have to be schooled in the fact that that convention (above) is not being followed.

You can mitigate the above by using Object.defineProperty (and similar) if supported, but beware that even in 2014, browsers like IE8 that don't support it properly remain in significant use (though we can hope that will change quickly now that XP is officially EOL'd). That's because using Object.defineProperty, you can add non-enumerable properties (ones that don't show up in for-in loops) and so you'll have a lot less trouble (at that point, you're primarily worried about name conflicts) — but it only works on systems that correctly implement Object.defineProperty (and a correct implementation cannot be "shimmed").

In your example, I wouldn't add getProperties to Object.prototype; I'd add it to Object and accept the object as an argument, like ES5 does for getPrototypeOf and similar.

Be aware that the Prototype library gets a lot of flak for extending Array.prototype because of how that affects for..in loops. And that's just Arrays (which you shouldn't use for..in on anyway (unless you're using the hasOwnProperty guard and quite probably String(Number(name)) === name as well).

...if the V8 people do it, who am I to say they're wrong?

On V8, you can rely on Object.defineProperty, because V8 is an entirely ES5-compliant engine.

Note that even when the properties are non-enumerable, there are issues. Years ago, Prototype (indirectly) defined a filter function on Array.prototype. And it does what you'd expect: Calls an iterator function and creates a new array based on elements the function chooses. Then ECMAScript5 came along and defined Array.prototype.filter to do much the same thing. But there's the rub: Much the same thing. In particular, the signature of the iterator functions that get called is different (ECMAScript5 includes an argument that Prototype didn't). It could have been much worse than that (and I suspect — but cannot prove — that TC39 were aware of Prototype and intentionally avoided too much conflict with it).

So: If you're going to do it, be aware of the risks and costs. The ugly, edge-case bugs you can run into as a result of trying to use off-the-shelf libraries could really cost you time...

Adding function to Object prototype causes function to show up in all 'for X in OBJ' loops

Which is why you should always check hasOwnProperty:

for (var temp in tempObject) {
if (Object.prototype.hasOwnProperty(tempObject, temp)) {
console.log(temp);
}
}

Crockford advocates using Object.prototype.hasOwnProperty instead of tempObject.hasOwnProperty, just in case you override hasOwnProperty in your object.


In ES5, you can set it to not be enumerable:

Object.defineProperty(Object.prototype, 'simpleFunction', {
value: function() {
return true;
},
enumerable: false, // this is actually the default
});

Alternatively (in ES5), you can use Object.keys() to only get the object's own keys:

Object.keys(tempObject).forEach(function(key) {
console.log(key);
});


Related Topics



Leave a reply



Submit