Частный член Javascript на прототипе - PullRequest
17 голосов
/ 27 января 2009

Ну, я пытался понять, возможно ли это любым способом. Вот код:

a=function(text)
{
   var b=text;
   if (!arguments.callee.prototype.get)
      arguments.callee.prototype.get=function()
    {
         return b;
    }
    else
      alert('already created!');
}

var c=new a("test");  // creates prototype instance of getter
var d=new a("ojoj");  // alerts already created
alert(c.get())        // alerts test 
alert(d.get())        // alerts test from context of creating prototype function :(

Как видите, я пытался создать прототип геттера. Для чего? Ну, если вы напишите что-то вроде этого:

a=function(text)
{
    var b=text;
    this.getText=function(){ return b}
}

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

EDIT:

Я попробовал решение, данное Кристофом, и кажется, что пока это единственное известное решение. Чтобы получить значение из контекста, нужно запомнить информацию об идентификаторе, но вся идея мне нравится :) Идентифицировать только одну вещь, все остальное можно сохранить один раз в памяти. На самом деле вы можете хранить множество закрытых членов таким образом и использовать в любое время только один идентификатор. На самом деле это удовлетворяет меня :) (если кто-то не получил лучшую идею).

someFunc = function()
{
  var store = new Array();
  var guid=0;
  var someFunc = function(text)
  {
    this.__guid=guid;
    store[guid++]=text;
  }

  someFunc.prototype.getValue=function()
  {
    return store[this.__guid];
  }

  return someFunc;
}()

a=new someFunc("test");
b=new someFunc("test2");

alert(a.getValue());
alert(b.getValue());

Ответы [ 8 ]

28 голосов
/ 27 января 2009

JavaScript традиционно не предусматривал механизм скрытия свойств («частные члены»).

Поскольку JavaScript имеет лексическую область действия, вы всегда можете смоделировать это на уровне отдельных объектов, используя функцию конструктора в качестве замыкания над вашими «закрытыми членами» и определяя ваши методы в конструкторе, но это не будет работать для методов определено в свойстве прототипа конструктора.

Конечно, есть способы обойти это, но я бы не советовал:

Foo = (function() {
    var store = {}, guid = 0;

    function Foo() {
        this.__guid = ++guid;
        store[guid] = { bar : 'baz' };
    }

    Foo.prototype.getBar = function() {
        var privates = store[this.__guid];
        return privates.bar;
    };

    Foo.prototype.destroy = function() {
        delete store[this.__guid];
    };

    return Foo;
})();

Это сохранит «частные» свойства в другом объекте, отдельном от вашего Foo экземпляра. Обязательно вызовите destroy() после завершения работы с объектом: в противном случае вы только что создали утечку памяти.


edit 2015-12-01: ECMAScript6 делает улучшенные варианты, которые не требуют ручного уничтожения объектов, например, с помощью WeakMap или предпочтительно Symbol, избегая необходимости внешнего магазина в целом:

var Foo = (function() {
    var bar = Symbol('bar');

    function Foo() {
        this[bar] = 'baz';
    }

    Foo.prototype.getBar = function() {
        return this[bar];
    };

    return Foo;
})();
2 голосов
/ 06 октября 2015

В современных браузерах, использующих некоторые технологии ES6, вы можете использовать WeakMap, чтобы обойти проблему с GUID. Это работает в IE11 и выше:

// Scope private vars inside an IIFE
var Foo = (function() { 
    // Store all the Foos, and garbage-collect them automatically
    var fooMap = new WeakMap();

    var Foo = function(txt) { 
        var privateMethod = function() { 
            console.log(txt); 
        };
        // Store this Foo in the WeakMap
        fooMap.set(this, {privateMethod: privateMethod}); 
    } 

    Foo.prototype = Object.create(Object.prototype); 
    Foo.prototype.public = function() { 
        fooMap.get(this).p(); 
     } 
     return Foo; 
 }());

 var foo1 = new Foo("This is foo1's private method");
 var foo2 = new Foo("This is foo2's private method");
 foo1.public(); // "This is foo1's private method"
 foo2.public(); // "This is foo2's private method"

WeakMap не будет хранить ссылки на любые Foo после того, как он будет удален или выпадет из области видимости, и поскольку он использует объекты в качестве ключей, вам не нужно прикреплять GUID к вашему объекту.

2 голосов
/ 11 декабря 2013

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

  • Больше не нужно выполнять какие-либо настройки в конструкторе
  • Убрана необходимость хранить публичный GUID в экземплярах
  • Добавлен синтаксический сахар

По сути, хитрость заключается в том, чтобы использовать сам объект экземпляра в качестве ключа для доступа к связанному частному объекту. Обычно это невозможно для простых объектов, поскольку их ключи должны быть строками. Однако я смог сделать это, используя тот факт, что выражение ({} === {}) возвращает false. Другими словами, оператор сравнения может различать уникальные экземпляры объектов.

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

