JavaScript OOP (Polymorphism and Inheritance)

Object Oriented Programming Goals

I assume that the reader has a basic familiarity with JavaScript, function calls, and the basic tenets of object oriented programming. I consider the three primary goals of object oriented programming to be:

  • Encapsulation - Support for method calls on a JavaScript object as a member of a Class.
  • Polymorphism - The ability for two classes to respond to the same (collection of) methods.
  • Inheritance - The ability to define the behavior of one object in terms of another by sub-classing.

Through a series of examples (which, for the curious reader, are actually snippets of live JavaScript code embedded within this page), I will demonstrate how objects can be used in JavaScript and how these object oriented paradigms can be best implemented. I will cover techniques for:

  • Defining a Class
  • Defining and calling Methods in a Class
  • Defining a Sub-Class
  • Calling the Super-Class constructor from a Sub-Class
  • Overriding Methods of a Super-Class in a Sub-Class
  • Calling a Super-Class method from a Sub-Class

Simple Objects


So, for example, we can create a new object and add several ad-hoc properties to it with the following code:The simplest object oriented construct in JavaScript is the built-in Object data type. In JavaScript, objects are implemented as a collection of named properties. Being an interpreted language, JavaScript allows for the creation of any number of properties in an object at any time (unlike C++, properties can be added to an object at any time; they do not have to be pre-defined in an object declaration or constructor).

obj&nbsp;=&nbsp;new&nbsp;Object; <br>obj.x&nbsp;=&nbsp;1; <br>obj.y&nbsp;=&nbsp;2;

Which creates a JavaScript object which I will represent graphically like this:

obj
x 1
y 2
Object.prototype
constructor Object

The left hand column displays the property name of each available property on the object, while the right hand column displays it's value. Note that in addition to the x and y properties that we created, our object has an additional property called constructor that points (in this case) to an internal JavaScript function. I will explain prototype properties, below.

Defining a Class - Object Constructors

A new JavaScript class is defined by creating a simple function. When a function is called with the newoperator, the function serves as the constructor for that class. Internally, JavaScript creates an Object, and then calls the constructor function. Inside the constructor, the variable this is initialized to point to the just created Object. This code snippet defines a new class, Foo, and then creates a single object of that class.

function&nbsp;Foo() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;1; <br>&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;=&nbsp;2; <br>} <br>&nbsp; <br>obj&nbsp;=&nbsp;new&nbsp;Foo; <br>
obj
x 1
y 2
Foo.prototype
constructor Foo
Object.prototype
(constructor) Object

Note that we can now create as many Foo type objects as we want, all of whom will be properly initialized to have their x and y properties set to 1 and 2, respectively.

Prototypes Explained

In JavaScript, each Object can inherit properties from another object, called it's prototype. When evaluating an expression to retrieve a property, JavaScript first looks to see if the property is defined directly in the object. If it is not, it then looks at the object's prototype to see if the property is defined there. This continues up the prototype chain until reaching the root prototype. Each object is associated with a prototype which comes from the constructor function from which it is created.

For example, if we want to create an object, X, from constructor function B, whose prototype chain is: B.prototype, A.prototype, Object.prototype:

We would use the following code:

Object.prototype.inObj&nbsp;=&nbsp;1; <br>&nbsp; <br>function&nbsp;A() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.inA&nbsp;=&nbsp;2; <br>} <br>&nbsp; <br>A.prototype.inAProto&nbsp;=&nbsp;3; <br>&nbsp; <br>B.prototype&nbsp;=&nbsp;new&nbsp;A;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Hook&nbsp;up&nbsp;A&nbsp;into&nbsp;B's&nbsp;prototype&nbsp;chain <br>B.prototype.constructor&nbsp;=&nbsp;B; <br>function&nbsp;B() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.inB&nbsp;=&nbsp;4; <br>} <br>&nbsp; <br>B.prototype.inBProto&nbsp;=&nbsp;5; <br>&nbsp; <br>x&nbsp;=&nbsp;new&nbsp;B; <br>document.write(x.inObj&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;x.inA&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;x.inAProto&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;x.inB&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;x.inBProto); <br>
1, 2, 3, 4, 5
x
inB 4
B.prototype
constructor B
inA 2
inBProto 5
A.prototype
(constructor) A
inAProto 3
Object.prototype
(constructor) Object
inObj 1

In FireFox and in ActionScript, an object's prototype can be explicitly referenced via the non-standard __proto__ property. But in standard JavaScript a prototype object can only by directly referenced through the object's constructor function object.

Defining and Calling Methods in a Class

