DatedProgrammer Example
Create a two concrete classes, one with an "abstract" superclass (just pretend BaseConstructor is abstract).
DatedProgrammer has a superclass, BaseConstructor and shadows its toString method.
BaseConstructor
|
+DatedProgrammer
Sortable
Borrow methods from Sortable to DatedProgrammer but don't shadow or replace any methods.
Object.borrow(DatedProgrammer.prototype, Sortable.prototype);
(function(){
/**
* 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 ];
}
}
})();
(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);
}
};
})();
(function(){
evalTextContent('borrow-source');
evalTextContent('function-extend-source');
//--------------------------------------------------------
// Implementation code:
// Here's where we set up the actual classes.
/**
* @constructor BaseConstructor
*/
function BaseConstructor(){};
BaseConstructor.prototype = {
toString : function /*getFunctionName*/() {
var match = /function\s+(\w+)/.exec(this.constructor);
if(match)
return match[1];
return "";
}
};
/**
* @constructor Sortable
*/
function Sortable() {}
Sortable.prototype = {
hashCode : function() {
return Number(this.valueOf());
}
,equals : function(other) {
return this === other;
}
,valueOf : function() {
return new Date().getTime();
}
};
/**
* @constructor
*/
function DatedProgrammer(name, date) {
// this.superclass.constructor.call(this);
this._name = name;
this._date = new Date(date);
};
// DatedProgrammer extends BaseConstructor
Function.extend(
DatedProgrammer,
BaseConstructor,
{
valueOf : function() {
return this._date.valueOf();
}
,toString : function() {
// Chop off the "(Pacific Standard Time)" in FF and Safari.
var dateString = this._date.toString().replace(/\(.*/, "");
var superToString =
this.constructor.superclass.prototype.toString.call(this);
return (superToString + ":"
+ String.nl
+ " name: " +
this._name +
", date: " + dateString);
}
,equals : function(o) {
return o && (this.valueOf() == o.valueOf());
}
});
Object.borrow(DatedProgrammer.prototype, Sortable.prototype);
var calDate = new Date("Tue, 11 Jan 2000 07:00:00 GMT-0800");
var japDate = new Date("Wed, 12 Jan 2000 00:00:00 GMT+900");
var garrett = new DatedProgrammer("Garrett", new Date(calDate));
var tomomi = new DatedProgrammer("Tomomi", new Date(japDate));
Template.evalTemplate("dont-enum-4-expected", ["calDate", calDate, "japDate", japDate]);
return garrett.toString()
+ String.nl
+ tomomi.toString()
+ String.nl
+"garrett.equals(tomomi): " + garrett.equals(tomomi);
})();
| Result | Expected |
|---|---|
DatedProgrammer:
name: Garrett, date: ${calDate.toString().replace(/\(.*/, "")}
DatedProgrammer:
name: Tomomi, date: ${japDate.toString().replace(/\(.*/, "")}
garrett.equals(tomomi): true |
| Browser: | Result: |
|---|---|
| Internet Explorer | DatedProgrammer: name: Garrett, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 DatedProgrammer: name: Tomomi, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 garrett.equals(tomomi): true |
| Mozilla | DatedProgrammer: name: Garrett, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 DatedProgrammer: name: Tomomi, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 garrett.equals(tomomi): true |
| Opera | DatedProgrammer: name: Garrett, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 DatedProgrammer: name: Tomomi, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 garrett.equals(tomomi): true |
| Safari 2 | DatedProgrammer: name: Garrett, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 DatedProgrammer: name: Tomomi, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 garrett.equals(tomomi): true |
| Safari 3 | DatedProgrammer: name: Garrett, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 DatedProgrammer: name: Tomomi, date: Tue, 11 Jan 2000 07:00:00 GMT-0800 garrett.equals(tomomi): true |
DatedProgrammer extended BaseConstructor
shadowing the toString method there.
DatedProgrammer borrowed from Sortable with shadow=false, replace=false.
The variation in the formatting of dates is an annoying distraction. Also notice that I input a Japanese date and got back a date string in local time (PST).