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

187

Есть ли способ сделать «закрытые» переменные (определенные в конструкторе) доступными для методов, определенных прототипами?

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

Это работает:

t.nonProtoHello()

Но это не так:

t.prototypeHello()

Я привык определять свои методы внутри конструктора, но ухожу от этого по нескольким причинам.

morgancodes
источник
14
@ecampver, кроме этого спросили 2 года назад ....
Pacerier

Ответы:

191

Нет, это невозможно сделать. По сути, это будет обзор в обратном направлении.

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

Методы, определенные в прототипе, не определены в области конструктора и не будут иметь доступа к локальным переменным конструктора.

Вы все еще можете иметь закрытые переменные, но если вы хотите, чтобы методы, определенные в прототипе, имели доступ к ним, вы должны определить 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()то?
Фахми
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. Право на другие языки разрешает доступ к другим объектам, принадлежащим к тому же классу , но только при наличии ссылки на них. Чтобы учесть это, вы можете скрыть ряды за функцией, которая принимает указатель объектов в качестве ключа (в отличие от идентификатора). Таким образом, у вас есть доступ только к частным данным об объектах, о которых вы знаете, что больше соответствует области видимости на других языках. Однако эта реализация проливает свет на более глубокую проблему с этим. Частные объекты никогда не будут собираться мусором до тех пор, пока не будет создана функция 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 Концепция выглядит здраво, но согласился, что это плохой пример, потому что показанные функции прототипа тривиальны. Если бы функции-прототипы были гораздо более длинными функциями, требующими простого доступа get / set к «приватным» переменным, это имело бы смысл.
блин
9
Зачем вообще пытаться разоблачить _setчерез set? Почему бы просто не назвать это setдля начала?
Скотт Риппи
15

Я полагаю, что было бы неплохо описать «назначение прототипа в конструкторе» как анти-паттерн Javascript. Подумай об этом. Это слишком рискованно.

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

В каком-то Javascript, над которым я недавно работал, я обнаружил ошибку, вызванную именно этим анти-паттерном. Он пытался установить обработчик перетаскивания для конкретного создаваемого объекта, но вместо этого делал это для всех экземпляров. Не хорошо.

Решение Дуга Крокфорда - лучшее.

Лэнс Юинг
источник
10

@Kai

Это не сработает. Если вы делаете

var t2 = new TestClass();

тогда t2.prototypeHelloбудет доступ к личному разделу т.

@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;
}());
Тим
источник
Ясно понял ваши идеи, но не могли бы вы объяснить, что пытается сделать ваш фрагмент кода?
Вишванат
privateFooявляется полностью приватным и, следовательно, невидимым при получении new Foo(). Только bar()публичный метод, который имеет доступ к privateFoo. Вы можете использовать тот же механизм для простых переменных и объектов, однако вам всегда следует помнить, что privatesони на самом деле являются статическими и будут общими для всех создаваемых вами объектов.
Филзен
6

Да, это возможно. Шаблон дизайна PPF только решает это.

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 Design Pattern

Эдвард
источник
4
Ответ только на ссылку обычно не одобряется на SO. Пожалуйста, покажите пример.
Кори Адлер
В статье есть примеры внутри, так что, пожалуйста, смотрите там
Эдвард
5
Однако что произойдет, если в какой-то момент этот сайт отключится? Как кто-то должен видеть пример тогда? Политика действует таким образом, что здесь можно сохранить все, что имеет значение в ссылке, и не нужно полагаться на веб-сайт, который не находится под нашим контролем.
Кори Адлер
3
@ Эдвард, твоя ссылка интересная для чтения! Однако мне кажется, что основная причина доступа к частным данным с использованием прототипных функций заключается в том, чтобы предотвратить то, что каждый объект тратит память на идентичные публичные функции. Метод, который вы описываете, не решает эту проблему, так как для публичного использования прототипная функция должна быть обернута в обычную публичную функцию. Я думаю, что шаблон может быть полезен для экономии памяти, если у вас есть много файлов ppf, которые объединены в одну публичную функцию. Вы используете их для чего-то еще?
Обедающий Философ
@DiningPhilosofer, спасибо за оценку моей статьи. Да, вы правы, мы все еще используем функции экземпляра. Но идея состоит в том, чтобы сделать их максимально легкими, просто перезвонив своим коллегам из PPF, которые выполняют всю тяжелую работу. В конечном итоге все экземпляры вызывают одни и те же PPF (конечно, через оболочки), поэтому можно ожидать определенной экономии памяти. Вопрос в том, сколько. Я ожидаю существенной экономии.
Эдвард
5

Вы можете достичь этого, используя Accessor Verification :

