prototype: Merge -r6792:HEAD from ../branches/inheritance/{src/base.js,test/base.html}. Robust inheritance support for Class.create. Closes #5459.

This commit is contained in:
Sam Stephenson 2007-08-04 04:03:51 +00:00
parent 90c9c69ff0
commit 281ac64876
3 changed files with 167 additions and 17 deletions

View File

@ -1,5 +1,12 @@
*SVN*
* Inheritance branch merged to trunk; robust inheritance support for Class.create. Closes #5459. [Dean Edwards, Alex Arnell, Andrew Dupont, Mislav Mahronic]
- To access a method's superclass method, add "$super" as the first argument. (The naming is significant.) Works like Function#wrap.
- Class.create now takes two optional arguments. The first is an existing class to subclass; the second is an object literal defining the instance properties/methods. Either can be omitted. Backwards-compatible with old Class.create.
- Added Class.extend for dynamically adding methods to existing classes (while preserving inheritance chain). Can also be used for mixins.
- The 'constructor' property of a class instance always points back to the proper class. Class objects themselves have two special properties: 'superclass' and 'subclasses' (which default to 'null' and '[]', respectively). Allows for powerful introspection.
- Added Object.isFunction [sam]
* Add Function#argumentNames, which returns an ordered array of the function's named arguments. [sam]
* Add Prototype.Browser.MobileSafari which evaluates to true on the iPhone's browser. [sam]

View File

