如何使用javascript(prototypal)继承正确派生私有变量对象

时间:2013-12-09 10:00:23

标签: javascript oop inheritance prototype prototypal-inheritance

我是JavaScript(原型)继承的新手,我正在尝试更多地了解它。 我使用一个简单的观察者模式作为示例,其中我希望可观察对象从'subject'对象派生。这就是我想要做的事情:

function subject()
{
    var callbacks = {}

    this.register = function(name, callback)
    {
        callbacks[name] = callback;
    }

    this.unregister = function(name)
    {
        delete callbacks[name];
    }

    var trigger = function()
    {
        var a = arguments;
        var t = this;

        $.each(callbacks, function(name, callback)
        {
            callback.apply(t, a);
        });
    }
}


list.prototype = new subject()

function list()
{
    var items = {}

    this.add = function(name, item)
    {
        items[name] = item;
        trigger('add', name);
    }

    this.remove = function(name)
    {
        delete items[name];
        trigger('remove', name);
    }
}

现在使用上面的代码时,我遇到了第一个问题:

var l = new list()
l.register('observer1', function() { console.log(this, arguments) });
l.add('item1', 'value1'); // <-- ReferenceError: trigger is not defined, trigger('add', name);

要继续测试,我使用this.trigger将触发器功能设为'public'。再次运行我的例子我遇到了下一个问题:

var l = new list()
l.register('observer1', function() { console.log(this, arguments) });
l.add('item1', 'value1'); // <-- output: subject, ["add", "item1"]

this对象为subject,我希望它为list。创建另一个列表时出现第三个问题:

var l2 = new list();
//Don;t register any observers
l2.add('item1', 'value1'); // <-- output: subject, ["add", "item1"]

callbacks列表在两个列表之间共享。

我也尝试过与Object.create(new subject())类似的东西,并遇到类似的问题。

我的3个问题是:

  1. 我可以使用可在派生对象中使用的私有方法(和 我是否应该关心私人或公共场合?
  2. 如何拥有我想要的this对象(如果可能,无需在派生对象中使用function.call)
  3. 如果callbacks列表没有被共享,我如何将{{1}}列表保留在基础对象中?

3 个答案:

答案 0 :(得分:0)

一个有趣的问题。至于#1和#2:假设你有一个函数foo:

function foo() {
 var _private = 'private var!';
 this.access = function () {
    return _private;
 }
}

access是一个所谓的特权方法,它是一个可以访问私有变量private的闭包。

你可以通过使用call来继承整个事物,如下所示:

function bar() {
  foo.call(this);
}

var b = new bar();
console.log(b.output()); // prints 'private var!'

使用方法applycallbind,您可以建立函数的上下文,有效地篡改this对象。 (您的第2个问题,请阅读here

当然,您无法在派生对象中使用完全私有的方法。你需要一个访问器方法,它会破坏原始方法私有的目的。话虽如此,这也是它在强类型语言中的工作方式(在java中,如果你将方法标记为私有,甚至子条带都不能访问它,那么它必须受到保护)。

对于#3,我想不出如何保持回调共享和私有。 但是你可以通过简单地声明一个函数来使它成为函数的所有实例的静态属性(很像像java这样的语言中的静态属性):

function foo() {
}

添加将分配给每个实例的原型

foo.prototype.bar = // ...

和静态属性

foo.callbacks = [];

foo的所有实例都将共享回调属性。

答案 1 :(得分:0)

结合Joe的建议,这就是我最终的结果:

function subject()
{
    var callbacks = {}

    this.register = function(name, callback)
    {
        callbacks[name] = callback;
    }

    this.unregister = function(name)
    {
        delete callbacks[name];
    }

    trigger = function()
    {
        var a = arguments;
        var t = this;
        $.each(callbacks, function(name, callback)
        {
            callback.apply(t, a);
        });
    }
}

//without the following line, 'this' in firefox is 'subject' instead of 'list' (in chrome it is)
list.prototype = new subject()
//without these, 'list' is not an instanceof 'subject'
list.constructor = subject;
list.prototype.constructor = list;

function list(n)
{
    this.name = n;
    subject.call(this); //as suggested by Joe
    var items = {}

    this.add = function(name, item)
    {
        items[name] = item;
        trigger.call(this, 'add', name); //no way to do this without using call/apply
    }

    this.remove = function(name)
    {
        delete items[name];
        trigger.call(this, 'remove', name); //no way to do this without using call/apply
    }

    this.getitems = function() { return items }
}

//without the following line, 'this' in firefox is 'subject' instead of 'queue'
queue.prototype = new subject()
//without these, 'queue' is not an instanceof 'subject'
queue.constructor = subject;
queue.prototype.constructor = queue;

function queue(n)
{
    this.name = n;
    subject.call(this); //as suggested by Joe
    var items = [];

    this.enqueue = function(item)
    {
        items.push(item);
        trigger.call(this, 'enqueue', item); //no way to do this without using call/apply
    }

    this.dequeue = function()
    {
        var d = items.shift();
        trigger.call(this, 'dequeue', d); //no way to do this without using call/apply
        return d;
    }

    this.getitems = function() { return items }
}

var l1 = new list('l1')

l1.register('observer1', function() { console.log('l1', this, arguments) });
l1.add('item1', 'value1');
// ^ 'l1', list { name = 'l1' ... }, ['add', 'item1']

var l2 = new list('l2')
l2.register('observer2', function() { console.log('l2', this, arguments) });
l2.add('item2', 'value2');
// ^ 'l2', list { name = 'l2' ... }, ['add', 'item2']

var q1 = new queue('q1')
q1.register('observer3', function() { console.log('q1', this, arguments) });
q1.enqueue('item3');
// ^ 'q1', queue { name = 'q1' ... }, ['enqueue', 'item3']

console.log(l1 instanceof list, l1 instanceof subject, l1 instanceof queue);
// ^ true, true, false
console.log(q1 instanceof list, q1 instanceof subject, q1 instanceof queue);
// ^ false, true, true

这会勾选我的所有方框(除了使用电话,但我可以忍受这种情况)。

感谢所有帮助,

玛蒂

编辑:显然这不会按预期工作。创建新对象会覆盖其他对象回调

答案 2 :(得分:0)

你不能拥有私人方法,就是这样。它永远不会同时正常和良好地工作,所以不要试图在JavaScript中模拟它们。

然后你要做的就是在派生的构造函数中调用父的构造函数。

function subject()
{
    var callbacks = {};

    this.register = function(name, callback)
    {
        callbacks[name] = callback;
    };

    this.unregister = function(name)
    {
        delete callbacks[name];
    };

    this.trigger = function()
    {
        var a = arguments;
        var t = this;

        $.each(callbacks, function(name, callback)
        {
            callback.apply(t, a);
        });
    };
}


list.prototype = Object.create(subject);
list.prototype.constructor = list;

function list()
{
    subject.call(this);

    var items = {};

    this.add = function(name, item)
    {
        items[name] = item;
        this.trigger('add', name);
    };

    this.remove = function(name)
    {
        delete items[name];
        this.trigger('remove', name);
    };
}