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:
parent
90c9c69ff0
commit
281ac64876
@ -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]
|
||||
|
89
src/base.js
89
src/base.js
@ -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 = {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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');
|
||||
|
Loading…
Reference in New Issue
Block a user