Foo = (function() {
    var instances = [], privates = [];

    // private object accessor function
    function _(instance) {
        var index = instances.indexOf(instance), privateObj;

        if(index == -1) {
            // Lazily associate instance with a new private object
            instances.push(instance);
            privates.push(privateObj = {});
        }
        else {
            // A privateObject has already been created, so grab that
            privateObj = privates[index];
        }
        return privateObj;
    }

    function Foo() {
        _(this).bar = "This is a private bar!";
    }

    Foo.prototype.getBar = function() {
        return _(this).bar;
    };

    return Foo;
})();

Вы заметите функцию _ выше. Это функция доступа для захвата частного объекта. Он работает лениво, поэтому, если вы вызовете его с новым экземпляром, он создаст новый приватный объект на лету.

Если вы не хотите дублировать код _ для каждого класса, вы можете решить эту проблему, поместив его в фабричную функцию:

function createPrivateStore() {
    var instances = [], privates = [];

    return function (instance) {
        // Same implementation as example above ...
    };
}

Теперь вы можете уменьшить его до одной строки для каждого класса:

var _ = createPrivateStore();

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

2 голосов
/ 27 января 2009

Методы в прототипе не могут получить доступ к «закрытым» членам, поскольку они существуют в javascript; вам нужен какой-то привилегированный доступ. Поскольку вы объявляете get, где он может лексически видеть b, он всегда вернет то, что b было при создании.

0 голосов
/ 22 сентября 2018

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

const Immutable = function ( val ) {
    let _val = val;

    this.$ = {
        _resolve: function () {
            return _val;
        }
    };
};
Immutable.prototype = {
    resolve: function () {
        return this.$._resolve();
    }
};

По существу скрывает внутреннюю _val от манипуляций и делает экземпляр этого объекта неизменным.

0 голосов
/ 17 августа 2018

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

  // Create the object
  function MyObject() {}

  // Add methods to the prototype
  MyObject.prototype = {

    // This is our public method
    public: function () {
      console.log('PUBLIC method has been called');
    },

    // This is our private method tucked away inside a nested privacy object called x
    x: {
      private: function () {
        console.log('PRIVATE method has been called');
      }
    },

  }

// Create an instance of the object
var mo = new MyObject(); 

теперь, когда кодер печатает "мо". intellisense покажет только публичную функцию и «х». Таким образом, все закрытые члены не показываются, но скрываются за «x», что делает маловероятным, чтобы кодер случайно вызвал закрытое членство, потому что им придется изо всех сил и набрать «mo.x.» чтобы увидеть частных членов. Этот метод также позволяет избежать загромождения списка intellisense несколькими частными именами участников, скрывая их все за одним элементом «x».

0 голосов
/ 17 апреля 2014

Лично мне не очень нравится решение с guid, потому что оно заставляет разработчика объявлять его в дополнение к хранилищу и увеличивать его в конструкторе. В больших javascript-приложениях разработчики могут забыть сделать это, что может привести к ошибкам.

Мне очень нравится ответ Питера, потому что вы можете получить доступ к закрытым членам, используя контекст (это). Но одна вещь, которая беспокоит меня довольно сильно, это то, что доступ к закрытым членам сделан в o (n) сложности. Действительно, нахождение индекса объекта в массиве - это линейный алгоритм. Предположим, вы хотите использовать этот шаблон для объекта, который создается 10000 раз. Затем вы можете перебирать 10000 экземпляров каждый раз, когда хотите получить доступ к приватному члену.

Чтобы получить доступ к частным магазинам со сложностью o (1), нет другого способа, кроме как использовать направляющие. Но чтобы не беспокоиться об объявлении и увеличении guid и чтобы использовать контекст для доступа к частному хранилищу, я изменил шаблон фабрики Peters следующим образом:

createPrivateStore = function () {
var privates = {}, guid = 0;

return function (instance) {
    if (instance.__ajxguid__ === undefined) {
        // Lazily associate instance with a new private object
        var private_obj = {};
        instance.__ajxguid__ = ++guid;
        privates[instance.__ajxguid__] = private_obj;
        return private_obj;
    }

    return privates[instance.__ajxguid__];
}

}

Хитрость заключается в том, чтобы учесть, что объекты, у которых нет свойства ajxguid , еще не обработаны. Действительно, можно вручную установить свойство перед первым доступом к магазину, но я думаю, что волшебного решения не существует.

0 голосов
/ 16 января 2014

Я создал новую библиотеку для включения частных методов в цепочке прототипов. https://github.com/TremayneChrist/ProtectJS

Пример:

var MyObject = (function () {

  // Create the object
  function MyObject() {}

  // Add methods to the prototype
  MyObject.prototype = {

    // This is our public method
    public: function () {
      console.log('PUBLIC method has been called');
    },

    // This is our private method, using (_)
    _private: function () {
      console.log('PRIVATE method has been called');
    }
  }

  return protect(MyObject);

})();

// Create an instance of the object
var mo = new MyObject();

// Call its methods
mo.public(); // Pass
mo._private(); // Fail
...