Есть ли способ сделать «частные» переменные (определенные в конструкторе) доступными для методов, определенных прототипом?
TestClass = function(){
var privateField = "hello";
this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};
Это работает:
t.nonProtoHello()
Но это не так:
t.prototypeHello()
Я привык определять свои методы внутри конструктора, но я отхожу от этого по нескольким причинам.
@ecampver, кроме этого спрашивали 2 года ранее ....



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Нет, нет возможности это сделать. По сути, это было бы наоборот.
Методы, определенные внутри конструктора, имеют доступ к закрытым переменным, потому что все функции имеют доступ к области, в которой они были определены.
Методы, определенные в прототипе, не определены в рамках конструктора и не будут иметь доступа к локальным переменным конструктора.
У вас все еще могут быть частные переменные, но если вы хотите, чтобы методы, определенные в прототипе, имели к ним доступ, вы должны определить геттеры и сеттеры в объекте 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()); };
"scoping in reverse" - это функция C++ с ключевым словом "friend". По сути, любая функция должна определять свой прототип как друга. К сожалению, это концепция C++, а не JS :(
Также важно знать, что если «секрет» является объектом (например, массивом), метод получения будет возвращать ссылку вместо значения переменной. Это означает, что person.getSecret (). Push (1) будет перемещаться в локальную переменную.
Я хотел бы добавить этот пост в верхнюю часть моего списка избранного и оставить его там.
Я не вижу в этом смысла - вы просто добавляете слой абстракции, который ничего не делает. Вы также можете просто сделать secret свойством this. JavaScript просто не поддерживает частные переменные с прототипами, поскольку прототипы привязаны к контексту call-site, а не к контексту «create-site».
Почему бы тогда просто не сделать person.getSecret()?
Почему так много положительных отзывов? Это не делает переменную частной. Как упоминалось выше, использование person.getSecret () позволит вам получить доступ к этой частной переменной из любого места.
см. Страница Дуга Крокфорда об этом. Вы должны делать это косвенно с помощью чего-то, что может получить доступ к области видимости частной переменной.
другой пример:
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
Этот пример кажется ужасной практикой. Смысл использования методов прототипа заключается в том, что вам не нужно создавать новый для каждого экземпляра. Вы все равно это делаете. Для каждого метода вы создаете еще один.
@ArmedMonkey Концепция выглядит разумной, но согласился, что это плохой пример, потому что показанные функции прототипа тривиальны. Если бы функции-прототипы были гораздо более длинными функциями, требующими простого доступа к «частным» переменным, это имело бы смысл.
Зачем вообще беспокоиться о том, чтобы выставлять _set через set? Почему бы просто не назвать его для начала set?
Вы можете использовать назначение прототипа в определении конструктора.
Переменная будет видна методу, добавленному в прототип, но все экземпляры функций будут обращаться к одной и той же переменной 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();
Надеюсь, это может быть полезно.
Я полагаю, что, вероятно, было бы хорошей идеей описать «назначение прототипа в конструкторе» как антипаттерн Javascript. Подумай об этом. Это слишком рискованно.
То, что вы на самом деле делаете там при создании второго объекта (т. Е. B), - это переопределение этой функции-прототипа для всех объектов, которые используют этот прототип. Это эффективно сбросит значение для объекта a в вашем примере. Это сработает, если вам нужна общая переменная и если вам случится создать все экземпляры объекта заранее, но это кажется слишком рискованным.
Я обнаружил ошибку в каком-то Javascript, над которым я недавно работал, из-за этого точного антипаттерна. Он пытался установить обработчик перетаскивания для конкретного создаваемого объекта, но вместо этого делал это для всех экземпляров. Нехорошо.
Решение Дуга Крокфорда - лучшее.
@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;
}());
Ясно понял вашу точку зрения, но не могли бы вы объяснить, что пытается сделать ваш фрагмент кода?
privateFoo полностью конфиденциальна и поэтому невидима при получении new Foo(). Только bar() здесь является общедоступным методом, имеющим доступ к privateFoo. Вы можете использовать тот же механизм для простых переменных и объектов, однако вы всегда должны помнить, что эти privates на самом деле статичны и будут использоваться всеми объектами, которые вы создаете.
Когда я прочитал это, мне показалось, что это непростая задача, поэтому я решил найти выход. Я придумал 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.info("a:", a.getPrivate()); // "a: Default"
var b = new SharedPrivateClass();
console.info("b:", b.getPrivate()); // "b: Default"
a.setPrivate("foo"); // a Sets private to "foo"
console.info("a:", a.getPrivate()); // "a: foo"
console.info("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!
console.info(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.info(a.private); // undefined
// getPrivate() is only created once and instanceof still works
console.info(a.getPrivate === b.getPrivate);
console.info(a instanceof SharedPrivateClass);
console.info(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.info("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.info(a.getPrivateName()); // Default Name
var b = new PrivateNamespaceClass();
console.info(b.getPrivateName()); // Default Name
console.info(a.setPrivateName("A"));
console.info(b.setPrivateName("B"));
console.info(a.getPrivateName()); // A
console.info(b.getPrivateName()); // B
// private objects are not accessible outside the PrivateNamespaceClass function
console.info(a.p);
// the prototype functions are not re-created for each instance
// and instanceof still works
console.info(a.getPrivateName === b.getPrivateName);
console.info(a instanceof PrivateNamespaceClass);
console.info(b instanceof PrivateNamespaceClass);Я хотел бы получить отзывы от тех, кто видит ошибку в этом способе.
Я предполагаю, что одна из потенциальных проблем заключается в том, что любой экземпляр может получить доступ к частным варам любых других экземпляров, используя другой идентификатор экземпляра. Не обязательно плохо ...
Важно отметить, что он работает так, как ожидалось, что делает его, по крайней мере, жизнеспособным в качестве способа достижения этого эффекта. Я не могу с уверенностью сказать, лучший ли это способ или способ, который придерживается стандартов и лучших практик. На мой взгляд, это хороший способ сделать это, и у него нет каких-либо серьезных очевидных проблем. Только мои 2 цента.
Вы переопределяете функции прототипа при каждом вызове конструктора
@ Lu4 Я не уверен, что это правда. Конструктор возвращается из замыкания; единственный раз, когда определяются функции-прототипы, - это первый раз, в этом немедленно вызываемом функциональном выражении. Помимо проблем с конфиденциальностью, которые были упомянуты выше, мне это нравится (на первый взгляд).
@ MimsH.Wright другие языки разрешают доступ к другим объектам приватного того же класса, но только тогда, когда у вас есть ссылка на них. Чтобы учесть это, вы можете спрятать рядовых за функцией, которая принимает указатель объектов в качестве ключа (в отличие от идентификатора). Таким образом, у вас будет доступ только к личным данным объектов, о которых вы знаете, что больше соответствует области видимости на других языках. Однако эта реализация проливает свет на более глубокую проблему. Частные объекты никогда не будут собираться мусором до тех пор, пока не будет выполнена функция конструктора.
Хорошее замечание о сборке мусора. Я полагаю, вы могли бы сделать delete p[this.i], но это не будет автоматически.
Между прочим, я изначально думал о том, чтобы переменная this имела указатель на себя, называемый r, а i был бы ivate, чтобы вы могли записать p[r.ivate], но решил, что это слишком глупо. А может, это не сработало вне конструктора. В любом случае, если бы кто-нибудь мог придумать более красивую стенографию, чем p[this.i], это было бы круто.
Не говоря уже о более приятном сокращении, у вас есть privateNamespace в качестве глобальной переменной!
@MikeM О, черт возьми, ты прав! Я починил это. Как это работает? почему это публично?
Вы не объявили это должным образом в локальной области видимости. Все личные данные были раскрыты. См. Разница между использованием var и неиспользованием var в JavaScript.
У вас нет утечки памяти?
Это не так уж и безумно: codeproject.com/Articles/133118/…
Хочу отметить, что i добавлен во все экземпляры. Так что он не полностью "прозрачен", и i все еще можно подделать.
Это та же концепция, что и Вывернутые наизнанку объекты в книге Дэмиана Конвея "Perl Best Practices".
Внимание: delete не освобождает память. Он не запускает сборку мусора, если только удаленная опора не является последней ссылкой.
Кто-нибудь делал глубокое тестирование комментария @ Lu4? Я подозреваю, что он прав. В каждом новом экземпляре PrivateNamespaceClass вы создаете новый экземпляр замыкания, который создает новый экземпляр внутренней функции и прототипа.
Вместо того, чтобы делать this.i обычным свойством, вы можете использовать Object.defineProperty, чтобы сделать его недоступным для записи свойством. Тогда было бы еще безопаснее.
Мне показалось очень хорошим решением, я проверял время выполнения и стоимость памяти, все в порядке. Кто-то упомянул об утечке памяти, как я могу проверить это решение?
Похоже, что это не имеет никакого преимущества перед объявлением всех методов в конструкторе (вообще без использования prototype), поскольку вы по-прежнему создаете новые объекты Function для каждого экземпляра класса. Я думаю, что у него также есть недостаток в том, что он ломает instanceof.
Ваш второй пример также реализует статическую частную переменную. Каждый раз, когда вы вызываете конструктор, частные переменные будут использоваться экземплярами. Я пробовал ваш пример с моим более простым примером и не работает, а также ранний логический анализ подсказывает мне, что ваш пример неверен
Вы действительно можете добиться этого, используя Проверка аксессуара:
(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());
Этот пример взят из моего сообщения о Прототипные функции и частные данные и объясняется там более подробно.
Этот ответ слишком «умный», чтобы быть полезным, но мне нравится ответ использования переменной, связанной с IFFE, в качестве секретного рукопожатия. Эта реализация использует слишком много замыканий, чтобы быть полезной; смысл методов, определенных прототипом, состоит в том, чтобы предотвратить создание новых объектов функций для каждого метода на каждом объекте.
Этот подход использует секретный ключ, чтобы определить, каким методам прототипа можно доверять, а каким нет. Однако именно экземпляр проверяет ключ, поэтому ключ должен быть отправлен экземпляру. Но тогда ненадежный код может вызвать доверенный метод на поддельном экземпляре, который украдет ключ. И с этим ключом создайте новые методы, которым будут доверять реальные экземпляры. Так что это только конфиденциальность безвестности.
Да, это возможно. Шаблон проектирования PPF как раз решает эту проблему.
PPF расшифровывается как Private Prototype Functions. Базовый PPF решает следующие проблемы:
Для первого просто:
Это так просто. Например:
// 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;
}
...
Прочтите полную историю здесь:
Ответ только по ссылке обычно не одобряется на SO. Покажите, пожалуйста, пример.
В статье есть примеры внутри, так что смотрите там
Но что произойдет, если в какой-то момент этот сайт выйдет из строя? Тогда как кто-то может увидеть пример? Политика действует таким образом, что все ценное в ссылке может храниться здесь, и вам не нужно полагаться на веб-сайт, который не находится под нашим контролем.
@Edward, твоя ссылка интересна! Однако мне кажется, что основная причина доступа к частным данным с использованием прототипных функций состоит в том, чтобы не допустить, чтобы каждый объект тратил впустую память идентичными общедоступными функциями. Описанный вами метод не решает эту проблему, поскольку для публичного использования прототипная функция должна быть заключена в обычную публичную функцию. Я предполагаю, что этот шаблон может быть полезен для экономии памяти, если у вас много ppf, которые объединены в одной общедоступной функции. Вы используете их для чего-нибудь еще?
@DiningPhilosofer, спасибо, что оценили мою статью. Да, вы правы, мы по-прежнему используем функции экземпляра. Но идея состоит в том, чтобы сделать их как можно более легкими, просто повторно вызвав их аналоги из PPF, которые выполняют всю тяжелую работу. В конце концов, все экземпляры вызывают одни и те же PPF (конечно, через оболочки), поэтому можно ожидать некоторой экономии памяти. Вопрос в том, сколько. Ожидаю существенной экономии.
Короче говоря, вы можете использовать новый 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;
}());
Самый простой способ конструировать объекты - вообще избегать прототипного наследования. Просто определите частные переменные и общедоступные функции в замыкании, и все общедоступные методы будут иметь частный доступ к переменным.
В JavaScript прототипное наследование - это в первую очередь оптимизация. Это позволяет нескольким экземплярам совместно использовать методы прототипа вместо того, чтобы каждый экземпляр имел свои собственные методы.
Недостатком является то, что this - это функция Только, которая различается каждый раз, когда вызывается прототипная функция.
Следовательно, любые частные поля должны быть доступны через this, что означает, что они будут общедоступными. Поэтому мы просто придерживаемся соглашений об именах для полей _private.
Я думаю, вы смешиваете закрывающие переменные не должен с методами-прототипами. Вы должны использовать то или другое.
Когда вы используете закрытие для доступа к частной переменной, методы прототипа не могут получить доступ к переменной. Итак, вы должны раскрыть закрытие this, что означает, что вы так или иначе раскрываете его публично. Такой подход дает очень мало пользы.
Для действительно простых объектов используйте простой объект с закрытием.
Если вам нужно прототипное наследование - для наследования, производительности и т. д. - придерживайтесь соглашения об именах "_private" и не беспокойтесь о замыканиях.
Я не понимаю, почему разработчики JS ТАК стараются сделать поля по-настоящему приватными.
К сожалению, соглашение об именах _private по-прежнему остается лучшим решением, если вы хотите воспользоваться преимуществами прототипного наследования.
ES6 будет иметь новую концепцию Symbol, которая является отличным способом создания частных полей. Вот отличное объяснение: curiosity-driven.org/private-properties-in-javascript
Разве вам не нужно было бы предоставлять ссылку на Symbol, чтобы получить доступ к Symbol в функции прототипа? WeakMaps в этом примере больше похож на частный доступ, чем на Symbol.
Нет, вы можете оставить Symbol в закрытии, охватывающем весь ваш класс. Таким образом, все методы прототипа могут использовать символ, но он никогда не отображается вне класса.
Решение WeakMap похоже, потому что WeakMap также хранится в закрытии, доступном для всех методов-прототипов. Но самый большой недостаток WeakMap - это отсутствие сборки мусора ... вам нужно удалять ссылки вручную, что является огромным недостатком.
В статье, которую вы связали, написано "Символы похожи на частные имена, но, в отличие от частных имен, они не обеспечивают истинную конфиденциальность.". Фактически, если у вас есть экземпляр, вы можете получить его символы с помощью Object.getOwnPropertySymbols. Так что это только конфиденциальность безвестности.
@Oriol Да, конфиденциальность находится в большой неизвестности. По-прежнему можно перебирать символы, и вы можете сделать вывод о назначении символа через toString. Это ничем не отличается от Java или C# ... частные члены по-прежнему доступны через отражение, но обычно сильно скрыты. Все это усиливает мою последнюю мысль: «Я не понимаю, почему разработчики JS ТАК стараются сделать поля по-настоящему приватными».
WeakMap.
@FelixKling Я ссылался на реализованный вручную ES5 WeakMap, как обсуждалось в другом ответе. Вы говорите о встроенном в ES6 WeakMap, в котором есть сборка мусора; но если у вас есть ES6, то Symbol - лучшее решение.
Думаю, в последней строчке есть опечатка. Должно быть: })();
+1 Спасибо Скотту за комментарий к ES5. Хотя принятый ответ предлагает решение исходной проблемы, ваш ответ хорошо описывает, почему нам не следует смешивать эти два подхода.
В текущем 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 до того, как он выйдет из области видимости, разве weakmap не будет ссылаться на него, чтобы у вас была утечка памяти? Я придумал шаблон для защищенный, поскольку другие экземпляры Person могут получить доступ к переменной, а те, которые наследуются от Person, могут. Просто поигрался, поэтому не уверен, есть ли какие-либо недостатки, кроме дополнительной обработки (не так много, как доступ к частным) stackoverflow.com/a/21800194/1641941 Возврат частного / защищенного объекта - это боль, поскольку вызывающий код может затем изменить ваш частный / защищенный.
@HMR Да, вы должны явно уничтожить личные данные. Я собираюсь добавить это предостережение к своему ответу.
Разве вы не можете поместить переменные в более высокий объем?
(function () {
var privateVariable = true;
var MyClass = function () {
if (privateVariable) console.info('readable from private scope!');
};
MyClass.prototype.publicMethod = function () {
if (privateVariable) console.info('readable from public scope!');
};
}))();
Затем переменные распределяются между всеми экземплярами MyClass.
Вы также можете попробовать добавить метод не непосредственно в прототип, а в функцию конструктора, например:
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.info(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.info(myArray2.getAll()); // ['some item 2']
console.info(myArray1.getAll()); // ['some item 2'] - FINE!
Есть более простой способ - использовать методы 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.info([].slice.call(arguments, 1));
return _public.message + this.value;
};
var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3] (console.info)
// => "Hello, World" (return value)
test.message = "Greetings, ";
test.getMessage(); // [] (console.info)
// => "Greetings, World" (return value)
Зачем кому-то создавать копию метода прототипа, а не просто создавать экземпляр метода в первую очередь?
Попытайся!
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.info(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;
var pot = new Potatoe(32);
console.info('Potatoe size: '+pot.size);
try {
console.info('Potatoe image: '+pot.image);
} catch(e) {
console.info('Oops: '+e);
}
pot.draw();
Это зависит от caller, который является расширением, зависящим от реализации, не разрешенным в строгом режиме.
Вот что я придумал.
(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.info(private.a);
}
};
return new MyObj;
};
window.YrObj = yrObj;
}());
var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2
Основная проблема этой реализации заключается в том, что она переопределяет прототипы при каждом создании экземпляра.
Интересно, что мне очень нравится эта попытка, и я думал о том же, но вы правы в том, что переопределение функции прототипа для каждого экземпляра является довольно большим ограничением. Это происходит не только потому, что циклы ЦП тратятся впустую, но и потому, что, если вы когда-нибудь позже измените прототип, он вернется в исходное состояние, как определено в конструкторе, при следующем создании экземпляра: /
Это не только переопределение прототипов, но и определение нового конструктора для каждого экземпляра. Таким образом, «экземпляры» больше не являются экземплярами одного и того же класса.
Вот кое-что, что я придумал, пытаясь найти самое простое решение этой проблемы, возможно, это может быть кому-то полезно. Я новичок в 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.info('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.info('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.info('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.info('error: ' + err);
}
}
test_smth();
Есть очень простой способ сделать это
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.info(o1.getP()); // secret
console.info(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.info(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.info(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.info(o1.getP()); // And it's only for $1,795._
Прототипы JavaScript - это золото.
Я считаю, что лучше не использовать прототип в функции конструктора, поскольку он будет создавать новую функцию каждый раз, когда создается новый экземпляр.
@whamsicore Да, верно, но в данном случае это важно, поскольку для каждого экземпляра объекта мы должны организовать общее закрытие. Вот почему определения функций находятся внутри конструктора, и мы должны ссылаться на SharedPrivate.prototype как на this.constructor.prototype. Нет ничего страшного в том, чтобы переопределить getP и setP несколько раз ...
Сегодня я столкнулся с тем же вопросом, и после разработки первоклассного ответа Скотта Риппи я придумал очень простое решение (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.info(this[privateField]);
};
};
cls.prototype.prototypeHello = function(){
console.info(this[privateField]);
};
return cls;
}());
return TestClass;
}());
a = new TestClass();
a.nonProtoHello();
a.prototypeHello();
Протестировано с помощью ringojs и nodejs. Я очень хочу прочитать ваше мнение.
Вот ссылка: проверьте раздел «На шаг ближе». philipwalton.com/articles/…
Я опаздываю на вечеринку, но думаю, что могу внести свой вклад. Вот, посмотри:
// 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.info(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Я называю этот метод шаблон аксессуара. Основная идея состоит в том, что у нас есть закрытие, ключ внутри замыкания, и мы создаем частный объект (в конструкторе), к которому можно получить доступ, только если у вас есть ключ.
Если вам интересно, вы можете прочитать об этом в моя статья. Используя этот метод, вы можете создать для каждого объекта свойства, к которым нельзя получить доступ за пределами закрытия. Следовательно, вы можете использовать их в конструкторе или прототипе, но не где-либо еще. Я нигде не видел, чтобы этот метод использовался, но я думаю, что он действительно эффективен.
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()
Как это? Использование частного аксессуара. Позволяет только получать переменные, но не устанавливать их, в зависимости от варианта использования.
Это дает нет полезный ответ на вопрос. Зачем вы верите, что это ответ? как работает? Простое сообщение кому-либо изменить свой код без какого-либо контекста или смысла не поможет им понять, что они сделали неправильно.
Ему нужен был способ доступа к скрытым частным переменным класса через прототипы без необходимости создавать эту скрытую переменную для каждого экземпляра класса. Приведенный выше код - это пример того, как это сделать. Как это не ответ на вопрос?
Я не сказал, что это не ответ на вопрос. Я сказал, что это не ответ полезный, потому что он никому не помогает учиться. Вы должны объяснить свой код, Зачем это работает, Зачем это правильный способ сделать это. Если бы я был автором вопроса, я бы не принял ваш ответ, потому что он не поощряет обучение, он не учит меня тому, что я делаю неправильно, или что делает данный код, или как он работает.
У меня есть одно решение, но я не уверен, что оно безупречно.
Чтобы это работало, вы должны использовать следующую структуру:
Вот код:
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.info(privateFields.field1);
};
TestClass.prototype.logField2 = function () {
var privateFields = this.getPrivateFields(hash);
console.info(privateFields.field2);
};
return TestClass;
})();
Это работает так: она предоставляет функцию экземпляра this.getPrivateFields для доступа к объекту частных переменных privateFields, но эта функция будет возвращать только объект privateFields внутри определенного основного замыкания (также функции прототипа, использующие this.getPrivateFields "необходимо определить внутри этого закрытия).
Хэш, созданный во время выполнения, который трудно угадать, используется в качестве параметров, чтобы гарантировать, что даже если "getPrivateFields" вызывается вне области закрытия, не вернет объект "privateFields".
Недостатком является то, что мы не можем расширить TestClass с помощью большего количества функций-прототипов за пределами закрытия.
Вот тестовый код:
var t1 = new TestClass();
console.info('Initial t1 field1 is: ');
t1.logField1();
console.info('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.info('t1 field1 is now: ');
t1.logField1();
console.info('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.info('Initial t2 field1 is: ');
t2.logField1();
console.info('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.info('t2 field1 is now: ');
t2.logField1();
console.info('t2 field2 is now: ');
t2.logField2();
console.info('t1 field1 stays: ');
t1.logField1();
console.info('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);
};
Я играл с этим сегодня, и это было единственное решение, которое я мог найти без использования символов. Самое лучшее в этом то, что на самом деле все это может быть полностью конфиденциальным.
Решение основано на самодельном загрузчике модулей, который в основном становится посредником для кеша частного хранилища (с использованием слабой карты).
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.info('pVar = ' + myClass.getPrivateVar())
console.info('pVar2 = ' + myClass2.getPrivateVar())
//Increment it
myClass.incrementPrivateVar()
//Print out to see if one affected the other or shared
console.info('pVar after increment = ' + myClass.getPrivateVar())
console.info('pVar after increment on other class = ' + myClass2.getPrivateVar())
//Clean up.
loader.remove('MyModule')
Вам нужно изменить 3 вещи в вашем коде:
var privateField = "hello" на this.privateField = "hello".privateField на this.privateField.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Я знаю, что с тех пор, как этот вопрос был задан, прошло более 10 лет, но я просто задумался об этом в 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, которая возвращает функцию Конструктор, которая возвращает объект Интерфейс. Единственный метод интерфейса - это $, который получает аргумент 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.info("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.info("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.info("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.info("classX1.valA: " + classX1.$("getValA"));
console.info("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
_ $ / $ шаблон обеспечивает полную конфиденциальность значений в полностью прототипированных классах. Я не знаю, буду ли я когда-нибудь использовать это, и есть ли у него недостатки, но эй, это была хорошая головоломка!
Используя простой шаблон, основанный на ES6 WeakMaps, можно получить частные переменные-члены, доступные из функций-прототипов.
Note : The usage of WeakMaps guarantees safety against memory leaks, by letting the Garbage Collector identify and discard unused instances.
// 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;
}());Более подробное объяснение этого паттерна можно найти в здесь
возможный дубликат Как создать приватную переменную, доступную для функции Prototype?