Частный член Javascript на прототипе
-
20-08-2019 - |
Вопрос
Ну, я пытался выяснить, возможно ли это каким-либо образом.Вот код:
a=function(text)
{
var b=text;
if (!arguments.callee.prototype.get)
arguments.callee.prototype.get=function()
{
return b;
}
else
alert('already created!');
}
var c=new a("test"); // creates prototype instance of getter
var d=new a("ojoj"); // alerts already created
alert(c.get()) // alerts test
alert(d.get()) // alerts test from context of creating prototype function :(
Как видите, я попытался создать прототип геттера.За что?Хорошо, если вы напишете что-то вроде этого:
a=function(text)
{
var b=text;
this.getText=function(){ return b}
}
...все должно быть хорошо..но на самом деле каждый раз, когда я создаю объект, я создаю функцию getText, которая использует память.Я хотел бы иметь в памяти одну прототипическую функцию, которая делала бы то же самое...Есть идеи?
РЕДАКТИРОВАТЬ:
Я попробовал решение, предложенное Кристофом, и, похоже, на данный момент это единственное известное решение.Чтобы получить значение из контекста, необходимо запомнить информацию об идентификаторе, но сама идея мне нравится :) Идентификатор - это только одна вещь, которую нужно запомнить, все остальное можно сохранить один раз в памяти.Фактически, таким образом вы можете хранить множество частных участников и использовать в любое время только один идентификатор.На самом деле меня это устраивает :) (если только у кого-то не появится идея получше).
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());
Решение
В JavaScript традиционно не предусмотрен механизм сокрытия свойств («частных членов»).
Поскольку JavaScript имеет лексическую область видимости, вы всегда можете смоделировать это на уровне каждого объекта, используя функцию конструктора в качестве замыкания ваших «частных членов» и определяя свои методы в конструкторе, но это не будет работать для методов, определенных в Свойство прототипа конструктора.
Конечно, есть способы обойти эту проблему, но я бы не советовал:
Foo = (function() {
var store = {}, guid = 0;
function Foo() {
this.__guid = ++guid;
store[guid] = { bar : 'baz' };
}
Foo.prototype.getBar = function() {
var privates = store[this.__guid];
return privates.bar;
};
Foo.prototype.destroy = function() {
delete store[this.__guid];
};
return Foo;
})();
Это сохранит «частные» свойства в другом объекте, отдельном от вашего. Foo
пример.Обязательно позвоните destroy()
после того, как вы закончите работу с объектом:в противном случае вы только что создали утечку памяти.
изменить 01.12.2015: ECMAScript6 делает возможными улучшенные варианты, которые не требуют уничтожения объекта вручную, например, с помощью Слабая карта или желательно Символ, полностью устраняя необходимость во внешнем хранилище:
var Foo = (function() {
var bar = Symbol('bar');
function Foo() {
this[bar] = 'baz';
}
Foo.prototype.getBar = function() {
return this[bar];
};
return Foo;
})();
Другие советы
Методы прототипа не могут получить доступ к «частным» членам, поскольку они существуют в JavaScript;вам нужен какой-то привилегированный метод доступа.Поскольку вы заявляете get
где он может лексически увидеть b
, он всегда вернет то, что b
было при создании.
Будучи очень вдохновленным обходным путем Кристофа, я придумал слегка измененную концепцию, включающую несколько улучшений.Опять же, это решение интересно, но не обязательно рекомендуется.Эти улучшения включают в себя:
- Больше не нужно выполнять какие-либо настройки в конструкторе
- Убрана необходимость хранить общедоступный GUID в экземплярах.
- Добавлено немного синтаксического сахара
По сути, хитрость здесь заключается в том, чтобы использовать сам объект экземпляра в качестве ключа для доступа к связанному частному объекту.Обычно это невозможно с простыми объектами, поскольку их ключи должны быть строками.Однако мне удалось добиться этого, используя тот факт, что выражение ({} === {})
возвращает false
.Другими словами, оператор сравнения может различать уникальные экземпляры объектов.
Короче говоря, мы можем использовать два массива для хранения экземпляров и связанных с ними частных объектов:
Foo = (function() {
var instances = [], privates = [];
// private object accessor function
function _(instance) {
var index = instances.indexOf(instance), privateObj;
if(index == -1) {
// Lazily associate instance with a new private object
instances.push(instance);
privates.push(privateObj = {});
}
else {
// A privateObject has already been created, so grab that
privateObj = privates[index];
}
return privateObj;
}
function Foo() {
_(this).bar = "This is a private bar!";
}
Foo.prototype.getBar = function() {
return _(this).bar;
};
return Foo;
})();
Вы заметите _
функция выше.Это функция доступа для захвата частного объекта.Он работает лениво, поэтому, если вы вызовете его с новым экземпляром, он на лету создаст новый приватный объект.
Если вы не хотите дублировать _
код для каждого класса, вы можете решить эту проблему, обернув его внутри фабричной функции:
function createPrivateStore() {
var instances = [], privates = [];
return function (instance) {
// Same implementation as example above ...
};
}
Теперь вы можете сократить его до одной строки для каждого класса:
var _ = createPrivateStore();
Опять же, вам придется быть очень осторожно используя это решение, поскольку оно может создавать утечки памяти, если вы не реализуете, и при необходимости вызывать функцию уничтожения.
Поскольку современные браузеры используют некоторые технологии ES6, вы можете использовать WeakMap
чтобы обойти проблему GUID.Это работает в IE11 и выше:
// Scope private vars inside an IIFE
var Foo = (function() {
// Store all the Foos, and garbage-collect them automatically
var fooMap = new WeakMap();
var Foo = function(txt) {
var privateMethod = function() {
console.log(txt);
};
// Store this Foo in the WeakMap
fooMap.set(this, {privateMethod: privateMethod});
}
Foo.prototype = Object.create(Object.prototype);
Foo.prototype.public = function() {
fooMap.get(this).p();
}
return Foo;
}());
var foo1 = new Foo("This is foo1's private method");
var foo2 = new Foo("This is foo2's private method");
foo1.public(); // "This is foo1's private method"
foo2.public(); // "This is foo2's private method"
WeakMap
не будет хранить ссылки ни на какие Foo
после того, как он будет удален или выйдет из области действия, и поскольку он использует объекты в качестве ключей, вам не нужно прикреплять GUID к вашему объекту.
Лично мне решение с гайдом не очень нравится, потому что оно заставляет разработчика объявлять его помимо магазина и инкрементировать в конструкторе.В больших приложениях Javascript разработчики могут забыть это сделать, что весьма подвержено ошибкам.
Мне очень нравится ответ Питера, потому что вы можете получить доступ к закрытым членам, используя контекст (это).Но меня очень беспокоит тот факт, что доступ к закрытым членам осуществляется со сложностью o(n).Действительно, поиск индекса объекта в массиве — это линейный алгоритм.Предположим, вы хотите использовать этот шаблон для объекта, экземпляр которого создается 10 000 раз.Затем вы можете перебирать 10 000 экземпляров каждый раз, когда захотите получить доступ к частному члену.
Чтобы получить доступ к частным хранилищам со сложностью o(1), нет другого способа, кроме как использовать направляющие.Но чтобы не заморачиваться с объявлением и приращением guid и чтобы использовать контекст для доступа к частному хранилищу, я изменил шаблон фабрики Peters следующим образом:
createPrivateStore = function () {
var privates = {}, guid = 0;
return function (instance) {
if (instance.__ajxguid__ === undefined) {
// Lazily associate instance with a new private object
var private_obj = {};
instance.__ajxguid__ = ++guid;
privates[instance.__ajxguid__] = private_obj;
return private_obj;
}
return privates[instance.__ajxguid__];
}
}
Хитрость здесь в том, чтобы учесть, что объекты, не имеющие ajxguid недвижимость еще не оформлена.Действительно, можно вручную установить это свойство перед первым доступом к хранилищу, но я думаю, что волшебного решения не существует.
Я думаю, что настоящая конфиденциальность переоценена.Виртуальная конфиденциальность — это все, что нужно.Я думаю, что использование _privateIdentifier — это шаг в правильном направлении, но недостаточный, поскольку вам по-прежнему предоставляется список всех _privateIdentifiers во всплывающих окнах intellisense.Дальнейший и лучший шаг — создать объект в функции прототипа и/или конструктора для отделения ваших практически частных полей и методов от глаз, например:
// Create the object
function MyObject() {}
// Add methods to the prototype
MyObject.prototype = {
// This is our public method
public: function () {
console.log('PUBLIC method has been called');
},
// This is our private method tucked away inside a nested privacy object called x
x: {
private: function () {
console.log('PRIVATE method has been called');
}
},
}
// Create an instance of the object
var mo = new MyObject();
Теперь, когда кодер типа «Мо». Intellisense покажет только публичную функцию и «X».Таким образом, все частные участники не показываются, но скрыты за «x», делая его более маловероятным для кодера случайно вызвать частного участника, потому что им придется уйти с пути и введите «Mo.x.». Чтобы увидеть частных членов.Этот метод также позволяет избежать загромождения списка intellisense множеством имен частных членов, скрывая их все за одним элементом «x».
Я знаю, что эта тема действительно очень старая, но подумал, что это решение может быть интересно любому прохожему:
const Immutable = function ( val ) {
let _val = val;
this.$ = {
_resolve: function () {
return _val;
}
};
};
Immutable.prototype = {
resolve: function () {
return this.$._resolve();
}
};
По сути, скрывая внутреннее _val от манипуляций и создания неизменяемого экземпляра этого объекта.
Я создал новую библиотеку для включения частных методов в цепочке прототипов.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