Object.defineProperty in ES5? - javascript

I'm seeing posts about a 'new' Object.create that makes enumeration configurable. However, it relies on a Object.defineProperty method. I can't find a cross browser implementation for this method.
Are we stuck writing for the old Object.create? I can't write things that won't work in IE6/7.

There are several things that you can't emulate from the ECMAScript 5 Object.create method on an ECMAScript 3 environment.
As you saw, the properties argument will give you problems since in E3-based implementations there is no way to change the property attributes.
The Object.defineProperty method as #Raynos mentioned, works on IE8, but partially, it can be used only in DOM elements.
Also accessor properties will give you problems, they could be mimicked with widely supported non-standard methods such as __defineGetter__/__defineSetter__, but again, you can't change the property attributes.
Another problem aside the property descriptors, is that the Object.create method can accept null as an argument, to create an object that doesn't inherits from anything.
This can't be emulated with the Crockford's Object.create shim, because when the new operator is used with a constructor function that has a prototype property containing null -or any other non-object value-, the newly created object will inherit from Object.prototype anyway by default.
In some implementations -V8, Spidermonkey, Rhino, etc...- they have a setteable __proto__ property which could be used to set a null [[Prototype]], but again, that's non-standard, and for sure it will never work on IE.
I would recommend, if you want to target old browsers to don't use those features, since there is no way to make them work properly on those environments.
If you still want to use Object.create, without using the properties argument, you could, however I would recommend you to detect the things that can't be emulated.
The following would be a safer version of the Crockford's Object.create shim:
if (typeof Object.create != 'function') {
(function () {
var F = function () {};
Object.create = function (o) {
if (arguments.length > 1) { throw Error('Second argument not supported');}
if (o === null) { throw Error('Cannot set a null [[Prototype]]');}
if (typeof o != 'object') { throw TypeError('Argument must be an object');}
F.prototype = o;
return new F;
};
})();
}
Anyway, use it carefully.

If you want a good defineProperty() implementation, take a look at https://github.com/kriskowal/es5-shim
Unfortunately, you cannot make enumeration configurable in an ES3 environment. This shim will let you call the API in either environment, but the properties will still be enumerable under ES3.

For what it's worth,
Object.defineProperty works in ie8 and FF4.
This means its worthwhile to feature sniff and implement it where it is useful seeing as you would hope the upgrade from ie 6/7 to 8/9 will occur in the next few years.
Another thing to be wary of is that the dontEnum property has a bug in JScript
You will have to work around the way you use the dontEnum property in IE.
[Edit]:
Here's documentation for Internet explorer and a link to the ES5 specification (Page 122 , 15.2.3.6)

Related

Is any getPrototypeOf alternative for IE8 ans less? [duplicate]

Pretty simple:
I have code using Object.getPrototypeOf(...) to get the inherited classes of a Dojo Widget (just a JS object). Object.getPrototypeOf(...) isn't supported in IE8. I need an IE work around. Any ideas? Thanks in advance.
Jon Resig's polyfill works
http://ejohn.org/blog/objectgetprototypeof/
I have made it even smaller
if (typeof Object.getPrototypeOf !== "function")
Object.getPrototypeOf = "".__proto__ === String.prototype
? function (object) {
return object.__proto__;
}
: function (object) {
// May break if the constructor has been tampered with
return object.constructor.prototype;
};
Use https://github.com/kriskowal/es5-shim. Among other things, it supports Object.getPrototypeOf.
Source: ECMAScript 5 polyfills from Modernizr project
Classes created with Dojo.declared store metadata with their superclasses so you don't need to use getPrototypeOf.
I think you can get the first superclass with
MyClass.prototype.constructor._meta.bases[1]
and its prototype with
MyClass.prototype.constructor._meta.bases[1].prototype
(bases[0] seems to be the class itself)
Although why are you even needing to get the prototype? Its very likely you will end up reimplementing some feature that is already provided by dojo.declare

Difference between proto link and Object.create

