Частный член Javascript на прототипе
Хорошо, я попытался выяснить, возможно ли это в любом случае. Вот код:
a=function(text)
{
var b=text;
if (!arguments.callee.prototype.get)
arguments.callee.prototype.get=function()
{
return b;
}
else
alert('already created!');
}
var c=new a("test"); // creates prototype instance of getter
var d=new a("ojoj"); // alerts already created
alert(c.get()) // alerts test
alert(d.get()) // alerts test from context of creating prototype function :(
Как вы видите, я пытался создать прототип getter. Для чего? Ну, если вы напишете что-то вроде этого:
a=function(text)
{
var b=text;
this.getText=function(){ return b}
}
... все должно быть хорошо.. но на самом деле каждый раз, когда я создаю объект, я создаю функцию getText, которая использует память. Я хотел бы иметь одну прототипную функцию, лежащую в памяти, которая будет делать то же самое... Любые идеи?
EDIT:
Я пробовал решение, данное Кристофом, и кажется, что это единственное известное решение на данный момент. Ему нужно запомнить информацию об идентификаторе, чтобы получить значение из контекста, но целая идея мне приятна:) Id - это только одна вещь, которую нужно запомнить, все остальное можно сохранить один раз в памяти. На самом деле вы можете хранить много частных членов таким образом и использовать в любое время только один идентификатор. На самом деле это удовлетворяет меня:) (если кто-то не получил лучшую идею).
someFunc = function()
{
var store = new Array();
var guid=0;
var someFunc = function(text)
{
this.__guid=guid;
store[guid++]=text;
}
someFunc.prototype.getValue=function()
{
return store[this.__guid];
}
return someFunc;
}()
a=new someFunc("test");
b=new someFunc("test2");
alert(a.getValue());
alert(b.getValue());
Ответы
Ответ 1
JavaScript традиционно не обеспечивал механизм скрытия свойств ( "частные члены" ).
Поскольку JavaScript лексически ограничен, вы всегда можете имитировать это на уровне каждого объекта, используя функцию-конструктор как закрытие над вашими "частными членами" и определяя ваши методы в конструкторе, но это не будет работать для методов определенный в свойстве прототипа конструктора.
Конечно, есть способы обойти это, но я бы не рекомендовал его:
Foo = (function() {
var store = {}, guid = 0;
function Foo() {
this.__guid = ++guid;
store[guid] = { bar : 'baz' };
}
Foo.prototype.getBar = function() {
var privates = store[this.__guid];
return privates.bar;
};
Foo.prototype.destroy = function() {
delete store[this.__guid];
};
return Foo;
})();
Это сохранит свойства 'private' в другом объекте отдельно от вашего экземпляра Foo
. Обязательно вызовите destroy()
после того, как вы закончите объект: иначе вы только что создали утечку памяти.
edit 2015-12-01: ECMAScript6 предлагает улучшенные варианты, которые не требуют ручного уничтожения объекта, например, используя WeakMap или предпочтительно Symbol, избегая необходимости внешнего хранилища:
var Foo = (function() {
var bar = Symbol('bar');
function Foo() {
this[bar] = 'baz';
}
Foo.prototype.getBar = function() {
return this[bar];
};
return Foo;
})();
Ответ 2
Методы прототипа не могут получить доступ к "private" членам, как они существуют в javascript; вам нужен какой-то привилегированный аксессуар. Поскольку вы объявляете get
, где он может лексически видеть b
, он всегда будет возвращать то, что b
было при создании.
Ответ 3
После того, как я был чрезвычайно вдохновлен работой Кристофа, я придумал слегка модифицированную концепцию, которая предоставляет несколько улучшений. Опять же, это решение интересно, но не обязательно рекомендуется. Эти улучшения включают в себя:
- Больше не нужно выполнять какие-либо настройки в конструкторе
- Устранена необходимость сохранения открытого GUID в экземплярах
- Добавлен синтаксический сахар
По сути, здесь следует использовать сам экземпляр-объект в качестве ключа для доступа к связанному частному объекту. Обычно это невозможно при использовании простых объектов, поскольку их ключи должны быть строками. Тем не менее, я смог выполнить это, используя тот факт, что выражение ({} === {})
возвращает false
. Другими словами, оператор сравнения может различать уникальные экземпляры объектов.
Короче говоря, мы можем использовать два массива для поддержки экземпляров и связанных с ними частных объектов:
Foo = (function() {
var instances = [], privates = [];
// private object accessor function
function _(instance) {
var index = instances.indexOf(instance), privateObj;
if(index == -1) {
// Lazily associate instance with a new private object
instances.push(instance);
privates.push(privateObj = {});
}
else {
// A privateObject has already been created, so grab that
privateObj = privates[index];
}
return privateObj;
}
function Foo() {
_(this).bar = "This is a private bar!";
}
Foo.prototype.getBar = function() {
return _(this).bar;
};
return Foo;
})();
Вы заметите выше описанную функцию _
. Это функция доступа для захвата частного объекта. Он работает лениво, поэтому, если вы вызываете его с помощью нового экземпляра, он будет создавать новый закрытый объект на лету.
Если вы не хотите дублировать код _
для каждого класса, вы можете решить это, обернув его внутри функции factory:
function createPrivateStore() {
var instances = [], privates = [];
return function (instance) {
// Same implementation as example above ...
};
}
Теперь вы можете уменьшить его до одной строки для каждого класса:
var _ = createPrivateStore();
Опять же, вы должны быть очень осторожны, используя это решение, поскольку оно может создавать утечки памяти, если вы не реализуете и не вызываете функцию destroy при необходимости.
Ответ 4
В современных браузерах, использующих некоторые технологии ES6, вы можете использовать WeakMap
, чтобы обойти проблему GUID. Это работает в IE11 и выше:
// Scope private vars inside an IIFE
var Foo = (function() {
// Store all the Foos, and garbage-collect them automatically
var fooMap = new WeakMap();
var Foo = function(txt) {
var privateMethod = function() {
console.log(txt);
};
// Store this Foo in the WeakMap
fooMap.set(this, {privateMethod: privateMethod});
}
Foo.prototype = Object.create(Object.prototype);
Foo.prototype.public = function() {
fooMap.get(this).p();
}
return Foo;
}());
var foo1 = new Foo("This is foo1 private method");
var foo2 = new Foo("This is foo2 private method");
foo1.public(); // "This is foo1 private method"
foo2.public(); // "This is foo2 private method"
WeakMap
не будет хранить ссылки на любой Foo
после того, как он будет удален или выпадет из области видимости, и поскольку он использует объекты как ключи, вам не нужно прикреплять GUID к вашему объекту.
Ответ 5
Лично мне не очень нравится решение с guid, потому что он заставляет разработчика объявлять его в дополнение к хранилищу и увеличивать его в конструкторе. В больших приложениях javascript разработчики могут забыть сделать это, что вполне подвержено ошибкам.
Мне нравится, что Питер очень сильно отвечает из-за того, что вы можете получить доступ к частным членам, используя контекст (это). Но меня очень беспокоит то, что доступ к частным членам осуществляется с помощью o (n) сложности. Действительно, поиск индекса объекта в массиве является линейным алгоритмом. Предположим, вы хотите использовать этот шаблон для объекта, который был создан 10000 раз. Затем вы можете проходить через 10000 экземпляров каждый раз, когда хотите получить доступ к частному члену.
Для доступа к частным магазинам по сложности (1) нет другого способа, кроме использования команд. Но для того, чтобы не беспокоиться о декларации и приращении guid, и чтобы использовать контекст для доступа к частному магазину, я изменил шаблон Peters factory следующим образом:
createPrivateStore = function () {
var privates = {}, guid = 0;
return function (instance) {
if (instance.__ajxguid__ === undefined) {
// Lazily associate instance with a new private object
var private_obj = {};
instance.__ajxguid__ = ++guid;
privates[instance.__ajxguid__] = private_obj;
return private_obj;
}
return privates[instance.__ajxguid__];
}
}
Трюк здесь заключается в том, что объекты, которые не имеют свойства ajxguid, еще не обработаны. Действительно, можно было вручную установить свойство перед тем, как получить доступ к хранилищу в первый раз, но я думаю, что нет волшебного решения.
Ответ 6
Я создал новую библиотеку для включения частных методов в цепочку прототипов.
https://github.com/TremayneChrist/ProtectJS
Пример:
var MyObject = (function () {
// Create the object
function MyObject() {}
// Add methods to the prototype
MyObject.prototype = {
// This is our public method
public: function () {
console.log('PUBLIC method has been called');
},
// This is our private method, using (_)
_private: function () {
console.log('PRIVATE method has been called');
}
}
return protect(MyObject);
})();
// Create an instance of the object
var mo = new MyObject();
// Call its methods
mo.public(); // Pass
mo._private(); // Fail