Michael said:
After reading the above, and the latest JavaScript reference (by
Netscape) concerning exceptions, I took a slightly different approach:
function Exception( msg ) {
I would be concerned whether "Exception" was already defined in any
execution environment. It doesn't seem that unlikely a name to be in
use, maybe "ObjectConstructionException" or something shorter.
if( a instanceof Exception ) {
I think - instanceof - may have entered the language at about the same
point as try-catch. (I have got a copy of the 3nd edition of ECMA 262
which I could e-mail you if you are interested)
You don't happen to know when exceptions were introduced, do you? I
was quite surprised to find that IE 5 is supposed to support
exceptions. I'm sure someone doubted that even IE 6 supported them.
ECMAScript 3rd edition, JavaScript 1.4 and JScript 5.
That's what I thought. Just checking that I wasn't missing anything.
For completeness here is how it could be done:-
The Emulation of Protected Members with Javascript.
The concept of package does not exist in javascript so it is not
practical to define protected in terms of accessibility only to members
of the same package and subclass as in Java. Instead protected is going
to be considered in terms of restricting the accessibility of members to
objects that belong to the same "Class" (built with the same
constructor) or objects that belong to a group of pre-defined related
"Classes". The potential for the use of protected members in subclasses
exists, to the extent that this type of javascript object can be
subclassed.
While it is possible for private instance members (including method) to
be successfully emulated with javascript, for interaction between object
the getters and setters for any protected functionality will themselves
have to be public. Data members will be implemented as private members
with getters and setters to control their accessibility to other
classes. The emulation cannot be done in terms of the visibility of the
getters and setters, instead their willingness to act will be restricted
to interactions initiated by a limited set of classes.
To restrict interactions to a within limited set of classes those
classes are going to need a way of identifying each other as belonging
to that set. This can be done with a shared secret, but it is going to
have to be a secret that cannot be accessed outside of the set of
classes. Similarly there will need to be a mechanism to pass that secret
between classes in the set without exposing the secret and in a way that
cannot be hijacked by external code.
In the same way as closures provide a method of emulating private static
and instance members that are not accessible outside of the Class or
instance in question, they can be used to hold information that is only
available to a group of classes:
(function(){
/* Assign a reference to the global object (this in a function
expression executed inline) to the local variable - global
- so that properties of the global object can be created
relative to it:
*/
var global = this;
/* Create an object to act as the shared secret for the two (or
possibly more) constructors defined during the execution of
this inline function expression. Later, when the complexity
of this closer increases to include (private) method at this
level, one of those methods may be used instead of this
object:
*/
var secret = {};
/* Execute another function expression inline such that it returns
a function object that can act as the constructor for objects
and assign that constructor to a global property called
"ClassA":
*/
global.ClassA = (function(){
/* This inner function definition will act as the constructor
for "ClassA":
*/
function constr(){
/* We do not want our constructor being called as a
function:
*/
if(this == global)return;
/* Ensure that each object instance created with this
constructor holds a reference to its object instance
(will be employed later to ensure that protected
methods can only be executed as methods of this object):
*/
var self = this;
}
/* return a reference to the inner function defined above:
*/
return constr;
})();
/* Execute another function expression inline such that it returns
a function object that can act as the constructor for objects
and assign that constructor to a global property called
"ClassB":
*/
global.ClassB = (function(){
/* This inner function definition will act as the constructor
for "ClassA":
*/
function constr(){
/* We do not want our constructor being called as a
function:
*/
if(this == global)return;
/* Ensure that each object instance created with this
constructor holds a reference to its object instance
(will be employed later to ensure that protected
methods can only be executed as methods of this object):
*/
var self = this;
}
/* return a reference to the inner function defined above:
*/
return constr;
})();
})();
That is the basic framework. It doesn't have any emulation of protected
members yet but the variable - secret - is only available from code
defined within the outer function expression. That code includes that
two class constructors so all instances created with those constructors
has access to the - secret - variable and can use the unique identity of
that object to verify that instructions to access any members it is
regarding as protected are only modified by code that shares that
secret. Code external to the outer function expression cannot access
the - secret - object.
Now to add the mechanics for getting and setting the protected members
and executing (publicly exposed but protected in execution) methods. My
thirst thought in this direction had the - secret - object passed by
reference with the various method calls but it occurred to me that that
would allow the objects to be tricked into giving up their secret. The
secret must stay within its closure, as must any protected data, so
instead I am going to use a series of variables within the same level of
the closure as - secret - to provide a transfer location for the values
and a semaphore mechanism for the requests:
(function(){
var global = this;
var secret = {};
var requestToRead = null;
var requestToSet = null;
var requestToExecute = null;
var validTransfer = null;
var transferValue;
global.ClassA = (function(){
function constr(){
if(this == global)return;
var self = this;
var protectedMemberOfA = 5;
this.getProtectedMemberOfA = function(){
/* First ensure that the function is being executed as
a method of this object:
*/
if(this == self){
/* Check to see that the - requestToGet - variable
has been set to the correct secret:
*/
if(requestToGet == secret){
/* All is well so expose the -
protectedMemberOfA - value to the calling
object (indirectly via the private -
transferValue - variable):
*/
transferValue = protectedMemberOfA;
validTransfer = secret;
}
}
/* Null the - requestToGet - variable:
*/
requestToGet = null;
}
this.setProtectedMemberOfA = function(){
/* First ensure that the function is being executed as
a method of this object:
*/
if(this == self){
/* Check to see that the - requestToSet - variable
has been set to the correct secret:
*/
if(requestToSet == secret){
/* All is well so set the - protectedMemberOfA
- (indirectly from the private -
transferValue - variable):
*/
protectedMemberOfA = transferValue;
}
}
/* Null the - requestToSet - variable:
*/
requestToSet = null;
}
this.incrementProtectedMemberOfA = function(){
/* First ensure that the function is being executed as
a method of this object:
*/
if(this == self){
/* Check to see that the - requestToExecute -
variable has been set to the correct secret:
*/
if(requestToExecute == secret){
/* All is well, so act: */
protectedMemberOfA++;
}
}
/* Null the - requestToExecute - variable:
*/
requestToExecute = null;
}
this.passOnProtMemOfAToClassBInst = function(classBinst){
/* First ensure that the function is being executed as
a method of this object and that the object passed
by reference has the required method:
*/
if((this == self)&&(classBinst.setProtectedMemberOfB)){
/* Set up the interaction with the instance of
ClassB by assigning - secret - to the -
requestToSet - variable:
*/
requestToSet = secret;
/* The place the value of - protectedMemberOfA - in
a location that is accessible to instances of
the appropriate classes:
*/
transferValue = protectedMemberOfA;
/* Call the protected setter on the instance of
ClassB:
*/
classBinst.setProtectedMemberOfB();
/* Null the - requestToSet - variable:
*/
requestToSet = null;
}
}
this.setProtMemOfAToClassBInstProM = function(classBinst){
/* First ensure that the function is being executed as
a method of this object and that the object passed
by reference has the required method:
*/
if((this == self)&&(classBinst.getProtectedMemberOfB)){
/* Set up the interaction with the instance of
ClassB by assigning - secret - to the -
requestToGet - variable:
*/
requestToGet = secret;
/* Call the protected getter on the instance of
ClassB:
*/
classBinst.getProtectedMemberOfB();
/* If the call was to a valid getter the -
requestToGet - variable should have been nulled,
and successful data transfer will be marked by
the - validTransfer - variable knowing the
secret:
*/
if((!requestToGet)&&(validTransfer == secret
){
/* Assign the value of the - transferValue -
variable to the local protected member:
*/
protectedMemberOfA = transferValue;
}
/* Null the - requestToGet - variable: */
requestToGet = null;
/* Null the - validTransfer - variable: */
validTransfer = null;
}
}
}
return constr;
})();
global.ClassB = (function(){
function constr(){
if(this == global)return;
var self = this;
var protectedMemberOfB = 8;
this.getProtectedMemberOfB = function(){
/* First ensure that the function is being executed as
a method of this object:
*/
if(this == self){
/* Check to see that the - requestToGet - variable
has been set to the correct secret:
*/
if(requestToGet == secret){
/* All is well so expose the -
protectedMemberOfB - value to the calling
object (indirectly via the private -
transferValue - variable):
*/
transferValue = protectedMemberOfB;
validTransfer = secret;
}
}
/* Null the - requestToGet - variable:
*/
requestToGet = null;
}
this.setProtectedMemberOfB = function(){
/* First ensure that the function is being executed as
a method of this object:
*/
if(this == self){
/* Check to see that the - requestToSet - variable
has been set to the correct secret:
*/
if(requestToSet == secret){
/* All is well so set the - protectedMemberOfB
- (indirectly from the private -
transferValue - variable):
*/
protectedMemberOfB = transferValue;
}
}
/* Null the - requestToSet - variable:
*/
requestToSet = null;
}
this.incrementProtectedMemberOfB = function(){
/* First ensure that the function is being executed as
a method of this object:
*/
if(this == self){
/* Check to see that the - requestToExecute -
variable has been set to the correct secret:
*/
if(requestToExecute == secret){
/* All is well, so act: */
protectedMemberOfB++;
}
}
/* Null the - requestToExecute - variable:
*/
requestToExecute = null;
}
this.passOnProtMemOfBToClassAInst = function(classAinst){
/* First ensure that the function is being executed as
a method of this object and that the object passed
by reference has the required method:
*/
if((this == self)&&(classBinst.setProtectedMemberOfA)){
/* Set up the interaction with the instance of
ClassA by assigning - secret - to the -
requestToSet - variable:
*/
requestToSet = secret;
/* The place the value of - protectedMemberOfB - in
a location that is accessible to instances of
the appropriate classes:
*/
transferValue = protectedMemberOfB;
/* Call the protected setter on the instance of
ClassA:
*/
classAinst.setProtectedMemberOfA();
/* Null the - requestToSet - variable:
*/
requestToSet = null;
}
}
this.setProtMemOfBToClassAInstProM = function(classAinst){
/* First ensure that the function is being executed as
a method of this object and that the object passed
by reference has the required method:
*/
if((this == self)&&(classBinst.getProtectedMemberOfA)){
/* Set up the interaction with the instance of
ClassA by assigning - secret - to the -
requestToGet - variable:
*/
requestToGet = secret;
/* Call the protected getter on the instance of
ClassA:
*/
classAinst.getProtectedMemberOfA();
/* If the call was to a valid getter the -
requestToGet - variable should have been nulled,
and successful data transfer will be marked by
the - validTransfer - variable knowing the
secret:
*/
if((!requestToGet)&&(validTransfer == secret
){
/* Assign the value of the - transferValue -
variable to the local protected member:
*/
protectedMemberOfB = transferValue;
}
/* Null the - requestToGet - variable: */
requestToGet = null;
/* Null the - validTransfer - variable: */
validTransfer = null;
}
}
}
return constr;
})();
})();
Thus only objects constructed as instances of ClassA and ClassB are able
to interact in certain ways, emulating a concept of "protected". As you
can see the effort to implement is considerable and probably not worth
the gains (if any). I can live without this particular aspect of OO,
after all we will not be implementing aircraft fly-by-wire systems in
javascript, or anything so complex that the simpler emulations of
private instance and static members won't provide all the data hiding
that is needed.
Richard.