Можно ли получить доступ к переменным из частного конструктора из прототипа функций?

Основываясь на моем понимании javascript, методы прототипа не могут обращаться к переменным, которые являются частными для области конструктора,

 var Foo = function() {
      var myprivate = 'I am private';    
      this.mypublic = 'I am public';
 }

 Foo.prototype = {
     alertPublic: function() { alert(this.mypublic); } // will work
     alertPrivate: function() { alert(myprivate); } // won't work
 }

Это имеет смысл, но существует ли какой-либо путь вокруг этого, что является безопасным и хорошей практикой? Так как использование прототипов обеспечивает преимущество в производительности, поскольку функции-члены распределяются только один раз, я хотел бы получить аналогичную функциональность, сохраняя при этом возможность доступа к своим личным переменным. Я не думаю, что это сработает с использованием прототипа, но есть ли другой шаблон, например метод factory или подход закрытия? Что-то вроде,

var fooFactory = function() {
    var _alertPrivate = function(p) { alert(p); } // bulk of the logic goes here
    return function(args) {
         var foo = {}; 
         var myprivate = args.someVar; 
         foo.mypublic = args.someOtherVar; 
         foo.alertPrivate = function() { _alertPrivate(myprivate); };
         return foo; 
    }; 
}

var makeFoo = new fooFactory();
var foo = makeFoo(args); 

Я не уверен, создается ли новая копия _alertPrivate каждый раз, когда я создаю новый Foo или если есть потенциальная полезность. Цель состоит в том, чтобы получить функциональность, похожую на прототипирование (поскольку она сохраняет память), хотя она все еще может обращаться к закрытым переменным.

Спасибо.

Ответы

Ответ 1

У меня появился следующий шаблон для решения этой проблемы, по крайней мере, пока. То, что мне было нужно, - это привилегированный сеттер, так что частная переменная может быть изменена изнутри определенных функций прототипа, но не из другого места:

 var Foo = (function() {

    // the bulk of the objects behavior goes here and is created once 
    var functions = {
        update: function(a) {
             a['privateVar'] = "Private variable set from the prototype";
        }
    }; 

    // the objects prototype, also created once
    var proto = {
        Update: function() {
             this.caller('update'); 
        }
    };

    // special function to get private vars into scope
    var hoist = function(accessor) {
        return function(key) {
             return functions[key](accessor()); 
        }
    }

    // the constructor itself
    var foo = function foo() {
        var state = {
            privateVar: "Private variable set in constructor",
            // put more private vars here
        }
        this.caller = hoist(function(){
            return state;
        }); 
    }

    // assign the prototype
    foo.prototype = proto;

    // return the constructor
    return foo; 

 })(); 

В основном указатель на внутреннее состояние объектов поднимается к его прототипу через замыкание через простую функцию доступа() {return state; }. Использование функции "caller" в любом данном экземпляре позволяет вам вызывать функции, которые создаются только один раз, но все равно могут ссылаться на личное состояние, хранящееся в этом экземпляре. Важно также отметить, что никакие функции вне прототипа никогда не могли получить доступ к привилегированному аксессуру, поскольку "вызывающий" принимает только ключ, который ссылается на предопределенные функции, которые находятся в области видимости.

Вот несколько эталонов этого метода, чтобы увидеть, как он сравнивается с чистым прототипом. Эти цифры представляют собой создание 80 000 экземпляров объекта в цикле (обратите внимание, что объект, используемый для бенчмаркинга, более сложный, чем тот, который был выше, что было просто для упрощения):

ХРОМ:

Только закрытие - 2172 мс

Прототипирование (выше) - 822ms

Прототипирование (std way) - 751 мс

FIREFOX:

Только закрытие - 1528 мс

Прототипирование (выше) - 971 мс

Прототипирование (std way) - 752ms

Как вы можете видеть, этот метод почти такой же быстрый, как и обычное прототипирование, и, безусловно, быстрее, чем просто обычное закрытие, которое копирует функции вместе с экземпляром.

Ответ 2

Я нашел, что Шон Томан ответил очень полезно (хотя сначала трудно понять).

Не похоже, что публичный сеттер мог принять значение для privateVar, поэтому я сделал несколько настроек:

Измените update на functions:

update: function(st, newVal) {
     st['privateVar'] = newVal;
}

Измените update на proto:

Update: function(newVal) {
     this.caller('update', newVal); 
}

Измените hoist:

var hoist = function(accessor) {
    return function(key) {
        // we can't slice arguments directly because it not a real array
        var args_tail = Array.prototype.slice.call(arguments, 1);
        return functions[key].apply(functions[key], [accessor()].concat(args_tail)); 
    }
}

Ответ 3

То, о чем вы просите, возможно, хотя всегда будет компромисс между производительностью (по скорости или памяти) и функциональностью.

В JavaScript возможно достичь частного состояния для каждого экземпляра, с обычными методами прототипа (и без централизованного, утечки, хранения на местах).

Проверьте статью, которую я написал о методе: http://www.codeproject.com/KB/ajax/SafeFactoryPattern.aspx

Или перейдите непосредственно к исходному коду в: https://github.com/dcleao/private-state.