Можно ли получить доступ к закрытым переменным в области конструктора из прототипа функции? - PullRequest
8 голосов
/ 17 октября 2011

Исходя из моего понимания 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
 }

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

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, или есть потенциальная выгода в производительности.Намерение состоит в том, чтобы получить функциональность, аналогичную прототипированию (поскольку она экономит память), и при этом иметь возможность доступа к закрытым переменным.

Спасибо.

Ответы [ 3 ]

4 голосов
/ 18 октября 2011

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

 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 экземпляров объекта в цикле (обратите внимание, что объект, используемый для сравнительного анализа, является более сложным, чем приведенный выше, который был только для упрощения):

CHROME:

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

Прототипирование (вышеуказанный способ) - 822мс

Прототипирование (стандартный способ) - 751мс

FIREFOX:

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

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

Прототипирование (стандартный способ) - 752 мс

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

1 голос
/ 18 июля 2014

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

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

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

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

1 голос
/ 09 мая 2013

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

Не похоже, что общедоступный установщик может принять значение для 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's not a real array
        var args_tail = Array.prototype.slice.call(arguments, 1);
        return functions[key].apply(functions[key], [accessor()].concat(args_tail)); 
    }
}
...