JavaScript Tutorial/Object Oriented/Inheritance
Содержание
- 1 Adding new properties and methods to the child class
- 2 All new properties and methods of the subclass must come after the assignment of the prototype property
- 3 Class Inheritance
- 4 Hybrid Method for class inheritance
- 5 Inheritance
- 6 Prototype Chaining
- 7 The Apply() Method
- 8 The Call() Method
- 9 Three-level inheritance
- 10 Three level inheritance by using the Hybrid Method
- 11 Using apply function to call base constructor
- 12 Using "apply method" to call the constructor of the base class
- 13 Using call function to call the constructor from base class
- 14 Using "call method" to call the constructor of base class
- 15 Using xbObjects to build three-level inheritance
- 16 Using xbObjects to call the function from base class
Adding new properties and methods to the child class
All new properties and methods must be added after the line that deletes the new method.
function BaseClass(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function SubClass(sColor, sName) {
this.newMethod = BaseClass;
this.newMethod(sColor);
delete this.newMethod;
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
var objA = new BaseClass("red");
var objB = new SubClass("blue", "MyName");
objA.sayColor();
objB.sayColor();
objB.sayName();
All new properties and methods of the subclass must come after the assignment of the prototype property
function SubClass() {
}
SubClass.prototype = new BaseClass();
SubClass.prototype.name = "";
SubClass.prototype.sayName = function () {
alert(this.name);
};
var objA = new BaseClass();
var objB = new SubClass();
objA.color = "red";
objB.color = "blue";
objB.name = "MyName";
objA.sayColor();
objB.sayColor();
objB.sayName();
Class Inheritance
<html>
<head>
<title>Inheritance</title>
<script type="text/javascript">
function Song(title,type) {
this.title = title;
this.type = type;
this.getTitle=function() {
return "Song: " + this.title + " Type: " + this.type;
}
}
function SubSong(title,type,artist) {
this.artist = artist;
this.toString("Artist is " + artist);
Song.apply(this,arguments);
this.toString = function () {
return "Artist: " + this.artist + " " + this.getTitle();
}
}
SubSong.prototype = new Song();
var song = new SubSong("name", "type", "Artist");
alert(song.toString());
</script>
</head>
<body>
</body>
</html>
Hybrid Method for class inheritance
In the SubClass constructor, object masquerading is used to inherit the color property from BaseClass.
Then prototype chaining is used to inherit the methods of BaseClass.
function BaseClass(sColor) {
this.color = sColor;
}
BaseClass.prototype.sayColor = function () {
alert(this.color);
};
function SubClass(sColor, sName) {
BaseClass.call(this, sColor);
this.name = sName;
}
SubClass.prototype = new BaseClass();
SubClass.prototype.sayName = function () {
alert(this.name);
};
var objA = new BaseClass("red");
var objB = new SubClass("blue", "MyName");
objA.sayColor();
objB.sayColor();
objB.sayName();
Inheritance
Call the constructor from base class
A constructor assigns all properties and methods using the this keyword.
You can make the constructor of BaseClass into a method of SubClass and call it.
After the calling, subClass receives the properties and methods defined in BaseClass"s constructor.
In the following code, the "newMethod" is assigned to BaseClass.
Then, the "newMethod" is called, passing the color argument from the SubClass constructor.
The final line of code deletes the reference to BaseClass so that it cannot be called later on.
function BaseClass(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function SubClass(sColor) {
this.newMethod = BaseClass;
this.newMethod(sColor);
delete this.newMethod;
}
var objA = new BaseClass("red");
var objB = new SubClass("blue");
objA.sayColor();
objB.sayColor();
objB.sayName();
Prototype Chaining
The downside to prototype chaining is that it has no support for multiple inheritance.
Prototype chaining involves overwriting the prototype property of the class with another type of object.
You can set the prototype property of SubClass to be an instance of BaseClass to inherit all the properties and methods of BaseClass.
function BaseClass() {
}
BaseClass.prototype.color = "red";
BaseClass.prototype.sayColor = function () {
alert(this.color);
};
function SubClass() {
}
SubClass.prototype = new BaseClass();
The Apply() Method
The apply() method takes two arguments.
The first one is the object to be used for this.
The second one is an array of arguments to be passed to the function.
function sayColor(sPrefix, sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "red";
sayColor.apply(obj, new Array("The color is ",", a very nice color indeed."));
The Call() Method
The first argument of the Call() Method is the object to be used for "this".
All other arguments are passed directly to the function itself.
function sayColor(sPrefix, sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "red";
sayColor.call(obj, "The color is ", ", a very nice color indeed. ");
Three-level inheritance
<html>
<head>
<title>Example</title>
</head>
<body>
<script type="text/javascript">
function Shape(iSides) {
this.sides = iSides;
if (typeof Shape._initialized == "undefined") {
Shape.prototype.getArea = function () {
return 0;
};
Shape._initialized = true;
}
}
function Triangle(iBase, iHeight) {
Shape.call(this, 3);
this.base = iBase;
this.height = iHeight;
if (typeof Triangle._initialized == "undefined") {
Triangle.prototype.getArea = function () {
return 0.5 * this.base * this.height;
};
Triangle._initialized = true;
}
}
Triangle.prototype = new Shape();
function Rectangle(iLength, iWidth) {
Shape.call(this, 4);
this.length = iLength;
this.width = iWidth;
if (typeof Rectangle._initialized == "undefined") {
Rectangle.prototype.getArea = function () {
return this.length * this.width;
};
Rectangle._initialized = true;
}
}
Rectangle.prototype = new Shape();
var triangle = new Triangle(12, 4);
var rectangle = new Rectangle(22, 10);
document.write("triangle.sides:"+triangle.sides);
document.write("<BR>");
document.write("triangle.getArea():"+triangle.getArea());
document.write("<BR>");
document.write("rectangle.sides:"+rectangle.sides);
document.write("<BR>");
document.write("rectangle.getArea():"+rectangle.getArea());
</script>
</body>
</html>
Three level inheritance by using the Hybrid Method
function Shape(iSides) {
this.sides = iSides;
}
Shape.prototype.getArea = function (ss) {
return 0;
};
function Triangle(iBase, iHeight) {
Shape.call(this, 3);
this.base = iBase;
this.height = iHeight;
}
Triangle.prototype = new Shape();
Triangle.prototype.getArea = function () {
return 0.5 * this.base * this.height;
};
function Rectangle(iLength, iWidth) {
Shape.call(this, 4);
this.length = iLength;
this.width = iWidth;
}
Rectangle.prototype = new Shape();
Rectangle.prototype.getArea = function () {
return this.length * this.width;
};
var triangle = new Triangle(12, 4);
var rectangle = new Rectangle(22, 10);
alert(triangle.sides); //outputs "3"
alert(triangle.getArea()); //outputs "24"
alert(rectangle.sides); //outputs "4"
alert(rectangle.getArea()); //outputs "220"
Using apply function to call base constructor
<html>
<head>
<title>Example</title>
</head>
<body>
<script type="text/javascript">
function BaseClass(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function SubClass(sColor, sName) {
BaseClass.apply(this, arguments);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
var objA = new BaseClass("red");
var objB = new SubClass("blue", "MyName");
objA.sayColor();
objB.sayColor();
objB.sayName();
</script>
</body>
</html>
Using "apply method" to call the constructor of the base class
function BaseClass(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function SubClass(sColor, sName) {
BaseClass.apply(this, new Array(sColor));
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
var objA = new BaseClass("red");
var objB = new SubClass("blue", "MyName");
objA.sayColor();
objB.sayColor();
objB.sayName();
Using call function to call the constructor from base class
<html>
<head>
<title>Example</title>
</head>
<body>
<script type="text/javascript">
function BaseClass(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function SubClass(sColor, sName) {
BaseClass.call(this, sColor);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
var objA = new BaseClass("red");
var objB = new SubClass("blue", "MyName");
objA.sayColor();
objB.sayColor();
objB.sayName();
</script>
</body>
</html>
Using "call method" to call the constructor of base class
function BaseClass(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function SubClass(sColor, sName) {
BaseClass.call(this, sColor);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
var objA = new BaseClass("red");
var objB = new SubClass("blue", "MyName");
objA.sayColor();
objB.sayColor();
objB.sayName();
Using xbObjects to build three-level inheritance
<html>
<head>
<title>Example</title>
<script type="text/javascript">
/*
* xbObjects.js
* $Revision: 1.2 $ $Date: 2003/02/07 16:04:20 $
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Bob Clary code.
*
* The Initial Developer of the Original Code is
* Bob Clary.
* Portions created by the Initial Developer are Copyright (C) 2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): Bob Clary <bc@bclary.ru>
*
* ***** END LICENSE BLOCK ***** */
/*
ChangeLog: 2001-12-19 - bclary - changed xbException init method to
remove possible exception due to permission denied issues
in gecko 0.9.5+
*/
function _Classes()
{
if (typeof(_classes) != "undefined")
throw("Only one instance of _Classes() can be created");
function registerClass(className, parentClassName)
{
if (!className)
throw("xbObjects.js:_Classes::registerClass: className missing");
if (className in _classes)
return;
if (className != "xbObject" && !parentClassName)
parentClassName = "xbObject";
if (!parentClassName)
parentClassName = null;
else if ( !(parentClassName in _classes))
throw("xbObjects.js:_Classes::registerClass: parentClassName " + parentClassName + " not defined");
// evaluating and caching the prototype object in registerClass
// works so long as we are dealing with "normal" source files
// where functions are created in the global context and then
// statements executed. when evaling code blocks as in xbCOM,
// this no longer works and we need to defer the prototype caching
// to the defineClass method
_classes[className] = { "classConstructor": null, "parentClassName": parentClassName };
}
_Classes.prototype.registerClass = registerClass;
function defineClass(className, prototype_func)
{
var p;
if (!className)
throw("xbObjects.js:_Classes::defineClass: className not given");
var classRef = _classes[className];
if (!classRef)
throw("xbObjects.js:_Classes::defineClass: className " + className + " not registered");
if (classRef.classConstructor)
return;
classRef.classConstructor = eval( className );
var childPrototype = classRef.classConstructor.prototype;
var parentClassName = classRef.parentClassName;
if (parentClassName)
{
var parentClassRef = _classes[parentClassName];
if (!parentClassRef)
throw("xbObjects.js:_Classes::defineClass: parentClassName " + parentClassName + " not registered");
if (!parentClassRef.classConstructor)
{
// force parent"s prototype to be created by creating a dummy instance
// note constructor must handle "default" constructor case
var dummy;
eval("dummy = new " + parentClassName + "();");
}
var parentPrototype = parentClassRef.classConstructor.prototype;
for (p in parentPrototype)
{
switch (p)
{
case "isa":
case "classRef":
case "parentPrototype":
case "parentConstructor":
case "inheritedFrom":
break;
default:
childPrototype[p] = parentPrototype[p];
break;
}
}
}
prototype_func();
childPrototype.isa = className;
childPrototype.classRef = classRef;
// cache method implementor info
childPrototype.inheritedFrom = new Object();
if (parentClassName)
{
for (p in parentPrototype)
{
switch (p)
{
case "isa":
case "classRef":
case "parentPrototype":
case "parentConstructor":
case "inheritedFrom":
break;
default:
if (childPrototype[p] == parentPrototype[p] && parentPrototype.inheritedFrom[p])
{
childPrototype.inheritedFrom[p] = parentPrototype.inheritedFrom[p];
}
else
{
childPrototype.inheritedFrom[p] = parentClassName;
}
break;
}
}
}
}
_Classes.prototype.defineClass = defineClass;
}
// create global instance
var _classes = new _Classes();
// register root class xbObject
_classes.registerClass("xbObject");
function xbObject()
{
_classes.defineClass("xbObject", _prototype_func);
this.init();
function _prototype_func()
{
// isa is set by defineClass() to the className
// Note that this can change dynamically as the class is cast
// into it"s ancestors...
xbObject.prototype.isa = null;
// classref is set by defineClass() to point to the
// _classes entry for this class. This allows access
// the original _class"s entry no matter how it has
// been recast.
// *** This will never change!!!! ***
xbObject.prototype.classRef = null;
xbObject.prototype.inheritedFrom = new Object();
function init() { }
xbObject.prototype.init = init;
function destroy() {}
xbObject.prototype.destroy = destroy;
function parentMethod(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
{
// find who implemented this method
var className = this.isa;
var parentClassName = _classes[className].classConstructor.prototype.inheritedFrom[method];
var tempMethod = _classes[parentClassName].classConstructor.prototype[method];
// "cast" this into the implementor of the method
// so that if parentMethod is called by the parent"s method,
// the search for it"s implementor will start there and not
// cause infinite recursion
this.isa = parentClassName;
var retVal = tempMethod.call(this, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
this.isa = className;
return retVal;
}
xbObject.prototype.parentMethod = parentMethod;
function isInstanceOf(otherClassConstructor)
{
var className = this.isa;
var otherClassName = otherClassConstructor.prototype.isa;
while (className)
{
if (className == otherClassName)
return true;
className = _classes[className].parentClassName;
}
return false;
}
xbObject.prototype.isInstanceOf = isInstanceOf;
}
}
// eof: xbObjects.js
</script>
</head>
<body>
<script type="text/javascript">
_classes.registerClass("Shape");
function Shape(iSides) {
_classes.defineClass("Shape", prototypeFunction);
this.init(iSides);
function prototypeFunction() {
Shape.prototype.init = function(iSides) {
this.parentMethod("init");
this.sides = iSides;
};
Shape.prototype.getArea = function () {
return 0;
};
}
}
_classes.registerClass("Triangle", "Shape");
function Triangle(iBase, iHeight) {
_classes.defineClass("Triangle", prototypeFunction);
this.init(iBase,iHeight);
function prototypeFunction() {
Triangle.prototype.init = function(iBase, iHeight) {
this.parentMethod("init", 3);
this.base = iBase;
this.height = iHeight;
};
Triangle.prototype.getArea = function () {
return 0.5 * this.base * this.height;
};
}
}
_classes.registerClass("Rectangle", "Shape");
function Rectangle(iLength, iWidth) {
_classes.defineClass("Rectangle", prototypeFunction);
this.init(iLength, iWidth);
function prototypeFunction() {
Rectangle.prototype.init = function(iLength, iWidth) {
this.parentMethod("init", 4);
this.length = iLength;
this.width = iWidth;
}
Rectangle.prototype.getArea = function () {
return this.length * this.width;
};
}
}
var triangle = new Triangle(10, 40);
var rectangle = new Rectangle(20, 50);
alert(triangle.sides);
alert(triangle.getArea());
alert(rectangle.sides);
alert(rectangle.getArea());
</script>
</body>
</html>
Using xbObjects to call the function from base class
<html>
<head>
<title>Example</title>
<script type="text/javascript">
/*
* xbObjects.js
* $Revision: 1.2 $ $Date: 2003/02/07 16:04:20 $
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Bob Clary code.
*
* The Initial Developer of the Original Code is
* Bob Clary.
* Portions created by the Initial Developer are Copyright (C) 2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): Bob Clary <bc@bclary.ru>
*
* ***** END LICENSE BLOCK ***** */
/*
ChangeLog: 2001-12-19 - bclary - changed xbException init method to
remove possible exception due to permission denied issues
in gecko 0.9.5+
*/
function _Classes()
{
if (typeof(_classes) != "undefined")
throw("Only one instance of _Classes() can be created");
function registerClass(className, parentClassName)
{
if (!className)
throw("xbObjects.js:_Classes::registerClass: className missing");
if (className in _classes)
return;
if (className != "xbObject" && !parentClassName)
parentClassName = "xbObject";
if (!parentClassName)
parentClassName = null;
else if ( !(parentClassName in _classes))
throw("xbObjects.js:_Classes::registerClass: parentClassName " + parentClassName + " not defined");
// evaluating and caching the prototype object in registerClass
// works so long as we are dealing with "normal" source files
// where functions are created in the global context and then
// statements executed. when evaling code blocks as in xbCOM,
// this no longer works and we need to defer the prototype caching
// to the defineClass method
_classes[className] = { "classConstructor": null, "parentClassName": parentClassName };
}
_Classes.prototype.registerClass = registerClass;
function defineClass(className, prototype_func)
{
var p;
if (!className)
throw("xbObjects.js:_Classes::defineClass: className not given");
var classRef = _classes[className];
if (!classRef)
throw("xbObjects.js:_Classes::defineClass: className " + className + " not registered");
if (classRef.classConstructor)
return;
classRef.classConstructor = eval( className );
var childPrototype = classRef.classConstructor.prototype;
var parentClassName = classRef.parentClassName;
if (parentClassName)
{
var parentClassRef = _classes[parentClassName];
if (!parentClassRef)
throw("xbObjects.js:_Classes::defineClass: parentClassName " + parentClassName + " not registered");
if (!parentClassRef.classConstructor)
{
// force parent"s prototype to be created by creating a dummy instance
// note constructor must handle "default" constructor case
var dummy;
eval("dummy = new " + parentClassName + "();");
}
var parentPrototype = parentClassRef.classConstructor.prototype;
for (p in parentPrototype)
{
switch (p)
{
case "isa":
case "classRef":
case "parentPrototype":
case "parentConstructor":
case "inheritedFrom":
break;
default:
childPrototype[p] = parentPrototype[p];
break;
}
}
}
prototype_func();
childPrototype.isa = className;
childPrototype.classRef = classRef;
// cache method implementor info
childPrototype.inheritedFrom = new Object();
if (parentClassName)
{
for (p in parentPrototype)
{
switch (p)
{
case "isa":
case "classRef":
case "parentPrototype":
case "parentConstructor":
case "inheritedFrom":
break;
default:
if (childPrototype[p] == parentPrototype[p] && parentPrototype.inheritedFrom[p])
{
childPrototype.inheritedFrom[p] = parentPrototype.inheritedFrom[p];
}
else
{
childPrototype.inheritedFrom[p] = parentClassName;
}
break;
}
}
}
}
_Classes.prototype.defineClass = defineClass;
}
// create global instance
var _classes = new _Classes();
// register root class xbObject
_classes.registerClass("xbObject");
function xbObject()
{
_classes.defineClass("xbObject", _prototype_func);
this.init();
function _prototype_func()
{
// isa is set by defineClass() to the className
// Note that this can change dynamically as the class is cast
// into it"s ancestors...
xbObject.prototype.isa = null;
// classref is set by defineClass() to point to the
// _classes entry for this class. This allows access
// the original _class"s entry no matter how it has
// been recast.
// *** This will never change!!!! ***
xbObject.prototype.classRef = null;
xbObject.prototype.inheritedFrom = new Object();
function init() { }
xbObject.prototype.init = init;
function destroy() {}
xbObject.prototype.destroy = destroy;
function parentMethod(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
{
// find who implemented this method
var className = this.isa;
var parentClassName = _classes[className].classConstructor.prototype.inheritedFrom[method];
var tempMethod = _classes[parentClassName].classConstructor.prototype[method];
// "cast" this into the implementor of the method
// so that if parentMethod is called by the parent"s method,
// the search for it"s implementor will start there and not
// cause infinite recursion
this.isa = parentClassName;
var retVal = tempMethod.call(this, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
this.isa = className;
return retVal;
}
xbObject.prototype.parentMethod = parentMethod;
function isInstanceOf(otherClassConstructor)
{
var className = this.isa;
var otherClassName = otherClassConstructor.prototype.isa;
while (className)
{
if (className == otherClassName)
return true;
className = _classes[className].parentClassName;
}
return false;
}
xbObject.prototype.isInstanceOf = isInstanceOf;
}
}
// eof: xbObjects.js
</script>
</head>
<body>
<script type="text/javascript">
_classes.registerClass("BaseClass");
function BaseClass(sColor) {
_classes.defineClass("BaseClass", prototypeFunction);
this.init(sColor);
function prototypeFunction() {
BaseClass.prototype.init = function (sColor) {
this.parentMethod("init");
this.color = sColor;
};
BaseClass.prototype.sayColor = function () {
alert(this.color);
};
}
}
var objA = new BaseClass("red");
objA.sayColor();
</script>
</body>
</html>