DontEnum ]DontEnum ]
]Panel class whose prototype has a type property that is truthy.
Menu class that extends Panel.
type property.
window.__r) in instances of Panel and Draggable.
(function(){
// Grab the previous example's source.
evalTextContent('dont-enum-aug-hidden-source');
function Panel( ){ }
Panel.prototype.type = 1;
Panel.prototype.dockable = false;
function Menu() {}
Menu.prototype = new Panel( );
Menu.superclass = new Panel;
Menu.prototype.show = function() {
return( this.constructor.prototype.type );
};
function Draggable ( id, type ) {
this.id = id;
this.type = "horizontal-drag";
this.dockable = true;
this.drag = function(){ return (this.type); };
};
Animatable = {
valueOf : function() {return 3;}
};
window.__r = new Menu;
var hasTypeBefore = window.__r.hasOwnProperty('type');
var hasToStringBefore = window.__r.hasOwnProperty('toString');
YAHOO.lang.augmentObject(window.__r, new Draggable, true)
YAHOO.lang.augmentObject(window.__r, Animatable);
var hasTypeAfter = window.__r.hasOwnProperty('type');
var hasToStringAfter = window.__r.hasOwnProperty('toString');
return [
window.__r.drag( )
,hasTypeBefore
,hasTypeAfter
,window.__r.valueOf() // Should the valueOf be taken from Animatable?
,hasToStringBefore
,hasToStringAfter
].join(String.nl);
})();
| Result | Expected | Yahoo Expects |
|---|---|---|
| horizontal-drag false true [object Object] false false | horizontal-drag false true unclear false unclear |
| Internet Explorer | Mozilla | Opera (as IE*) | Safari 2 | Safari 3 |
|---|---|---|---|---|
| horizontal-drag false true 3 false false | horizontal-drag false true [object Object] false false | horizontal-drag false true 3 false false | horizontal-drag false true [object Object] false false | horizontal-drag false true [object Object] false false |
It's difficult to say what the Expected Result should be. YUI's comments are unclear and the code is contradictory.
No property value of window.__r was replaced, or "overwritten".
I will prove this with the delete operator.
Browsers that identify as IE will get valueOf copied to window.__r.
Other browsers will not get valueOf copied. This is because overrides is false.
Function _IEEnumFix ignores overrides.
* To Change Opera's User Agent: Opera > Quck Preferences > Mask as Internet Explorer
Do we consider the receiver's [[Prototype]], or just the receiver?
What about the supplier's prototype?
To understand the difference, it is necessary to understand the
prototype chain. window.__r.type is resolved in
window.__r's prototype chain. The value is 1.
Before borrowing from Draggable, window.__r does not have a
type property on its self. The type property exists in the [[Prototype]].
A new property is created on __r, shadowing the existing
type property in the prototype. This can be proven by calling delete window.__r.type.
(function(){ // Grab the previous example's source. evalTextContent('dont-enum-aug-2-source'); var result = [ "before-delete: " + window.__r.type ]; if( delete window.__r.type ) { result.push("after-delete: " + window.__r.type); } return result.join( String.nl ); })();
| Result: | |
|---|---|
before-delete: horizontal-drag after-delete: 1 |
A shadowed type property still exists. We could re-shadow it, if we wanted to, but I think once is enough
to get the idea.
If you have a browser that supports read/write access to __proto, you could delete
that shadowed property with delete window.__r.__proto__.__proto__.type
The delete operator returns false only when the property had the DontDelete attribute.
It returns true otherwise, even when nothing was deleted (even if the object does not contain the property). This is correct
behavoir, as per the spec, but is counterintuitive.
(function(){ return delete ({}).nothing; })();
true
Internet Explorer will throw an error when calling delete on a host object,
even for an expando or custom property (expando can also cause memory leak problems).
(function(){
var isIE = false;
try{
var deleted = delete window.clipboardData;
} catch( e ) {
isIE = true;
try {
alert( e.name ); // IE Actually throws an error on e.name - bad variable name.
}
catch(ee) { // IE Actually throws an error here.
alert( ee.name + ": " + ee.message + " \n" );
}
} finally {
return isIE;
}
})();
isIE:
true
So now I've covered delete; helping to demonstrate how shadowing works.
Such misunderstandings can and should be expected for language features that are not clearly exposed to the programmer.
Now that I've explained shadowing, I'll back to where I left off with the Yahoo example.
YAHOO.lang._IEEnumFix
Method _IEEnumFix is intended to address the JScript DontEnum bug.
A bug report was filed around January, 2007,
recommending that Yahoo investigate the problem and test the solution.
The problem was that
Yahoo.augment had not addressed the JScript DontEnum bug.
YUI's answer is _IEEnumFix.
/** * IE will not enumerate native functions in a derived object even if the * function was overridden. This is a workaround for specific functions * we care about on the Object prototype. * @property _IEEnumFix * @param {Function} r the object to receive the augmentation * @param {Function} s the object that supplies the properties to augment * @static * @private */ _IEEnumFix: function(r, s) { if (YAHOO.env.ua.ie) { var add=["toString", "valueOf"]; for (i=0;i < add.length;i=i+1) { var fname=add[i],f=s[fname]; if (YAHOO.lang.isFunction(f) && f!=Object.prototype[fname]) { r[fname]=f; } } } }
The code comment states:
IE will not enumerate native functions in a derived object even if the
function was overridden.
During enumeration, IE will skip any property in any object where
there is a corresponding property in the object's prototype chain that has the
DontEnum attribute. Native code is not a property; it's a value;
the term derived has no meaning or relevance.
_IEEnumFix does not take the overrides parameter variable.
This explains the different results in IE.
The userAgent string will usually
provide the correct userAgent. This addresses a browser, however, not a problem.
Opera will be inflicted with the _IEEnumFix when the user
preference is set to mask as Internet Explorer.
Do not fork for a browser if it can be avoided. Use feature detection when you can. Use the user agent as a last resort.—YAHOO.env.ua documentation
Good advice.
One final oddity about the loop is the avoidance of the postfix-increment operator in favor of
i=i+1. This is most likely due to paranoia that can be directly accredited to JSLint
and its author who provides advice to Yahoo.
Working with such low-level problems as these is not simple.
After all this analysis, I'm going to provide what I think will address the problems and avoid the bugs.
Next in this tutorial: Borrow Properties that are Ignored by IE and Other Browsers