@ -1,19 +1,74 @@
/* Based on Alex Arnell's inheritance implementation. */
var Class = {
create: function() {
return function() {
this.initialize.apply(this, arguments);
}
}
}
create: function(parent, methods) {
if (arguments.length == 1 && typeof parent !== 'function')
methods = parent, parent = null;
var method = function() {
if (!Class.extending) this.initialize.apply(this, arguments);
};
method.superclass = parent;
method.subclasses = [];
if (Object.isFunction(parent)) {
Class.extending = true;
method.prototype = new parent();
method.prototype.constructor = method;
var Abstract = new Object();
parent.subclasses.push(method);
delete Class.extending;
}
if (methods) Class.extend(method, methods);
return method;
},
extend: function(destination, source) {
for (var name in source) Class.inherit(destination, source, name);
return destination;
},
inherit: function(destination, source, name) {
var prototype = destination.prototype, ancestor = prototype[name],
descendant = source[name];
if (ancestor && Object.isFunction(descendant) &&
descendant.argumentNames().first() == "$super") {
var method = descendant, descendant = ancestor.wrap(method);
Object.extend(descendant, {
valueOf: function() { return method },
toString: function() { return method.toString() }
});
}
prototype[name] = descendant;
if (destination.subclasses && destination.subclasses.length > 0) {
for (var i = 0, subclass; subclass = destination.subclasses[i]; i++) {
Class.extending = true;
Object.extend(subclass.prototype, new destination());
subclass.prototype.constructor = subclass;
delete Class.extending;
Class.inherit(subclass, destination.prototype, name);
}
}
},
mixin: function(destination, source) {
return Object.extend(destination, source);
}
};
var Abstract = { };
Object.extend = function(destination, source) {
for (var property in source) {
destination[property] = source[property];
}
return destination;
}
};
Object.extend(Object, {
inspect: function(object) {
@ -29,21 +84,24 @@ Object.extend(Object, {
toJSON: function(object) {
var type = typeof object;
switch(type) {
switch (type) {
case 'undefined':
case 'function':
case 'unknown': return;
case 'boolean': return object.toString();
}
if (object === null) return 'null';
if (object.toJSON) return object.toJSON();
if (Object.isElement(object)) return;
var results = [];
for (var property in object) {
var value = Object.toJSON(object[property]);
if (value !== undefined)
results.push(property.toJSON() + ': ' + value);
}
return '{' + results.join(', ') + '}';
},
@ -66,7 +124,7 @@ Object.extend(Object, {
},
clone: function(object) {
return Object.extend({}, object);
return Object.extend({ }, object);
},
isElement: function(object) {
@ -75,6 +133,10 @@ Object.extend(Object, {
isArray: function(object) {
return object && object.constructor === Array;
},
isFunction: function(object) {
return typeof object == "function";
}
});
@ -155,14 +217,13 @@ var Try = {
return returnValue;
}
}
};
RegExp.prototype.match = RegExp.prototype.test;
/*--------------------------------------------------------------------------*/
var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
@ -191,4 +252,4 @@ PeriodicalExecuter.prototype = {
}
}
}
}
});

View File

@ -45,15 +45,43 @@
var arg1 = 1;
var arg2 = 2;
var arg3 = 3;
function TestObj(){}
function TestObj() { };
TestObj.prototype.assertingEventHandler =
function( event, assertEvent, assert1, assert2, assert3, a1, a2, a3 ){
function(event, assertEvent, assert1, assert2, assert3, a1, a2, a3) {
assertEvent(event);
assert1(a1);
assert2(a2);
assert3(a3);
}
};
var globalBindTest = null;
// base class
var Animal = Class.create({
initialize: function(name) {
this.name = name;
},
name: "",
eat: function() {
return this.say("Yum!");
},
say: function(message) {
return this.name + ": " + message;
}
});
// subclass that augments a method
var Cat = Class.create(Animal, {
eat: function($super, food) {
if (food instanceof Mouse) return $super();
else return this.say("Yuk! I only eat mice.");
}
});
// empty subclass
var Mouse = Class.create(Animal, {});
new Test.Unit.Runner({
testFunctionArgumentNames: function() { with(this) {
@ -201,6 +229,18 @@
assert(!Object.isElement(document.createTextNode('bla')));
}},
testObjectIsFunction: function() { with(this) {
assert(Object.isFunction(function() { }));
assert(Object.isFunction(Class.create()));
assert(!Object.isFunction("a string"));
assert(!Object.isFunction($("testlog")));
assert(!Object.isFunction([]));
assert(!Object.isFunction({}));
assert(!Object.isFunction(0));
assert(!Object.isFunction(false));
assert(!Object.isFunction(undefined));
}},
// sanity check
testDoesntExtendObjectPrototype: function() {with(this) {
// for-in is supported with objects
@ -283,6 +323,48 @@
info('Running on Gecko');
assert(Prototype.Browser.Gecko);
}
}},
testInstantiation: function() { with(this) {
var pet = new Animal("Nibbles");
assertEqual("Nibbles", pet.name, "property not initialized");
assertEqual('Nibbles: Hi!', pet.say('Hi!'));
assertEqual(Animal, pet.constructor, "bad constructor reference");
assertUndefined(pet.superclass);
}},
testInheritance: function() { with(this) {
var tom = new Cat('Tom');
assertEqual(Cat, tom.constructor, "bad constructor reference");
assertEqual(Animal, tom.constructor.superclass, 'bad superclass reference');
assertEqual('Tom', tom.name);
assertEqual('Tom: meow', tom.say('meow'));
assertEqual('Tom: Yuk! I only eat mice.', tom.eat(new Animal));
}},
testSupercall: function() { with(this) {
var tom = new Cat('Tom');
assertEqual('Tom: Yum!', tom.eat(new Mouse));
}},
testAddingInstanceMethod: function() { with(this) {
var tom = new Cat('Tom');
var jerry = new Mouse('Jerry');
Class.extend(Animal, {
sleep: function() {
return this.say('ZZZ');
}
});
Class.extend(Mouse, {
sleep: function($super) {
return $super() + " ... no, can't sleep! Gotta steal cheese!";
}
});
assertEqual('Tom: ZZZ', tom.sleep(), "added instance method not available to subclass");
assertEqual("Jerry: ZZZ ... no, can't sleep! Gotta steal cheese!", jerry.sleep());
}}
}, 'testlog');