(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
Этот подход использует секретный ключ, чтобы определить, какие методы-прототипы являются доверенными, а какие - нет. Однако именно экземпляр проверяет ключ, поэтому ключ должен быть отправлен в экземпляр. Но затем ненадежный код может вызвать доверенный метод на поддельном экземпляре, который украл бы ключ. И с этим ключом создайте новые методы, которые в реальных случаях будут считаться доверенными. Так что это только конфиденциальность по неизвестности.
Oriol
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];
        }
    };
}

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

Однако есть две оговорки. Во-первых, это снижает производительность - каждый раз, когда вы получаете доступ к частным данным, это 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объекта. Тем не менее, все же невозможно предоставить доступ к области видимости объекта экземпляра. Вы можете передать context ( this) объекта в качестве второго аргумента bindили callпо-прежнему иметь доступ к его открытым значениям в функции prototype.

Доступ к общественным ценностям

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)
thgaskell
источник
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();
AlanNLohse
источник
1
Это зависит от того caller, что расширение, зависящее от реализации, не разрешено в строгом режиме.
Oriol
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

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

Xeltor
источник
Интересно, что мне действительно нравится эта попытка, и я думал об одном и том же, но вы правы в том, что переопределение функции-прототипа в каждом случае является довольно большим ограничением. Это не только потому, что он тратит впустую циклы процессора, но и потому, что если вы когда-нибудь измените прототип позже, он получит «сброс» обратно в исходное состояние, как определено в конструкторе при следующем создании: /
Нико Беллик
1
Это не только переопределенные прототипы, но и новый конструктор для каждого экземпляра. Таким образом, «экземпляры» больше не являются экземплярами одного и того же класса.
Oriol
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 золотые.

Redu
источник
2
Я считаю, что лучше не использовать прототип в функции конструктора, поскольку он будет создавать новую функцию каждый раз, когда создается новый экземпляр.
whamsicore
@whamsicore Да, но в этом случае это важно, поскольку для каждого экземпляра объекта мы должны организовать общее закрытие. Вот причина , почему определение функции находится внутри конструктора , и мы должны относиться к , SharedPrivate.prototypeкак this.constructor.prototypeэто не большая сделка по - новому getP и SEtP несколько раз ...
Redu
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

Я называю этот метод шаблоном доступа . Основная идея заключается в том, что у нас есть замыкание , ключ внутри замыкания, и мы создаем частный объект (в конструкторе), к которому можно получить доступ, только если у вас есть ключ .

Если вы заинтересованы, вы можете прочитать больше об этом в моей статье . Используя этот метод, вы можете создавать для каждого объекта свойства, к которым нельзя получить доступ за пределами замыкания. Следовательно, вы можете использовать их в конструкторе или прототипе, но не где-либо еще. Я нигде не видел, чтобы этот метод использовался, но я думаю, что он действительно мощный.

guitarino
источник
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();
V.Mihaly4
источник
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. Я хочу прочитать ваше мнение.

alexgirao
источник
Вот ссылка: проверьте раздел «На шаг ближе». 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()

Как это? Использование частного доступа. Только позволяет получить переменные, но не устанавливать их, зависит от варианта использования.

dylan0150
источник
Это не ответ на вопрос полезным способом. почему вы считаете, что это ответ? как это работает? Простое указание кому-либо изменить свой код без какого-либо контекста или значения не поможет ему понять, что он сделал неправильно.
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);

РЕДАКТИРОВАТЬ: Используя этот метод, также можно «определить» частные функции.

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

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};
Яннес Ботис
источник
0

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

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

   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')
Eladian
источник
0

Я знаю, что прошло более 1 десятилетия с тех пор, как об этом спросили, но я просто размышлял над этим n-й раз в своей жизни программиста и нашел возможное решение, которое мне пока неизвестно, нравится ли мне еще , Я не видел этой методологии, документированной ранее, поэтому я назову ее «паттерн частного / публичного доллара» или паттерн _ $ / $ .

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. Это следует за коротким шаблоном get / set, поэтому, если 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объект в функции $, вы получите исключения, если вызовете функции-братья Constructor напрямую; в _ $ / $ шаблон должен сопровождаться в функции органов прототип тоже. Ниже пример использования.

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

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

JPortillo
источник
0

ES6 WeakMaps

Используя простой шаблон, основанный на ES6, WeakMaps позволяет получить закрытые переменные-члены, доступные из функций прототипа .

Примечание. Использование WeakMaps гарантирует безопасность от утечек памяти , позволяя сборщику мусора идентифицировать и отбрасывать неиспользуемые экземпляры.

// 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;
}());

Более подробное объяснение этого паттерна можно найти здесь

colxi
источник
-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()
A-Sharabiani
источник
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();

Я надеюсь, что это может быть полезно.

AngelsCrimes
источник