I want to know the difference between __proto__ and Object.create method. Take this example:
var ob1 = {a:1};
var ob2 = Object.create(ob1);
ob2.__proto__ === ob1; // TRUE
This implies Object.create method creates a new object and sets __proto__ link to the object received as parameter.
Why don't we directly use __proto__ link instead of using create method ?
__proto__ is nonstandard and won't be supported everywhere. Object.create is part of the official spec and should be supported by every environment going forward.
It also is implemented differently in different places.
From Effective Javascript:
Environments differ for example, on the treatment of objects with a
null prototype. In some environments, __proto__ is inherited from
Object.prototype, so an object with a null prototype has no special
__proto__ property
Moving forward the accepted way to create objects and implement inheritance is the Object.create function, and if you do need to access the prototype, you'll want to use Object.getPrototypeOf These functions are standardized and should work the same in all modern environments
Why don't we directly use proto link instead of using create method ?
Because __proto__ is a non-standard property and therefore not necessarily available in every browser.
However it seemed to be considered for ES.next. More info: MDN - __proto__.

IE8 getPrototypeOf method

Pretty simple:
I have code using Object.getPrototypeOf(...) to get the inherited classes of a Dojo Widget (just a JS object). Object.getPrototypeOf(...) isn't supported in IE8. I need an IE work around. Any ideas? Thanks in advance.
Jon Resig's polyfill works
http://ejohn.org/blog/objectgetprototypeof/
I have made it even smaller
if (typeof Object.getPrototypeOf !== "function")
Object.getPrototypeOf = "".__proto__ === String.prototype
? function (object) {
return object.__proto__;
}
: function (object) {
// May break if the constructor has been tampered with
return object.constructor.prototype;
};
Use https://github.com/kriskowal/es5-shim. Among other things, it supports Object.getPrototypeOf.
Source: ECMAScript 5 polyfills from Modernizr project
Classes created with Dojo.declared store metadata with their superclasses so you don't need to use getPrototypeOf.
I think you can get the first superclass with
MyClass.prototype.constructor._meta.bases[1]
and its prototype with
MyClass.prototype.constructor._meta.bases[1].prototype
(bases[0] seems to be the class itself)
Although why are you even needing to get the prototype? Its very likely you will end up reimplementing some feature that is already provided by dojo.declare

Object.preventExtensions actually allows mutation of __proto__?

I was browsing MDC about new functions added to Object. One of them, Object.preventExtensions, is said to prevent mutations to the object's prototype, which can be obtained by using Object.getPrototypeOf or __proto__.
On Chrome, however, it seems to simply allow mutations to the object's prototype. This can be confirmed by just executing the code on the relevant page:
// EXTENSION (only works in engines supporting __proto__
// (which is deprecated. Use Object.getPrototypeOf instead)):
// A non-extensible object's prototype is immutable.
var fixed = Object.preventExtensions({});
fixed.__proto__ = { oh: "hai" }; // throws a TypeError
I don't get this TypeError, and fixed.__proto__.oh === 'hai', so it has been set even though it should have been disallowed. I can also add it when coding like Object.getPrototypeOf(fixed).oh = 'hai'.
Does this mean Chrome has a different interpretation of this function? How can one prevent extensions to an object's prototype (in Chrome)?
Nope, Chrome and Mozilla both implement the standards part of the spec the same. Read carefully:
Object.preventExtensions only prevents
addition of own properties. Properties
can still be added to the object
prototype.
Everything to do with .__proto__ is non-standard, and Chrome can implement that differently. You showed only that Chrome implements details with .__proto__ differently, and in my opinion, more intuitively: The spec says that the prototype is still extensible, so it makes sense that you should still be able to mutate it. The question then becomes why did Mozilla implement it that way?
For example, the following code works the same on both Chrome and FF:
var fixed = Object.preventExtensions({});
Object.getPrototypeOf(fixed).p = 99;
fixed.p; // 99
Clearly the prototype is still mutable. That makes sense with Chrome's implementation of .__proto__.
So to prevent extensions of a prototype, do so separately:
var fixed = Object.preventExtensions({});
Object.preventExtensions(Object.getPrototypeOf(fixed));
Object.getPrototypeOf(fixed).p = 99; // TypeError: Can't add property p, object is not extensible
In ECMAScript 5, objects have a boolean internal property named [[Extensible]], this property is set to false when you call the Object.preventExtensions method, and after that, no new own properties can be added to the object.
On Chrome 14.0.786.0, the assignment to __proto__ throws a TypeError as you expect.
But remember that the __proto__ extension is non-standard, so it's behavior may vary, of course syntactically it is a "property assignment", but internally it doesn't "add an own property", it mutates the object's prototype, thing that is not possible to do by any standard method.
About the example of the Object.getPrototypeOf method you show us, it's simply retrieving the object's prototype, in the case of your fixed object, it's the Object.prototype object:
Object.getPrototypeOf(fixed) === Object.prototype; // true
So your example:
Object.getPrototypeOf(fixed).oh = 'hai'
Is equivalent to:
Object.prototype.oh === 'hai';