JavaScript allows you to assign any function to a property of an object. When you call that function usingobj.Function() syntax, it will execute the function with this defined as a reference to the object (just as it was in the constructor).

The standard paradigm for defining methods is to assign functions to a constructor's prototype. That way, all objects created with the constructor automatically inherit the function references via the prototype chain.

function&nbsp;Foo() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;1; <br>} <br>&nbsp; <br>Foo.prototype.AddX&nbsp;=&nbsp;function(y)&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;Method <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;+=&nbsp;y; <br>} <br>&nbsp; <br>obj&nbsp;=&nbsp;new&nbsp;Foo; <br>&nbsp; <br>obj.AddX(5);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Call&nbsp;Method <br>
obj
x 6
Foo.prototype
constructor Foo
AddX  
Object.prototype
(constructor) Object

Polymorphism is achieved by simply having different object classes implement a collection of methods that use the same names. Then, a caller, need just use the correctly named function property to invoke the appropriate function for each object type.

function&nbsp;A() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;1; <br>} <br>&nbsp; <br>A.prototype.DoIt&nbsp;=&nbsp;function()&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;Method <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;+=&nbsp;1; <br>} <br>&nbsp; <br>function&nbsp;B() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;1; <br>} <br>&nbsp; <br>B.prototype.DoIt&nbsp;=&nbsp;function()&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;Method <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;+=&nbsp;2; <br>} <br>&nbsp; <br>a&nbsp;=&nbsp;new&nbsp;A; <br>b&nbsp;=&nbsp;new&nbsp;B; <br>&nbsp; <br>a.DoIt(); <br>b.DoIt(); <br>document.write(a.x&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;b.x); <br>
2, 3
a
x 2
A.prototype
constructor A
DoIt  
Object.prototype
(constructor) Object
b
x 3
B.prototype
constructor B
DoIt  
Object.prototype
(constructor) Object

Defining a Sub-Class

The standard paradigm, is to use the prototype chain to implement the inheritance of methods from a super class. Any methods defined on the sub-class will supersede those defined on the super-class.

function&nbsp;A()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;super&nbsp;class <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;1; <br>} <br>&nbsp; <br>A.prototype.DoIt&nbsp;=&nbsp;function()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;Method <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;+=&nbsp;1; <br>} <br>&nbsp; <br>B.prototype&nbsp;=&nbsp;new&nbsp;A;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;sub-class <br>B.prototype.constructor&nbsp;=&nbsp;B; <br>function&nbsp;B() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;A.call(this);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Call&nbsp;super-class&nbsp;constructor&nbsp;(if&nbsp;desired) <br>&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;=&nbsp;2; <br>} <br>&nbsp; <br>B.prototype.DoIt&nbsp;=&nbsp;function()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;Method <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;A.prototype.DoIt.call(this);&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Call&nbsp;super-class&nbsp;method&nbsp;(if&nbsp;desired) <br>&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;+=&nbsp;1; <br>} <br>&nbsp; <br>b&nbsp;=&nbsp;new&nbsp;B; <br>&nbsp; <br>document.write((b&nbsp;instanceof&nbsp;A)&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;(b&nbsp;instanceof&nbsp;B)&nbsp;+&nbsp;'&lt;BR/&gt;'); <br>b.DoIt(); <br>document.write(b.x&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;b.y); <br>
true, true
2, 3
b
x 2
y 3
B.prototype
constructor B
(x) 1
DoIt  
A.prototype
(constructor) A
(DoIt)  
Object.prototype
(constructor) Object

Something to keep in mind is that each time a sub-class is defined, we explicitly call the constructor of the super-class in order to insert it into our prototype chain. So it is important to ensure that no undesirable side-effects will occur when this call is made. Conversely, if the super-class constructor should be called for each instance of every sub-class, code must be explicitly added to the sub-class's constructor to make this call (as is done in the above example).

An Alternate Sub-Classing Paradigm

As an alternate to using the prototype chain, I've developed a method which avoids calling the constructor of a super class when each sub-class is defined. Three methods are added to the Function object:

