开发者

Javascript equivalent to C#s "protected"

开发者 https://www.devze.com 2023-04-05 17:42 出处:网络
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 c

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();
    }
};
0

精彩评论

暂无评论...
验证码 换一张
取 消