DontEnum ]DontEnum ]
]The solution is to make the fucking browsers support the language!
OK, ok, IE is crap, JavaScript sucks, blah, blah... time to stop complaing and roll my sleeves up.
I've just addressed borrowing here. I'll show how to create an array from an object next.
The borrow function is unrefined. In developing your own library, you should work on packaging
and API design.
The approach I've taken for borrowing is to create an Array of
names from properties of Object.prototype that have the DontEnum attribute,
objectDontEnums.
(§15.2.4.7).
for in, copying properties using a given
shadow/replace strategy (see the code for details).
objectDontEnums.
objectDontEnums[ i ].
DontEnum attribute,
copy the property in the receiver, based on the given shadow/replace strategy.
DontEnum properties in Object.prototypetoString toLocaleString valueOf hasOwnProperty isPrototypeOf propertyIsEnumerable constructor
Function instance and Function.prototype properties that have DontEnum.
prototype call apply
There are many more properties of other objects that have the DontEnum attribute.
For example, Number.MAX_VALUE has the attributes { DontEnum, DontDelete, ReadOnly }.
If we create a Number object and add a MAX_VALUE property to that number object,
IE will skip that property.
We can avoid dealing with these imaginary problems by limiting our function to support only
Function and Object instances (YAGNI).
Object.prototype.hasOwnProperty
We can use hasOwnProperty safely.
(function(){ var obj = { toString : function() { return "obj to string"; } }; return obj.hasOwnProperty("toString"); })();
Should be: true
| Browser: | Internet Explorer | Mozilla | Opera | Safari 2 | Safari 3 |
|---|---|---|---|---|---|
| Result: | true | true | true | true | true |
YUI does not use hasOwnProperty and I think it is because they are trying to support
Safari 1.3.
The idea is the same thing as augmentObject; copy properties from one object to another. The difference
is it considers shadowing separately from replacing.
The method signature will take four parameters.
borrow( receiver, supplier, shadow, override )
/** * Borrow properties from supplier to receiver using a for-in loop. * * Does not copy values from the supplier's prototype; uses hasOwnProperty. * this prevents things like "constructor" and "prototype" from slpping through * inconsistantly in some browsers. However, if the object is a Function, and contains * a "prototype" property that is enumerable, this property will be copied over; forcefully, * if the browser incorrectly skips it in enumeration. * * The only property that will be skipped is "constructor". In JScript, there is no way * to know if a constructor property is enumerable or not. In most cases, it is not. If you have * added a "constructor" property to the supplier, and want that copied to the receiver, * it must be done manually, e.g. r.constructor = s.constructor. * * @param {Function|Object} receiver The object that is to receive the properties. * Cannot be null or undefined. * * * @param {Function|Object} supplier The object that is to supply the properties. * Cannot be a built-in, but may be a subclass of any other object. May be a function. * * @param {Boolean} shadow if true, properties that exist in * the receiver's prototype, but not the receiver, will be copied to the receiver. * * @param {Boolean} replace if true, every property that exists in the supplier * will be copied to the receiver. * * @throws {TypeError} Thrown if receiver or supplier is neither typeof * "object" or typeof "function" * * This provides for four possible use-cases * 1. receiver has a property in its prototype and wants to borrow properties from supplier, * but does not want to affect it's own behavior: * borrow( receiver, supplier ); // * * 2. receiver has a property in its prototype, but wants to get every method from * supplier, even if it shadows something it receiver's superclass. * borrow( receiver, supplier, true ); // most common. * * 3. receiver wants to borrow everything from supplier, even when * certain properties would replace values in the receiver's own instance. * borrow( receiver, supplier, true, true ); // common * * 4. receiver wants to borrow everything from supplier, even when certain properties would * replace values in the receiver's own instance, but does not want to shadow any properties * that occur in the receiver's prototype. * borrow( receiver, supplier, false, true ); // uncommon. * ` */ Object.borrow = function borrow( receiver, supplier, shadow, replace ) { // Throw an error if supplier or receiver is not an Object or a Function. if(typeof supplier != "object" && typeof supplier != "function") throw new TypeError("borrow failed: supplier not an object or function: " + typeof supplier); if(typeof receiver != "object" && typeof receiver != "function") throw new TypeError("borrow failed: receiver not an object or function: " + typeof supplier); for( var prop in supplier) { var rp = receiver[ prop ]; if( rp === supplier[ prop ] || !supplier.hasOwnProperty( prop ) ) continue; // User must explicitly copy this over. if( prop == "constructor" ) continue; if ( shadow && replace ) { // shadow and replace receiver[ prop ] = supplier[ prop]; } else if( shadow ) { // shadow, don't replace if( !receiver.hasOwnProperty( prop ) ) receiver[ prop ] = supplier[ prop ]; } else if( replace ) { // replace, don't shadow if( receiver.hasOwnProperty( prop ) &&!(prop in receiver.constructor.prototype)) receiver[ prop ] = supplier[ prop ]; } else if( !(prop in receiver) ) { // don't shadow, don't replace // don't shadow, don't replace. receiver[ prop ] = supplier[ prop ]; } } if(shadow) dontEnumFix(receiver, supplier, replace ); }; /** * All shadowed properties */ function dontEnumFix(r,s, replace ) { // JScript goes wrong here. var objectDontEnums = [ // For "constructor" the user must explicitly copy it over. // "constructor" "toString" ,"toLocaleString" ,"isPrototypeOf" ,"propertyIsEnumerable" ,"hasOwnProperty" ,"valueOf" ]; if( typeof s == "function" && typeof s.call == "function" ) { objectDontEnums.push("call", "apply", "prototype"); } for( var i = 0; i < objectDontEnums.length; i++ ) { var p = objectDontEnums[ i ]; if(!s.hasOwnProperty( p ) || r[ p ] === s[ p ] ) continue; if ( replace || !r.hasOwnProperty(p) ) r[ p ] = s[ p ]; } }
Unfortunately, one of the pitfalls with having to support Internet Explorer is bloated, inefficient, and unnecessarily complex code.
Fortunately, MSIE is working with TG1 and will be implementing ECMAScript 4. This will resolve problems with enumeration. In ES4, smaller, faster, and less buggy library code will be possible.
borrow throws a TypeError if you pass it something that's not an Object or a
Function. This is used here as an equivalent of Java's IllegalArgumentException. It
helps us discover a problem as soon as possible.
Type checking is something that JavaScript does not make easy. Built-ins like String or Date objects can slip through our function.
borrow({}, "oops"); // borrow would throw a TypeError
borrow(null, function(){}) // Error
Function.extend can use Object.borrowWe can now use borrow with extend. The default behavior used here is to shadow properties in the receiver, but not replace them.
(function(){ /** * @param {prototypeShadows} - extra properties to add to the subclass' prototype */ Function.extend = function extend(subc, superc, prototypeShadows) { if (typeof subc != "function" || typeof superc.call != "function") { throw new TypeError("extend: subclass is not a function " + subc); } if (typeof superc != "function" || typeof superc.call != "function") { throw new TypeError("extend: superclass is not a function: " + superc); } // Fix for JavaScript's broken constructor. var F = Function(); F.prototype = superc.prototype; subc.prototype = new F(); subc.prototype.constructor=subc; subc.superclass = superc; if (superc.prototype.constructor === Object) { superc.prototype.constructor=superc; } if (prototypeShadows) { Object.borrow(subc.prototype, prototypeShadows, true); } }; })();
Use extend when you want to chain constructors and methods. Having a clearly
defined interface can make your code more explicit and obvious.
Use borrow when you want to add additional behavior to an object. Having a shallow inheritance hierarchy
is almost always easier to deal with. Borrowing adds behavior. It can be thought of as a substitute for a
Decorator pattern.
Ideally, your objects will be so simple that you won't need either borrow or extend.
If you're writing library code, it's necessary to study OOA&D. Some useful readings I've found: Head First Design Patterns, Refactoring, and Domain Driven Design.
Object.borrow and Function.extend
I've created a deep prototype chain in two examples.
The examples are useful to the point of testing the code.
JavaScript Needs a DateFormatter
Dates cannot be formatted to localized formats in JavaScript.
Method Date.prototype.toLocaleString returns an implementation-dependent
string which has varied results. The format cannot be relied upon. Date Strings cannot be
reliably parsed by a programmer. The built-in Date.parse is not guaranteed to parse localized date strings.
Java's SimpleDateFormat
solves these problems.
Next in this tutorial: Borrow Using an Array?