В общем-то способ создавать члены класса, которые будут недоступны извне, в JavaScript существует и называется замыканием. Но при таком подходе есть один серьёзный минус — создаётся много экземпляров одной и той же функции, что не есть хорошо для ресурсов.
Я немного поразмышлял, как сделать всё просто и в то же время эффективно. Сначала я нашёл решение, которое скорее экономит ресурсы, чем избавляет о «перерасхода». Вот оно:
Здесь мы экономим ресурсы за счёт того, что создаваемая каждый раз в конструкторе функция состоит всего из одной строки — вызова настоящего тела метода. Но функции продолжают плодиться, пость они и стали меньше.
Как ещё можно реализовать закрытые члены? Можно вынести их в элемент какого-нибудь глобального массива, создаваемый для каждого объекта. Но объект нужно как-то идентифицировать. Но сделать такой идентификатор в виде поля объекта нельзя — поле можно изменить (а мы, напоминаю, пытаемся закрыть методы — иначе можно просто определить для них какой-нибудь префикс типа l_privateMethod и успокоиться). Никакой уникальной характеристики, кроме адреса в памяти и/или номера в списке интерпретатора, у объекта нет, а к этим характеристикам мы доступ получить не можем. Объект не может являться индексом в массиве… или стоп! Может, если это объект Number! Таким образом мы приходим к весьма простому но эффективному решению:
Теперь настоящие объекты у нас хранятся в массиве objects сервера объектов, а вовне отдаётся только публичная (external) часть. Чтобы создать объект на основе функции-конструктора класса, нужно написать objs.create, чтобы удалить — objs.destroy (кстати появляется шикарная возможность включить сюда деструкторы, если немного доработать решение — ссылка на объект будет храниться в массиве до тех пор, пока мы её явно не удалим). Чтобы обратиться к закрытой части объекта, нужно воспользоваться самой функцией objs:
Внешние методы объявляются как члены подъобъекта external так же, как закрытые — как члены подобъекта prototype. Единственное ограничение — параметров у конструктора быть не должно, т.к. нельзя писать new constructor.call(...).
Вот как можно использовать этот механизм для реализации некоторого модуля:
Вызвав извне global.Class (или просто Class — это имя глобального пространства) мы получим публичный интерфейс класса, в то время как закрытый интерфейс останется только в анналах сервера объектов — посмотрите сами, alert(object1.privateMethod); отобразит undefined. Главное не забывать определять сервер объектов внутри модуля, чтобы он не был доступен извне. Функцию создания сервера можно определить один раз где-нибудь снаружи в наборе утилитарных функций. И можно не опасаться, что кто-то переопределит номер объекта — сервер откажет такому запросу (см. две последние стоки).
Надеюсь, что способ вам пригодится, благо он прост как три копейки и позволяет не расходовать память и время зазря (ну разве что на указатели на функции). Если кто-то его модифицирует, чтобы можно было делать ещё и защищённые (protected) члены для переклички между разными модулями, то будет вообще замечательно. Ну а я на сём кланяюсь! Спасибо за ваше внимание!
UPD 02.12.09
При помощи вот такой модификации можно делать функции-конструкторы с параметрами (т.е. objs.create(Class, 10, 20)):
UPD 02.12.09
Спасибо dsCode за совет, я исправил недостаток библиотеки в части копирования external-методов. Теперь выигрыш по сравнению со стандартным решением по времени составляет около 6000% на том же тесте (см. комментарии) :^) (0,864 против 0,013, т.е. в ~ 60 раз). Библиотеку можно найти здесь:
библиотека JOS
пример
P.S. Теперь нужно запомнить ещё два ключевых слова: external и id. Их не стоит использовать в качестве идентификаторов полей объекта.
Я немного поразмышлял, как сделать всё просто и в то же время эффективно. Сначала я нашёл решение, которое скорее экономит ресурсы, чем избавляет о «перерасхода». Вот оно:
- var Class_privateMethod = function (_this_privateVar)
- {
- alert(_this_privateVar);
- return null;
- }
-
- var Class_publicMethod = function (_this_privateVar)
- {
- return Class_privateMethod.call(this, _this_privateVar);
- }
-
- var Class = function ()
- {
- var privateVar = 'Hello, world!';
- this.publicMethod = function () { return Class_publicMethod.call(this, privateVar); }
- }
* This source code was highlighted with Source Code Highlighter.
Здесь мы экономим ресурсы за счёт того, что создаваемая каждый раз в конструкторе функция состоит всего из одной строки — вызова настоящего тела метода. Но функции продолжают плодиться, пость они и стали меньше.
Как ещё можно реализовать закрытые члены? Можно вынести их в элемент какого-нибудь глобального массива, создаваемый для каждого объекта. Но объект нужно как-то идентифицировать. Но сделать такой идентификатор в виде поля объекта нельзя — поле можно изменить (а мы, напоминаю, пытаемся закрыть методы — иначе можно просто определить для них какой-нибудь префикс типа l_privateMethod и успокоиться). Никакой уникальной характеристики, кроме адреса в памяти и/или номера в списке интерпретатора, у объекта нет, а к этим характеристикам мы доступ получить не можем. Объект не может являться индексом в массиве… или стоп! Может, если это объект Number! Таким образом мы приходим к весьма простому но эффективному решению:
- var ObjectServer = function ()
- {
- var objects = new Array(), CURRENT_ID = 1;
-
- var result = function (_id)
- {
- if(!(_id instanceof Number))
- {
- return;
- }
- if(objects[_id].external != _id)
- {
- return;
- }
- return objects[_id];
- }
-
- result.create = function (_class)
- {
- var object = new _class();
- object.external = new Number(CURRENT_ID);
- for(var key in _class.external)
- {
- object.external[key] = _class.external[key];
- }
- objects[CURRENT_ID ++] = object;
- return object.external;
- }
-
- result.destroy = function (_id)
- {
- if(!(_id instanceof Number))
- {
- return;
- }
- if(objects[_id].external != _id)
- {
- return;
- }
- delete objects[_id];
- }
-
- return result;
- }
-
- var objs = ObjectServer();
* This source code was highlighted with Source Code Highlighter.
Теперь настоящие объекты у нас хранятся в массиве objects сервера объектов, а вовне отдаётся только публичная (external) часть. Чтобы создать объект на основе функции-конструктора класса, нужно написать objs.create, чтобы удалить — objs.destroy (кстати появляется шикарная возможность включить сюда деструкторы, если немного доработать решение — ссылка на объект будет храниться в массиве до тех пор, пока мы её явно не удалим). Чтобы обратиться к закрытой части объекта, нужно воспользоваться самой функцией objs:
objs(myObject).privateMember;
Внешние методы объявляются как члены подъобъекта external так же, как закрытые — как члены подобъекта prototype. Единственное ограничение — параметров у конструктора быть не должно, т.к. нельзя писать new constructor.call(...).
Вот как можно использовать этот механизм для реализации некоторого модуля:
- var global = this;
-
- (function(){
-
- var objs = ObjectServer(); // Функция ObjectServer определена где-то снаружи.
-
- var Class = function ()
- {
- this.privateVar = 'I\'m a private variable.';
- }
-
- Class.prototype.privateMethod = function ()
- {
- alert(this.privateVar);
- alert(this.external.publicPrimitiveVar);
- this.external.publicCompoundVar.push(this.privateVarDefinedInConstructor);
- alert(this.external.publicCompoundVar);
- }
-
- Class.external = new Object();
-
- Class.external.publicPrimitiveVar = 10;
-
- Class.external.publicMethod = function ()
- {
- objs(this).privateMethod();
- objs(this).privateVar = 'Changed!';
- objs(this).privateMethod();
- }
-
- global.Class = function (_arg)
- {
- var result = objs.create(Class);
- result.publicCompoundVar = new Array('a', 'b', 'c');
- objs(result).privateVarDefinedInConstructor = _arg;
- return result;
- }
-
- })();
-
- var object1 = Class('d');
- var object2 = Class('e');
- object2.publicPrimitiveVar = 50;
- object1.publicMethod();
- object2.publicMethod();
- alert(object1.privateMethod);
- object2 = new Number(object2 * 1);
- object2.publicMethod();
* This source code was highlighted with Source Code Highlighter.
Вызвав извне global.Class (или просто Class — это имя глобального пространства) мы получим публичный интерфейс класса, в то время как закрытый интерфейс останется только в анналах сервера объектов — посмотрите сами, alert(object1.privateMethod); отобразит undefined. Главное не забывать определять сервер объектов внутри модуля, чтобы он не был доступен извне. Функцию создания сервера можно определить один раз где-нибудь снаружи в наборе утилитарных функций. И можно не опасаться, что кто-то переопределит номер объекта — сервер откажет такому запросу (см. две последние стоки).
Надеюсь, что способ вам пригодится, благо он прост как три копейки и позволяет не расходовать память и время зазря (ну разве что на указатели на функции). Если кто-то его модифицирует, чтобы можно было делать ещё и защищённые (protected) члены для переклички между разными модулями, то будет вообще замечательно. Ну а я на сём кланяюсь! Спасибо за ваше внимание!
UPD 02.12.09
При помощи вот такой модификации можно делать функции-конструкторы с параметрами (т.е. objs.create(Class, 10, 20)):
- ObjectServer.create = function (_class)
- {
- var constructor = new Function();
- constructor.prototype = _class.prototype;
- var object = new constructor();
- object.constructor = _class;
- object.external = new Number(this.CURRENT_ID);
- for(var key in _class.external)
- {
- object.external[key] = _class.external[key];
- }
- this.objects[this.CURRENT_ID ++] = object;
- _class.apply(object, Array.prototype.slice.call(arguments, 1));
- return object.external;
- }
* This source code was highlighted with Source Code Highlighter.
UPD 02.12.09
Спасибо dsCode за совет, я исправил недостаток библиотеки в части копирования external-методов. Теперь выигрыш по сравнению со стандартным решением по времени составляет около 6000% на том же тесте (см. комментарии) :^) (0,864 против 0,013, т.е. в ~ 60 раз). Библиотеку можно найти здесь:
библиотека JOS
пример
P.S. Теперь нужно запомнить ещё два ключевых слова: external и id. Их не стоит использовать в качестве идентификаторов полей объекта.