What modernizer scripts exist for the new ECMAScript 5 functions?

ECMAScript 5 has quite a few nice additions. John Resig has a good overview here. Here is a good ECMAScript 5 compatibility table.
A lot of this stuff can be "faked" for browsers that don't support these functions yet. Do you know of any scripts that can do this? I'm particularly interested in Object.create.
For example, Douglas Crockford's JSON script checks if JSON functions exist before creating them.
If there was more like the JSON one we could include them when we need to use the new functions.
Crockford recommends this kind of Object.create shim:
if (typeof Object.create != "function") {
Object.create = function (o) {
function F(){}
F.prototype = o;
return new F;
};
}
But please don't do this.
The problem with this approach is that ES5 Object.create has a signature of 2 arguments: first — an object to inherit from, and second (optional) — an object representing properties (or rather, descriptors) to add to newly created object.
Object.create(O[, Properties]); // see 15.2.3.5, ECMA-262 5th ed.
What we have is an inconsistent implementation with 2 different behaviors. In environments with native Object.create, method knows how to handle second argument; in environments without native Object.create, it doesn't.
What are the practical implications?
Well, if there's some code (say, a third party script) that wants to use Object.create, it's rather reasonable for that code to do this:
if (Object.create) {
var child = Object.create(parent, properties);
}
— essentially assuming that if Object.create exists, it must conform to specs — accept second argument and add corresponding properties to an object.
But, with the above-mentioned shim, second argument is simply ignored. There's not even an indication of something going wrong differently. A silent failure, so to speak — something that's rather painful to detect and fix.
Can we do better?
Well, it's actually impossible to create a fully-conforming Object.create shim using only (standard) ES3 facilities. The best solution is to create a custom wrapper method.
There are, however, few alternative (less than optimal) things you can try:
1) Notify user about inability to work with second argument
if (!Object.create) {
Object.create = function (o) {
if (arguments.length > 1) {
throw Error('second argument is not supported');
}
// ... proceed ...
};
}
2) Try to handle second argument:
if (!Object.create) {
Object.create = function (parent, properties) {
function F(){}
F.prototype = parent;
var obj = new F;
if (properties) {
// ... augment obj ...
}
return obj;
};
}
Note that "properties" is an object representing property descriptors, not just property names/values, and is something that's not very trivial to support (some things are not even possible, such as controlling enumerability of a property):
Object.create(parent, {
foo: {
value: 'bar',
writable: true
},
baz: {
get: function(){ return 'baz getter'; },
set: function(value){ return 'baz setter'; },
enumerable: true
}
});
The other inconsistency in the original shim is that it doesn't take care of parent object being null.
var foo = Object.create(null);
This creates an object whose [[Prototype]] is null; in other words, object that doesn't inherit from anything, not even Object.prototype (which all native objects in ECMAScript inherit from).
foo.toString; // undefined
foo.constructor; // undefined
// etc.
This is, by the way, useful to create "proper" hash tables in ECMAScript.
It's possible to emulate this behavior, but only using non-standard extensions, such as "magical" __proto__ property (so implementation would be not very portable or robust). Solution to this problem is similar: either emulate ES5 implementation fully, or notify about inconsistency/failure.
es5-shim http://github.com/kriskowal/es5-shim/
This was part of the narwhal stand-alone javascript environment, but has been broken out on its own. It's pretty darn mature and precise.
es5 - JavaScript/EcmaScript 5 in 3 is a collection shared at BitBucket. Object.create in particular is an easy one to fake, made popular by Crockford et al, but improved on here by Justin Love, focussing on many ES5 parts.
If you don't mind learning the library and writing some code yourself, you can find some code implementations of the ECMAScript 5 library at
https://developer.mozilla.org/En/JavaScript/ECMAScript_5_support_in_Mozilla
For example, the code for Array.filter
And then Crockford has JSON.parse/stringify in json2.js
https://github.com/douglascrockford/JSON-js

Categories

Resources