Javascript equivalent to C#s "protected"
The code below uses Javascript to create a base class, eventRaiser
, that has the internals needed to allow clients to subscribe to events, and subclasses to raise these events. The idea is that other classes, like ThingWithEvent
, will inherit from eventRaiser
and expose the subscribe method, and fire off the raise method internally. The jQuery init function demonstrates this.
The way this is written, there's nothing stopping a client from directly raising an event. In other words, adding er.raise("Y");
to the jQuery init function causes the Y event to be raised without difficulty. 开发者_如何学Python
Ideally I'd like to make it so that outside code, interacting with eventRaiser
through some class that inherits from it, with can only subscribe to events, and not raise them.
In other words I'd like raise
to be the equivalent of C# protected
—visible only to itself, and classes that inherit from it.
Is there some slick ninja closure I should use to achieve this, or should I recognize that Javascript is not meant to incorporate OO Encapulation, rename raise
to _raise
to imply to client code that _raise
is private and should not be invoked, and move on?
$(function() {
var er = new ThingWithEvent();
er.subscribe("X", function() { alert("Hello"); });
er.subscribe("X", function() { alert("World"); });
er.subscribe("Y", function() { alert("Not Called"); });
er.doSomething("X");
});
function eventRaiser() {
var events = {};
this.subscribe = function(key, func) {
if (!events[key])
events[key] = { name: key, funcs: [] };
events[key].funcs.push(func);
};
this.raise = function(key) {
if (!events[key]) return;
for (var i = 0; i < events[key].funcs.length; i++)
events[key].funcs[i]();
};
}
function ThingWithEvent() {
eventRaiser.call(this);
var self = this;
this.doSomething = function() {
alert("doing something");
self.raise("X");
}
}
function surrogate() { }
surrogate.prototype = eventRaiser;
ThingWithEvent.prototype = new surrogate();
ThingWithEvent.prototype.constructor = ThingWithEvent;
Read this: http://javascript.crockford.com/private.html
There are no classes in javascript, so you can make constructor for event manager with this.subscribe(obj)
as a method for subscribing, and var raise(event)
as a private method for rising them, which can only be called by instances of it..
function EventRaiser () {
var foo = 1; // will be private
function raise() { ... }; // will be private
var raise1 = function () { ... }; // will be private
this.subscribe = function () { ... }; // will be privileged, has access to private vars and methods
this.foo = 1; // public, anyone can read/write
return this;
}
var er = new EventRaiser (); // here we make instance of constructor
er.subscribe(); // will work
er.raise(); // will THROW error, because it is 'private'
Local function raise(event)
will only be visible to instances of eventRaiser
, and not to instances of derived constructors. (But they will have their own private raise
function, inaccessible to anyone else).
Consider:
function ThingWithEvent() {
var thing = {},
events = {};
function raise( key ) {
if ( !events[ key ] ) { return; }
for ( var i = 0; i < events[ key ].funcs.length; i++ )
events[ key ].funcs[ i ]();
}
thing.subscribe = function ( key, func ) {
if ( !events[ key ] ) {
events[ key ] = { name: key, funcs: [] };
}
events[ key ].funcs.push( func );
};
thing.doSomething = function () {
alert( "doing something" );
raise( "X" );
};
return thing;
}
So, each instance of ThingWithEvent
will get it's own events
object (which is a private member of the instance because it's a local variable of the constructor).
raise
is a nested function inside the constructor which makes it a private method of the instance.
this.subscribe
and this.doSomething
are "privileged" methods of the instance. They and only they can access the private members and private methods of the instance.
Btw, I defined a explicit thing
object which represents the new instance. I do this instead of just using this
(which represents the new instance by default), because it enables me to identify the new instance uniquely inside the constructor even in nested functions - no var self = this;
hack necessary.
Update:
This would be inheritance:
function Thing() {
var thing = Object.create( new EventTarget );
thing.doSomething = function () {
alert( "doing something" );
this.raise( "X" );
};
return thing;
}
function EventTarget() {
var events = {};
this.raise = function ( key ) {
if ( !events[ key ] ) { return; }
for ( var i = 0; i < events[ key ].funcs.length; i++ )
events[ key ].funcs[ i ]();
}
this.subscribe = function ( key, func ) {
if ( !events[ key ] ) {
events[ key ] = { name: key, funcs: [] };
}
events[ key ].funcs.push( func );
};
}
Usage:
var thing = new Thing();
thing.subscribe( ... );
thing.doSomething( ... );
You can get closer to what you want by returning an object with a limited interface:
function EventSource() {
var events = {};
var self = this;
this.subscribe = function(key, func) {
if (!events[key])
events[key] = { name: key, funcs: [] };
events[key].funcs.push(func);
};
this.raise = function(key) {
if (!events[key]) return;
for (var i = 0; i < events[key].funcs.length; i++)
events[key].funcs[i]();
};
this.limited = function() {
return {
subscribe: function(k, f) { return self.subscribe(k,f);}
};
};
}
Then you can call .limited()
on an EventSource
and get a limited-access object that you can call .subscribe()
on, but not .raise()
. If you can control where these are instantiated, say, with a factory, you can limit the damage.
jQuery uses this pattern with its Deferred
objects; The limited objects are called promises
, and are created with .promise()
.
I hate answering my own question, let alone accepting my own answer, but it turns out this is not only possible, but insanely easy. The idea behind this code comes from Douglas Crockford's JavaScript The Good Parts. The trick is to ditch constructors (neoclassical inheritance) altogether and use functional inheritance.
This code not only achieves public, protected, and private access levels, but it's much cleaner, and easier to read IMO than inheriting constructors and swapping prototypes and constructors around. The only catch is that this code will be slightly slower since each object creation necessitates the creation of each of the object's functions, instead of getting all that dumped in for free with a constructor's prototype. So, if you need to create tens of thousands of objects in your web site then you might prefer a constructor. For...everyone else, this code is likely for you.
function eventRaiser(protectedStuff) {
protectedStuff = protectedStuff || {};
var that = {};
var events = {}; //private
protectedStuff.raise = function(key) {
if (!events[key]) return;
for (var i = 0; i < events[key].funcs.length; i++)
events[key].funcs[i].apply(null, Array.prototype.slice.call(arguments, 1));
};
that.subscribe = function(key, func) {
if (!events[key])
events[key] = { name: key, funcs: [] };
events[key].funcs.push(func);
};
return that;
}
function widget() {
var protectedStuff = {};
var that = eventRaiser(protectedStuff);
that.doSomething = function() {
alert("doing something");
protectedStuff.raise("doStuffEvent");
};
return that;
}
$(function() {
var w = widget();
w.subscribe("doStuffEvent", function(){ alert("I've been raised"); });
w.doSomething();
w.protectedStuff.raise("doStuffEvent"); //error!!!!! raise is protected
w.raise("doStuffEvent"); //and this obviously won't work
});
Inspired by Adam's answer (just what I was looking for!) I whipped this together to give protected methods with constructors / neoclassical inheritance.
IT IS COMPLETELY UNTESTED and may be wrong, but I thought I'd throw it out there.
It uses the idea of "protectedStuff" from Adam's answer to allow two-way non-public communication between the base class and derived class.
/*** Example Base Class ***/
var Example = {};
Example.BaseClass = function(shared)
{
// Private Variables
var protected = shared; // protected is the two-way communication mechanism between the base and derived class
var internal = 0;
// Protected Variables and Methods
protected.internalStuff = function()
{
}
// Public Variables and Methods
this.public = 123;
this.show = function(arg)
{
protected.onBeforeShow();
privateMethod(arg);
protected.onAfterShow();
doSomeMoreStuff();
};
// Private Methods
function privateMethod(arg)
{
internal = arg;
}
};
/*** Example Derived Class ***/
Example.DerivedClass = function(parameter)
{
var protected = (
{
onBeforeShow : function()
{
// ...
},
onAfterShow : function()
{
// ...
}
});
// Call our base class' constructor
Example.BaseClass.call(this, protected); // Tells base class about our protected methods, adds its protected methods to the 'protected' variable.
// At this point our instance has all the public methods and properties of the base class.
// Public Methods that our derived class overrides
var baseShow = this.show; // Save the base class' implementation of the show method
this.show = function(arg)
{
baseShow.call(this, arg); // Call the base class' implementation
// ..
}
// Public Variables and Methods Unique to This Class
this.derivedVariable = 123;
this.derivedMethod = function()
{
protected.internalStuff();
}
};
精彩评论