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 toObject.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 Array
s (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
How to Get All Options of a Select Using Jquery
Using Ajax to Read Local Files
Typescript Recursive Function Composition
JavaScript to Sort Contents of Select Element
Firefox 'Cross-Origin Request Blocked' Despite Headers
Need to Cancel Click/Mouseup Events When Double-Click Event Detected
Jquery .On() Method with Multiple Event Handlers to One Selector
Scaling Socket.Io to Multiple Node.Js Processes Using Cluster
Triggering a JavaScript Click() Event at Specific Coordinates
Merge Two Arrays So That the Values Alternate
How to Build Query String with JavaScript