Доступ до змінних приватних членів від визначених прототипом функцій


187

Чи є спосіб зробити "приватні" змінні (ті, які визначені в конструкторі), доступними для визначених прототипом методів?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

Це працює:

t.nonProtoHello()

Але це не так:

t.prototypeHello()

Я звик визначати свої методи всередині конструктора, але я відступаю від цього з кількох причин.



14
@ecampver, За винятком цього запитали на 2 роки раніше ....
Pacerier

Відповіді:


191

Ні, немає ніякого способу зробити це. Це, по суті, полягало б у зворотному обході.

Методи, визначені у конструкторі, мають доступ до приватних змінних, оскільки всі функції мають доступ до області, у якій вони були визначені.

Методи, визначені на прототипі, не визначаються в межах конструктора і не матимуть доступу до локальних змінних конструктора.

Ви все ще можете мати приватні змінні, але якщо ви хочете, щоб методи, визначені в прототипі, мали доступ до них, вам слід визначити getters та setters на thisоб’єкті, до яких методи прототипу (разом із усім іншим) матимуть доступ. Наприклад:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };

14
"скоринг у зворотному порядку" - це функція C ++ із ключовим словом "друг" По суті, будь-яка функція повинна визначати прототип як свого друга. На жаль, ця концепція є C ++, а не JS :(
TWiStErRob

1
Я хотів би додати цю публікацію до початку мого списку улюблених та зберегти її там.
Донато

2
Я не бачу сенсу в цьому - ви лише додаєте шар абстракції, який нічого не робить. Ви можете просто зробити secretвласність this. JavaScript просто не підтримує приватні змінні з прототипами, оскільки прототипи прив’язані до контексту сайту виклику, а не до "створення сайту".
nicodemus13

1
Чому б не зробити це person.getSecret()тоді?
Fahmi

1
Чому для цього є стільки результатів? Це не робить змінну приватною. Як було сказано вище, використання person.getSecret () дозволить отримати доступ до цієї приватної змінної з будь-якого місця.
alexr101

64

Оновлення: із ES6 є кращий спосіб:

Якщо коротко розповісти, ви можете використовувати нове Symbolдля створення приватних полів.
Ось чудовий опис: https://curiosity-driven.org/private-properties-in-javascript

Приклад:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

Для всіх сучасних браузерів з ES5:

Ви можете використовувати просто закривання

Найпростіший спосіб побудови об'єктів - це взагалі уникнути прототипічного успадкування. Просто визначте приватні змінні та публічні функції під час закриття, і всі відкриті методи матимуть приватний доступ до змінних.

Або ви можете використовувати просто прототипи

У JavaScript прототипічне успадкування - це насамперед оптимізація . Це дозволяє декільком екземплярам ділитися прототипними методами, а не кожен екземпляр, який має свої методи.
Недолік - thisце єдиний , що відрізняється щоразу, коли викликається функція прототипу.
Тому будь-які приватні поля повинні бути доступними через this, а це означає, що вони будуть публічними. Тож ми просто дотримуємось умовних назв _privateполів.

Не турбуйте змішування закриттів з прототипами

я думаю, що ви не слід змішувати змінні закриття із методами прототипу. Вам слід використовувати те чи інше.

Коли ви використовуєте закриття для доступу до приватної змінної, методи прототипу не можуть отримати доступ до змінної. Отже, вам доведеться виставити закриття наthis , а це означає, що ви так чи інакше викриваєте його публічно. Такого підходу можна отримати дуже мало.

Який я обираю?

Для дійсно простих об'єктів просто використовуйте звичайний об’єкт із закриттями.

Якщо вам потрібна прототипічна спадщина - для успадкування, продуктивності тощо - тоді дотримуйтесь конвенції про іменування "_private" і не турбуйтеся про закриття.

Я не розумію, чому розробники JS дуже намагаються зробити поля справді приватними.


4
На жаль, _privateконвенція про іменування все ще є найкращим рішенням, якщо ви хочете скористатися прототиповим успадкуванням.
розчавити

1
ES6 матиме нову концепцію, Symbolяка є прекрасним способом створення приватних полів. Ось велике пояснення: curiosity-driven.org/private-properties-in-javascript
Скотт Риппи

1
Ні, ви можете тримати Symbolзакриття, яке охоплює весь ваш клас. Таким чином, всі методи-прототипи можуть використовувати Symbol, але він ніколи не піддається впливу класу.
Скотт Ріппі

2
У статті ви пов'язані каже : « Символи схожі на приватні імена , але - в відміну від приватних імен - вони не забезпечують справжню конфіденційність . ». Ефективно, якщо у вас є примірник, ви можете отримати його символи Object.getOwnPropertySymbols. Тож це лише конфіденційність через невідомість.
Оріол

2
@Oriol Так, конфіденційність відбувається через велику неясність. Ще можна повторити символи, і ви зрозумієте призначення символу через toString. Це не відрізняється від Java або C # ... приватних членів, як і раніше, доступні через рефлексію, але зазвичай сильно затьмарені. Що все підсилює мою остаточну точку: "Я не розумію, чому розробники JS так намагаються зробити поля справді приватними".
Скотт Ріппі

31

Коли я прочитав це, це звучало як важкий виклик, тому я вирішив придумати спосіб. Те, що я придумав, це CRAAAAZY але він повністю працює.

По-перше, я спробував визначити клас у безпосередній функції, щоб ви мали доступ до деяких приватних властивостей цієї функції. Це працює і дозволяє отримати деякі приватні дані, однак, якщо ви спробуєте встановити приватні дані, незабаром ви побачите, що всі об’єкти матимуть однакове значення.

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

Існує маса випадків, коли це було б адекватно, якби ви хотіли мати постійні значення, наприклад, імена подій, які поділяються між екземплярами. Але по суті вони діють як приватні статичні змінні.

Якщо вам абсолютно потрібен доступ до змінних у приватному просторі імен з ваших методів, визначених у прототипі, ви можете спробувати цю модель.

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

Я хотів би отримати відгуки всіх, хто бачить помилку з таким способом.


4
Я думаю, одна з проблем може полягати в тому, що будь-який екземпляр може отримати доступ до будь-яких інших приватних варіантів, використовуючи інший ідентифікатор екземпляра. Не обов’язково погана річ ...
Мімс Х. Райт

15
Ви переосмислюєте функції прототипу під час кожного виклику конструктора
Lu4,

10
@ Lu4 Я не впевнений, що це правда. Конструктор повертається зсередини закриття; єдиний раз, коли визначені функції прототипу, це перший раз, коли негайно викликається вираз функції. Питання конфіденційності, про які згадувалося вище, мені здається непоганим (на перший погляд).
guypursey

1
@ MimsH.Wright іншими мовами дозволяють отримати доступ до інших об'єктів приватними особами одного класу , але лише тоді, коли ви посилаєтесь на них. Щоб дозволити це, ви можете приховати приватних осіб за функцією, яка приймає вказівник об'єктів як ключ (як призначений для ідентифікатора). Таким чином, у вас є лише доступ до приватних даних об’єктів, про які ви знаєте, що більше відповідає масштабуванню інших мов. Однак ця реалізація проливає світло на глибшу проблему. Приватні об'єкти ніколи не будуть збирати сміття, поки функція Constructor не буде.
Томас Надін

3
Хочу зазначити, що iдодано до всіх примірників. Тож він не є повністю "прозорим", і iйого все одно можна підробити.
Скотт Ріппі

18

див . сторінку Дага Крокфорда на цьому . Ви повинні це робити опосередковано з тим, що може отримати доступ до області приватної змінної.

ще один приклад:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

випадок використання:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42

47
Цей приклад здається жахливою практикою. Сенс використання методів прототипу полягає в тому, що вам не доведеться створювати новий для кожного примірника. Ти все одно робиш це. Для кожного методу ви створюєте інший.
Кір

2
@ArmedMonkey Концепція виглядає здоровою, але погодився, це поганий приклад, тому що показані прототипи функції є тривіальними. Якщо функції прототипу були набагато довшими функціями, що вимагають простого доступу / встановлення доступу до "приватних" змінних, це мало б сенс.
млинець

9
Чому навіть турбуватися викривати _setчерез set? Чому б просто не назвати це setдля початку?
Скотт Ріппі

15

Я вважаю, що, мабуть, було б хорошою ідеєю описати "мати призначення прототипу в конструкторі" як антидіаграма Javascript. Подумай над цим. Це занадто ризиковано.

Те, що ви насправді робите там для створення другого об'єкта (тобто b), переосмислює цю функцію прототипу для всіх об'єктів, які використовують цей прототип. Це ефективно скине значення для об'єкта a у вашому прикладі. Він буде працювати, якщо ви хочете отримати спільну змінну і якщо вам трапиться створити всі екземпляри об'єкта вперед, але це здається занадто ризикованим.

Я знайшов помилку в деяких Javascript, над яким я нещодавно працював, що було зумовлено саме цією анти-схемою. Він намагався встановити обробник перетягування для конкретного об'єкта, який створюється, але натомість робив це для всіх випадків. Не добре.

Рішення Дуга Крокфорда - найкраще.


10

@Kai

Це не спрацює. Якщо ти зробиш

var t2 = new TestClass();

тоді t2.prototypeHello буде доступ до приватного розділу t.

@AnglesCrimes

Зразок коду працює чудово, але він фактично створює "статичний" приватний член, яким поділяються всі екземпляри. Це може бути не рішенням, яке шукали морганкоди.

Поки що я не знайшов простого та чистого способу зробити це без введення приватного хешу та додаткових функцій очищення. Функція приватного члена може бути симульована певною мірою:

(function() {
    function Foo() { ... }
    Foo.prototype.bar = function() {
       privateFoo.call(this, blah);
    };
    function privateFoo(blah) { 
        // scoped to the instance by passing this to call 
    }

    window.Foo = Foo;
}());

Ясно зрозумів свої моменти, але чи можете ви, будь ласка, пояснити, що намагається зробити ваш фрагмент коду?
Vishwanath

privateFooє повністю приватним і, таким чином, невидимим при отриманні new Foo(). Тут bar()є лише публічний метод, до якого є доступ privateFoo. Ви можете використовувати один і той же механізм для простих змінних і об'єктів, однак вам потрібно завжди мати на увазі, що privatesвони насправді статичні і будуть спільними для всіх створених вами об'єктів.
Фільцен

6

Так, це можливо. Модель дизайну PPF це просто вирішує.

PPF розшифровується як функція приватного прототипу. Основний ППФ вирішує ці питання:

  1. Функції прототипу отримують доступ до приватних даних екземпляра.
  2. Функції прототипу можна зробити приватними.

Для першого просто:

  1. Помістіть усі змінні приватних екземплярів, до яких потрібно отримати доступ, від прототипних функцій всередині окремого контейнера даних та
  2. Передати посилання на контейнер даних для всіх функцій прототипу як параметр.

Це так просто. Наприклад:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

Прочитайте повну історію тут:

Шаблон дизайну PPF


4
Відповідь, що стосується лише посилань, як правило, надихається на SO. Покажіть, будь ласка, приклад.
Corey Adler

У статті є приклади всередині, тому дивіться там
Едвард

5
Що ж станеться, якщо в якийсь момент пізніше на цьому веб-сайті вийде? Як тоді хтось повинен бачити приклад? Політика діє так, що будь-що цінне у посиланні може зберігатися тут, і не потрібно покладатися на веб-сайт, що це не під нашим контролем.
Корі Адлер

3
@Edward, ваше посилання цікаве прочитання! Однак мені здається, що головна причина доступу до приватних даних за допомогою прототипних функцій полягає в тому, щоб запобігти тому, щоб кожен об'єкт витрачав пам’ять з однаковими загальнодоступними функціями. Описаний вами метод не вирішує цю проблему, оскільки для загальнодоступного використання прототипічну функцію потрібно загорнути в звичайну загальну функцію. Я думаю, що модель може бути корисною для збереження пам’яті, якщо у вас багато ppf-файлів, об’єднаних в одну загальнодоступну функцію. Чи використовуєте ви їх для чогось іншого?
Їдальня Філософ

@DiningPhilosofer, дякую за те, що ви оцінили мою статтю. Так, ви праві, ми все ще використовуємо функції екземпляра. Але ідея полягає в тому, щоб вони були максимально легкими, просто передзвонивши колегам з ППФ, які виконують важку роботу. Врешті-решт всі екземпляри викликають одні й ті ж PPF (звичайно через обгортки), тому можливо очікувати певного збереження пам'яті. Питання - скільки. Я очікую істотного заощадження.
Едвард

5

Дійсно досягти цього можна за допомогою перевірки Accessor :

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

Цей приклад походить з мого допису про прототипові функції та приватні дані та пояснюється там більш докладно.


1
Ця відповідь занадто "розумна", щоб бути корисною, але мені подобається відповідь на використання змінної, пов'язаної з IFFE, як таємного рукостискання. Ця реалізація використовує занадто багато закриттів, щоб бути корисними; сенс використання прототипу визначених методів полягає у запобіганні побудови нових об'єктів функцій для кожного методу на кожному об'єкті.
greg.kindel

Цей підхід використовує секретний ключ, щоб визначити, яким методам прототипу довіряти, а яким ні. Однак саме той екземпляр перевіряє ключ, тому ключ повинен бути надісланий екземпляру. Але тоді ненадійний код може викликати довірений метод у підробленому екземплярі, який би вкрав ключ. І за допомогою цього ключа створіть нові методи, яким реальні випадки вважатимуться довіреними. Тож це лише конфіденційність через невідомість.
Оріол

4

У поточному JavaScript я досить впевнений, що існує один і єдиний спосіб мати приватний стан , доступний через прототипні функції, не додаючи нічого публічного вthis . Відповідь - використовувати шаблон "слабкої карти".

Підсумовуючи це: Personклас має єдину слабку карту, де ключі є екземплярами Person, а значення - це звичайні об'єкти, які використовуються для приватного зберігання.

Ось повністю функціональний приклад: (грати на http://jsfiddle.net/ScottRippey/BLNVr/ )

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.push(instance);
            values.push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

Як я вже сказав, це дійсно єдиний спосіб досягти всіх 3 частин.

Однак є два застереження. По-перше, це коштує продуктивності - кожен раз, коли ви отримуєте доступ до приватних даних, це O(n)операція, де nкількість примірників. Тож вам не захочеться цього робити, якщо у вас є велика кількість примірників. По-друге, коли ви закінчите з екземпляром, ви повинні зателефонуватиdestroy ; інакше екземпляр і дані не будуть зібрані сміття, і ви закінчитеся з витоком пам'яті.

І тому моя оригінальна відповідь "Ти не повинен" - це те, чого я хотів би дотримуватися.


Якщо ви явно не знищите екземпляр Person до того, як він вийде за межі, чи слабка карта не посилається на нього, щоб у вас витік пам'яті? Я придумав шаблон для захищеного, оскільки інші екземпляри Person можуть отримати доступ до змінної, а ті, що успадковуються від Person, можуть. Щойно викрили це, тому не впевнені, чи є якісь інші переваги, крім додаткової обробки (виглядає не так, як доступ до приватних осіб) stackoverflow.com/a/21800194/1641941 Повернення приватного / захищеного об'єкта - це біль, оскільки виклик коду може потім вимкнути вашу приватну / захищену.
HMR

2
@HMR Так, ви повинні явно знищити приватні дані. Я збираюся додати цей застереження до своєї відповіді.
Скотт Ріппей

3

Існує простіший спосіб, використовуючи використання bindта callметоди.

Встановлюючи приватні змінні об'єкту, ви можете використовувати область його використання.

Приклад

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

Цей метод не позбавлений недоліків. Оскільки контекст діапазону фактично перекрито, ви не маєте доступу поза _privateоб'єктом. Однак не можна, хоча все-таки надати доступ до області об’єкта екземпляра. Ви можете передавати в контексті об'єкта ( this) як другий аргумент до bindабо callвсе ще мати доступ до його загальнодоступних значень у функції прототипу.

Доступ до суспільних цінностей

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)

2
Чому б хтось створив копію методу прототипу, а не просто створити інстальований метод?
розчавити

3

Спробуй це!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();

1
Це покладається на те caller, що розширення, що залежить від реалізації, не дозволено в суворому режимі.
Оріол

1

Ось що я придумав.

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

Основна проблема цієї реалізації полягає в тому, що вона переосмислює прототипи для кожної інстанції.


Цікаво, що мені дуже подобається спроба, і я думав про те саме, але ти маєш рацію, що переосмислення функції прототипу для кожної інстанції є досить великим обмеженням. Це не лише тому, що це витрачаються на цикл процесора, але й тому, що якщо ви коли-небудь будете змінювати прототип пізніше, він повернеться до початкового стану, як визначено в конструкторі, при наступній інстанції: /
Niko Bellic

1
Це не тільки переосмислені прототипи, але визначає новий конструктор для кожного екземпляра. Тож "екземпляри" вже не є екземплярами одного класу.
Оріол

1

Існує дуже простий спосіб зробити це

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

Прототипи JavaScript - золоті.


2
Я вважаю, що краще не використовувати прототип у функції конструктора, оскільки він створюватиме нову функцію кожного разу, коли створюється новий екземпляр.
whamsicore

@whamsicore Так, але в цьому випадку це важливо, оскільки для кожного окремого об'єкта ми маємо організувати спільне закриття. Ось чому визначення функцій знаходяться всередині конструктора, і ми мусимо посилатися на те SharedPrivate.prototype, що this.constructor.prototypeпереосмислювати getP та setP кілька разів не варто ...
Зменшити

1

Я спізнююсь на вечірку, але думаю, що можу внести свій внесок. Ось, перевірте це:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

Я називаю цей метод аксесуаром . Основна ідея полягає в тому, що у нас є закриття , ключ всередині закриття, і ми створюємо приватний об'єкт (у конструкторі), до якого можна отримати доступ лише за наявності ключа .

Якщо вас цікавить, ви можете прочитати більше про це в моїй статті . Використовуючи цей метод, ви можете створювати для кожного об'єкта властивості, до яких не можна отримати доступ за межами закриття. Тому ви можете використовувати їх у конструкторі чи прототипі, але ніде більше. Я не бачив цього методу ніде, але думаю, що він справді потужний.


0

Ви не можете розмістити змінні у більшій області?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();

4
Потім змінні поділяються між усіма екземплярами MyClass.
розчавити

0

Ви також можете спробувати додати метод не безпосередньо на прототипі, а на такій функції конструктора:

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!

0

Ось що я придумав, намагаючись знайти найбільш просте рішення цієї проблеми, можливо, хтось може бути корисним. Я новачок у JavaScript, тому з кодом цілком можуть виникнути проблеми.

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();

0

Сьогодні я зіткнувся з точно таким же питанням, і після того, як розробив відповідь першого класу Скотта Ріппея, я придумав дуже просте рішення (IMHO), яке одночасно сумісне з ES5 і ефективне, воно також безпечно для зіткнення імен (використання _private видається небезпечним) .

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Тестовано з ringojs та nodejs. Я нетерпляю прочитати вашу думку.


Ось посилання: перевірте розділ «На крок ближче». philipwalton.com/articles/…
jimasun

0
var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

Як це? Використання приватного доступу. Тільки дозволяє отримати змінні, хоча їх не встановлювати, залежить від випадку використання.


Це не дає відповіді на питання корисним чином. чому ти вважаєш, що це відповідь? як це працює? Просто кажучи комусь змінити код без будь-якого контексту чи сенсу, це не допомагає їм зрозуміти, що вони зробили не так.
GrumpyCrouton

Він хотів спосіб отримати доступ до прихованих приватних змінних класу через прототипи, не створюючи цієї прихованої змінної у кожному окремому екземплярі класу. Вищевказаний код є прикладом способу виконання такого. Як це не відповідь на питання?
dylan0150

Я не сказав, що це не відповідь на питання. Я сказав, що це не корисна відповідь, бо це нікому не допомагає вчитися. Ви повинні пояснити свій код, чому він працює, чому це правильний спосіб зробити це. Якби я був автором запитання, я б не прийняв вашу відповідь, оскільки це не заохочує навчання, не вчить мене, що я роблю неправильно, або що робить даний код або як він працює.
GrumpyCrouton

0

У мене є одне рішення, але я не впевнений, що це без вад.

Щоб він працював, ви повинні використовувати таку структуру:

  1. Використовуйте 1 приватний об'єкт, який містить усі приватні змінні.
  2. Використовуйте функцію 1 екземпляра.
  3. Застосовуйте закриття до конструктора та всіх функцій прототипу.
  4. Будь-який створений екземпляр робиться поза визначеним закриттям.

Ось код:

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

Як це працює, це те, що він забезпечує функцію екземпляра "this.getPrivateFields" для доступу до об'єкта приватних змінних "privateFields", але ця функція повертає лише об'єкт "privateFields" всередині визначеного основного закриття (також функції прототипу, використовуючи "this.getPrivateFields" "потрібно визначити всередині цього закриття).

Хеш, створений під час виконання, і важко його здогадатися, використовується як параметри, щоб переконатися, що навіть якщо "getPrivateFields" викликається за межами області закриття, не поверне об'єкт "privateFields".

Недолік - те, що ми не можемо розширити TestClass за допомогою додаткових функцій прототипу поза закриттям.

Ось кілька тестових кодів:

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

EDIT: Використовуючи цей метод, можна також «визначити» приватні функції.

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};

0

Сьогодні грав із цим, і це було єдине рішення, яке я міг знайти, не використовуючи символи. Найкраще в цьому, що це насправді все може бути повністю приватним.

Рішення ґрунтується на завантажувальному модулі для домашнього вирощування, який в основному стає посередником для кешу приватного зберігання (використовуючи слабку карту).

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')

0

Я знаю, що пройшло вже більше десяти років з того часу, як про це запитали, але я просто п ятий раз в житті програміста виклав свої думки з цього приводу і знайшов можливе рішення, яке не знаю, чи мені все ще подобається . Я раніше не бачив цієї методології задокументованою, тому буду називати її "шаблоном приватного / публічного долара" або " $ / $" .

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

Концепція використовує функцію ClassDefinition, яка повертає функцію Constructor, яка повертає об'єкт Interface . Єдиний метод інтерфейсу - це те, $що отримує nameаргумент для виклику відповідної функції в об'єкті конструктора, а додаткові аргументи, передані після name, передаються у виклику.

Глобально визначена допоміжна функція ClassValuesзберігає всі поля в якості об'єкта по мірі необхідності. Він визначає _$функцію доступу до них name. З цього випливає короткий шаблон отримання / встановлення, тож якщо valueвін буде переданий, він буде використовуватися як нове значення змінної.

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

Глобально визначена функція Interfaceприймає об'єкт і Valuesоб'єкт для повернення знака _interfaceоднієї єдиної функції, $яка вивчає, objщоб знайти функцію, названу за параметром, nameі викликає її valuesяк об'єкт, що охоплює область . Додаткові аргументи, передані до, $будуть передані при виклику функції.

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

У наведеному нижче зразку ClassXприсвоюється результат ClassDefinition, який є Constructorфункцією. Constructorможе отримати будь-яку кількість аргументів. Interfaceце те, що зовнішній код отримує після виклику конструктора.

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

Немає сенсу мати непрообразовані функції Constructor, хоча ви можете їх визначити в тілі функції конструктора. Усі функції називаються за схемою публічного долара this.$("functionName"[, param1[, param2 ...]]) . До приватних цінностей звертається за схемою приватного долара this._$("valueName"[, replacingValue]); . Оскільки Interfaceне має визначення для _$, до значень не можуть бути доступні зовнішні об'єкти. Оскільки кожен прототипний орган функції thisвстановлений на valuesоб'єкт у функції $, ви отримаєте винятки, якщо ви будете викликати безпосередньо функції конструктора братів; також слід дотримуватися шаблону _ $ / $ у прототипованих функціональних органах. Нижче використання зразка.

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

І консольний вихід.

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

_ $ / $ Шаблон забезпечує повну конфіденційність значень в повністю прототипів класів. Я не знаю, чи коли-небудь я буду користуватися цим, ні якщо у нього є недоліки, але ей, це була гарна загадка!


0

Слабкі карти ES6

За допомогою простого шаблону, заснованого на ES6, WeakMaps можна отримати змінні приватних членів, доступні з функцій прототипу .

Примітка. Використання WeakMaps гарантує безпеку від витоку пам’яті , дозволяючи Garbage Collector ідентифікувати та видаляти невикористані екземпляри.

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

Більш детальне пояснення цієї закономірності можна знайти тут


-1

У своєму коді потрібно змінити 3 речі:

  1. Замініть var privateField = "hello"на this.privateField = "hello".
  2. У прототипі замінити privateFieldна this.privateField.
  3. У непрототипі також замінити privateFieldна this.privateField.

Кінцевим кодом буде такий:

TestClass = function(){
    this.privateField = "hello";
    this.nonProtoHello = function(){alert(this.privateField)};
}

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()

this.privateFieldне було б приватним полем. він доступний ззовні:t.privateField
В. Рубінетті

-2

Ви можете використовувати призначення прототипу в межах визначення конструктора.

Змінна буде видима методом доданого прототипу, але всі екземпляри функцій отримають доступ до тієї ж змінної SHARED.

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

Я сподіваюся, що це може бути корисним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.