Function.prototype.DeriveFrom = function&nbsp;(fnSuper) <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;prop; <br> <br>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(this&nbsp;==&nbsp;fnSuper) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;alert("Error&nbsp;-&nbsp;cannot&nbsp;derive&nbsp;from&nbsp;self"); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <br> <br>&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(prop&nbsp;in&nbsp;fnSuper.prototype) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(typeof(fnSuper.prototype[prop])&nbsp;==&nbsp;"function"&nbsp;&amp;&amp;&nbsp;!this.prototype[prop]) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.prototype[prop]&nbsp;=&nbsp;fnSuper.prototype[prop]; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <br> <br>&nbsp;&nbsp;&nbsp;&nbsp;this.prototype[fnSuper.StName()]&nbsp;=&nbsp;fnSuper; <br>} Function.prototype.StName = function&nbsp;() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;st; <br> <br>&nbsp;&nbsp;&nbsp;&nbsp;st&nbsp;=&nbsp;this.toString(); <br>&nbsp;&nbsp;&nbsp;&nbsp;st&nbsp;=&nbsp;st.substring(st.indexOf("&nbsp;")+1,&nbsp;st.indexOf("(")); <br>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(st.charAt(0)&nbsp;==&nbsp;"(") <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;st&nbsp;=&nbsp;"function&nbsp;..."; <br> <br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;st; <br>} Function.prototype.Override = function&nbsp;(fnSuper,&nbsp;stMethod) <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.prototype[fnSuper.StName()&nbsp;+&nbsp;"_"&nbsp;+&nbsp;stMethod]&nbsp;=&nbsp;fnSuper.prototype[stMethod]; <br>}

Repeating the sub-classing example using this new paradigm:

function&nbsp;A()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;super&nbsp;class <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;1; <br>} <br>&nbsp; <br>A.prototype.DoIt&nbsp;=&nbsp;function()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;Method <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;+=&nbsp;1; <br>} <br>&nbsp; <br>B.DeriveFrom(A);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;sub-class <br>function&nbsp;B() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.A();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Call&nbsp;super-class&nbsp;constructor&nbsp;(if&nbsp;desired) <br>&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;=&nbsp;2; <br>} <br>&nbsp; <br>B.Override(A,&nbsp;'DoIt'); <br>B.prototype.DoIt&nbsp;=&nbsp;function()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Define&nbsp;Method <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;this.A_DoIt();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Call&nbsp;super-class&nbsp;method&nbsp;(if&nbsp;desired) <br>&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;+=&nbsp;1; <br>} <br>&nbsp; <br>b&nbsp;=&nbsp;new&nbsp;B; <br>&nbsp; <br>document.write((b&nbsp;instanceof&nbsp;A)&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;(b&nbsp;instanceof&nbsp;B)&nbsp;+&nbsp;'&lt;BR/&gt;'); <br>b.DoIt(); <br>document.write(b.x&nbsp;+&nbsp;',&nbsp;'&nbsp;+&nbsp;b.y); <br>
false, true
2, 3
b
x 2
y 3
B.prototype
constructor B
DoIt  
A A
A_DoIt  
Object.prototype
(constructor) Object

Unfortunately, this technique does not allow for the use of the instanceof operator to test for membership of a super-class. But, we have the added benefit that we can derive from more than one super class (multiple inheritance).

Private Members

Amazingly, JavaScript also can support private members in an object. When the constructor is called, variables declared in the function scope of the constructor will actually persist beyond the lifetime of the construction function itself. To access these variables, you need only create local functions within the scope of the constructor.  They may reference local variables in the constructor.

function&nbsp;A() <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;x&nbsp;=&nbsp;7; <br>&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;this.GetX&nbsp;=&nbsp;function()&nbsp;{&nbsp;return&nbsp;x;} <br>&nbsp;&nbsp;&nbsp;&nbsp;this.SetX&nbsp;=&nbsp;function(xT)&nbsp;{&nbsp;x&nbsp;=&nbsp;xT;&nbsp;} <br>} <br>&nbsp; <br>obj&nbsp;=&nbsp;new&nbsp;A; <br>obj2&nbsp;=&nbsp;new&nbsp;A; <br>document.write(obj.GetX()&nbsp;+&nbsp;'&nbsp;'&nbsp;+&nbsp;obj2.GetX()); <br>obj.SetX(14); <br>document.write('&nbsp;'&nbsp;+&nbsp;obj.GetX()&nbsp;+&nbsp;'&nbsp;'&nbsp;+&nbsp;obj2.GetX()); <br>
7 7 14 7
obj
GetX  
SetX  
A.prototype
constructor A
Object.prototype
(constructor) Object
obj2
GetX  
SetX  
A.prototype
constructor A
Object.prototype
(constructor) Object

I believe, however, that each instance of an object created in this way, has it's own copy of each local function.  The local copy of the function can maintain a copy of the local scope (a closure) of the constructor.  This would be rather inefficient for object classes that construct many instances.  Experiments with a single (shared) reference to a function reveal that they can only reference variables from a single instance of the class.  Since the benefits of using private members is rather limited in the context of JavaScript (which is already lacking any form of type safety), I would not recommend making extensive use of the private member paradigm.

[zz]http://mckoss.com/jscript/object.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值