Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Использование классов в JavaScript (https://javascript.ru/forum/project/27339-ispolzovanie-klassov-v-javascript.html)

devote 25.07.2012 15:02

Очередное обновление.

Добавлена возможность множественного наследования, так же внесены изменения во входные параметры.

Так же добавлен параметр Class.defaultContext в который вы можете указать объект, куда по умолчанию будут складываться все созданные классы, у которых явно не указан контекст.

Объявлять классы как обычно можно:
Class("Foo", {
    // code
});
если нужны указать дополнительные параметры, это теперь можно сделать так:
Class("Foo", {
    // контекст в котором будет создан класс
    context: window,
    // строка/конструктор от которого будем наследоваться
    extends: "Bar",
    // строка/конструктор или список строк/конструкторов, из которых будут получены дополнительные свойства
    implements: [ "IBase", "IPersistent" ],
    // статические параметры
    static: {
        STATIC_PARAM: 1,
        STATIC_CONST: "lala"
    }
}, {
    // code
});

Пример:
<script type="text/javascript" src="https://github.com/devote/jsClasses/raw/master/core.class.min.js"></script>
<script type="text/javascript">

	Class("Foo", {
		foo: function(){
			alert( this.__class__.className );
		},
		"get name": function() {
			return "this Foo name";
		}
	});

	Class("Bar", {
		bar: function(){
			alert( this.__class__.className );
		},
		"get name": function() {
			return "this Bar name";
		}
	});

	Class("Tre", Foo, {
		trefoo: function(){
			alert( this.__class__.className );
		}
	});

	Class("Mix", {
	    extends: "Tre",
	    implements: "Bar"
	}, {
	});

	var a = new Mix();
	a.foo();
	a.bar();
	a.trefoo();
	alert( a.name );
</script>
или так:
<script type="text/javascript" src="https://github.com/devote/jsClasses/raw/master/core.class.min.js"></script>
<script type="text/javascript">

	Class("Foo", {
		foo: function(){
			alert( this.__class__.className );
		},
		"get name": function() {
			return "this Foo name";
		}
	});

	Class("Bar", {
		bar: function(){
			alert( this.__class__.className );
		},
		"get name": function() {
			return "this Bar name";
		}
	});

	Class("Tre extends Foo", {
		trefoo: function(){
			alert( this.__class__.className );
		}
	});

	Class("Mix extends Tre, Bar", {
	});

	var a = new Mix();
	a.foo();
	a.bar();
	a.trefoo();
	alert( a.name );

	// или
	Class("Mix2", "Tre, Bar", {
	});

	var a = new Mix2();
	a.foo();
	a.bar();
	a.trefoo();
	alert( a.name );
</script>

B~Vladi 25.07.2012 15:05

Почему бы не сделать так:

Class("Foo", {
    // контекст в котором будет создан класс
    __context: window,
    // строка/конструктор от которого будем наследоваться
    __extends: Class.Bar,
    // строка/конструктор или список строк/конструкторов, из которых будут получены дополнительные свойства
    __implements: [ Class.IBase, Class.IPersistent ]
}, {
    // code
});


Таким образом можно будет передавать любой конструктор...

devote 25.07.2012 15:07

Цитата:

Сообщение от B~Vladi
Почему бы не сделать так:

Дык так оно тоже работает, я же написал строка/конструктор
<script type="text/javascript" src="https://github.com/devote/jsClasses/raw/master/core.class.min.js"></script>
<script type="text/javascript">

    Class("IBase", {
        someField: 100
    });

    Class("IPersistent", {
        key: "persistentKey"
    });

    Class("Bar", {
        bar: function() {
            alert( this.__class__.className );
        }
    });

    Class("Foo", {
        // контекст в котором будет создан класс
        context: window,
        // строка/конструктор от которого будем наследоваться
        extends: Bar,
        // строка/конструктор или список строк/конструкторов, из которых будут получены дополнительные свойства
        implements: [ IBase, IPersistent ]
    }, {
        constructor: function() {
            alert( "Foo constructor execute" );
        }
    });

    var a = new Foo();

    a.bar();
    alert( a.someField );
    alert( a.key );

</script>

B~Vladi 25.07.2012 15:43

Цитата:

Сообщение от devote
Дык так оно тоже работает, я же написал строка/конструктор

А, сорри.

x-yuri 25.07.2012 16:08

А теперь получается, что первый аргумент - имя класса, третий - прототип, а второй - свалка для всего остального, отчасти это статические свойства, отчасти параметры для функции. Я бы тогда поместил статические свойства в отдельный параметр и убрал двойное подчеркивание:
Class("Foo", {
    context: window,
    extends: "Bar",
    implements: [ "IBase", "IPersistent" ],
    static: {
        STATIC_PARAM: 1,
        STATIC_CONST: "lala"}
}, {
    // code
});


Кстати, можно как-то в прототипе указать конструктор? Или надо для этого указывать функцию вместо объекта?

Ну и, предыдущий интерфейс остался же? Я считаю, не стоит стремится всем угодить.

P.S. Я не очень старался, но я не въехал в код угадывания какой входной параметр что обозначает. ;)

devote 25.07.2012 16:18

Цитата:

Сообщение от x-yuri
Кстати, можно как-то в прототипе указать конструктор? Или надо для этого указывать функцию вместо объекта?

хм.. не совсем понял что имеется ввиду. конструктор выглядит так:
Class("Foo", {
    context: window,
    extends: "Bar",
    implements: [ "IBase", "IPersistent" ],
}, {
    // это и есть конструктор, который сработает при создании экземпляра класса
    constructor: function( test ) {
        alert( test );
    }
});
new Foo( "Hello!" );
можно писать и так:
Class("Foo", {
    context: window,
    extends: "Bar",
    implements: [ "IBase", "IPersistent" ],
}, function() {
    // тут можно поместить приватные переменные, для каждого экземпляра они свои собственные.
    var private = "tarata";

    return {
        // это и есть конструктор, который сработает при создании экземпляра класса
        constructor: function( test ) {
            alert( test );
        }
    }
});
new Foo( "Hello!" );

Цитата:

Сообщение от x-yuri
Ну и, предыдущий интерфейс остался же? Я считаю, не стоит стремится всем угодить.

Я и не пытаюсь угодить всем, я пытаюсь сделать так что бы не переписывать уже написанные свои классы, я их уже штук 50 начирикал. Лениво переиначивать :(

Цитата:

Сообщение от x-yuri
Я не очень старался, но я не въехал в код угадывания какой входной параметр что обозначает.

А гадать и не надо, ты и так правильно все понял.

Первый Имя,
Второй свалка,
Третий объект класса

либо

Первый Имя,
Второй объект класса

x-yuri 25.07.2012 16:48

Цитата:

Сообщение от devote
А гадать и не надо, ты и так правильно все понял.

Ну я как бы о поддержке кода задумался... Если там изменить чего надо будет...

devote 25.07.2012 16:56

Цитата:

Сообщение от x-yuri
Ну я как бы о поддержке кода задумался... Если там изменить чего надо будет...

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

B~Vladi 25.07.2012 16:59

Цитата:

Сообщение от devote
Первый Имя,
Второй свалка,
Третий объект класса

либо

Первый Имя,
Второй объект класса

Ну да, jQuery-way. Так было бы красивее, имхо:

Первый Имя,
Второй объект класса,
Третий свалка (необязательный параметр)

devote 25.07.2012 17:11

Цитата:

Сообщение от B~Vladi
Так было бы красивее, имхо:

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

B~Vladi 25.07.2012 17:28

Цитата:

Сообщение от devote
возможно, но когда у вас будут огромных размеров классы, не особо удобно будет открывая файл, матать в самый низ что бы посмотреть от чего он унаследован и т.д. И есть ли вообще доп параметры.

Ну ок.

Тогда следующее тролль-предложение: объединить доп. параметры с "прототипом". Т.к. это и не совсем прототип, из-за свойства constructor, то и доп. параметры вполне неплохо туда впишутся.

devote 25.07.2012 17:39

Цитата:

Сообщение от B~Vladi
объединить доп. параметры с "прототипом".

а вот это я пока не знаю как реализовать, так как данные в прототипе, не доступны до тех пор пока не будет создан экземпляр класса. И вытащить из него что-то будет тяжко в том случае если его передадут таким образом:
Class("Foo", function() { // функция врапер
    var private = "1";
    return {
        __extends: "Bar",
        constructor: function() {
        }
    }
});
вот параметр __extends мне не будет доступен в момент объявления класса, так как он будет завернут в функцию, а что бы его получить придется инициализировать приватные параметры. То-есть вызвать функции врапер. что не есть гуд, потому как я вызываю его лишь когда создают экземпляр

B~Vladi 25.07.2012 17:51

Цитата:

Сообщение от devote
а вот это я пока не знаю как реализовать, так как данные в прототипе, не доступны до тех пор пока не будет создан экземпляр класса.

Ок, понятно. Если нужна поддержка функции-враппера, то да.

x-yuri 25.07.2012 18:52

Цитата:

Сообщение от devote
ну изменения в своих классах можно любые делать, а если ты имел ввиду про изменения в ядре создания классов, хм.. не думаю что это кому то навредит.

Говоря прямо, я о тебе забочусь ;) и тех кто решит заглянуть в исходник

Цитата:

Сообщение от devote
возможно, но когда у вас будут огромных размеров классы, не особо удобно будет открывая файл, матать в самый низ что бы посмотреть от чего он унаследован и т.д. И есть ли вообще доп параметры.

В vim никуда мотать не надо будет, в идеале, я могу переместиться в конец класса с помощью одного нажатия клавиши. Все зависит от того, где курсор расположен. Что же касается расположения параметра с опциями, я бы все же на втором месте его расположил. Два варианта вызова - это не так сложно, по сравнению с 12-ю. Разве что не мешал бы статические свойства и опции (см. выше).

Цитата:

Сообщение от devote
То-есть вызвать функции врапер. что не есть гуд, потому как я вызываю его лишь когда создают экземпляр

А почему только при создании? Почему бы не вызвать его один раз при объявлении и запомнить полученную информацию?

devote 25.07.2012 19:07

Цитата:

Сообщение от x-yuri
Говоря прямо, я о тебе забочусь и тех кто решит заглянуть в исходник

Ну там с эти проблем вроде как нету.

Цитата:

Сообщение от x-yuri
Разве что не мешал бы статические свойства и опции (см. выше).

но ведь опций не так много, разве это может навредить? да и статические параметры объявляются лишь раз при объявлении класса. А больше к ним и не притрагиваемся в будущем при создании экземпляров.

Цитата:

Сообщение от x-yuri
Почему бы не вызвать его один раз при объявлении и запомнить полученную информацию?

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

x-yuri 25.07.2012 22:21

Цитата:

Сообщение от devote
но ведь опций не так много, разве это может навредить?

Получается, что в параметра хранится 2 вида информации: опции и статические свойства. Хотя бы, как назвать такой параметр? optionsAndStaticData?

Цитата:

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

Получается,

1. Ты сам создаешь конструктор, который вызывает сначала функцию-обертку, а потом конструктор указанный пользователем? Другими словами функция-обертка - часть конструктора?

2. Попытавшись просмотреть код конструктора, я получу код твоей функции, а не той, которую я передал в Class?

devote 25.07.2012 23:36

Цитата:

Сообщение от x-yuri
Хотя бы, как назвать такой параметр? optionsAndStaticData?

над этим я еще подумаю конечно.

Цитата:

Сообщение от x-yuri
1. Ты сам создаешь конструктор, который вызывает сначала функцию-обертку, а потом конструктор указанный пользователем? Другими словами функция-обертка - часть конструктора?

можно сказать и так, поэтому встроенный instanceof не работает с этими классами, это в первую очередь идут проблем из ИЕ, так как там юзаем VB и он то нам и выдает кучу гемора. Вот поэтому Class.Foo.prototype.constructor не является тем что хотелось бы ожидать.

Цитата:

Сообщение от x-yuri
Попытавшись просмотреть код конструктора, я получу код твоей функции, а не той, которую я передал в Class?

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

devote 26.07.2012 08:47

Очередное обновление, сделал так как предложил x-yuri,
Class("Foo", {
    context: window,
    extends: "Bar",
    implements: [ "IBase", "IPersistent" ],
    static: {
        STATIC_PARAM: 1,
        STATIC_CONST: "lala"
    }
}, {
    // code
});

x-yuri 27.07.2012 00:22

Цитата:

Сообщение от devote
можно сказать и так, поэтому встроенный instanceof не работает с этими классами, это в первую очередь идут проблем из ИЕ, так как там юзаем VB и он то нам и выдает кучу гемора. Вот поэтому Class.Foo.prototype.constructor не является тем что хотелось бы ожидать.

А в чем прикол двух-конструкторов? При чем первый (обертка) ведь не имеет доступа к параметрам вызова.

devote 27.07.2012 00:32

Цитата:

Сообщение от x-yuri
А в чем прикол двух-конструкторов?

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

x-yuri 27.07.2012 01:22

Цитата:

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

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

B~Vladi 27.07.2012 09:49

Цитата:

Сообщение от x-yuri
На мой взгляд толку от приватных свойств мало, больше вреда

Согласен.

devote 27.07.2012 10:20

Цитата:

Сообщение от x-yuri
На мой взгляд толку от приватных свойств мало, больше вреда, если к ним вдруг извне понадобилось получить доступ: банально при отладке, или может быть для тестов. Но будем считать, что я этого не говорил...

спорить не будут, в чем то ты и прав, но это все равно что говорить, зачем изоляция на проводах, ведь и без нее провода могут делать то что делают. И к любому свойству при желании можно получить доступ. Но тут все же есть и плюсы, да я понимаю что открыв исходник можно и приватные свойства открыть. Но это лишь временное решение, до первого обновления, да и защита от дурака. Если давать людям все поголовно, то и тупых вопросов будет на тех же форумах и т.д. А так ты просто избавляешь себя от нападения вопросов. Банально простой пример, открыв свойство тебя спросят что это. А закрыв никто не спросит.

Но это мое ИМХО, и если все же приватные свойства были бы не к чему, их бы не использовали бы нигде.

x-yuri 27.07.2012 12:09

Есть такая штука как, назовём это, приватность по соглашению:
Class('MyClass', {
    myPublicMethod: function() {...},
    _myPrivateMethod: function() {...}

Так что никто спрашивать не будет. А по поводу защиты от случайной ошибки, то выглядит она сомнительно, непонятно, насколько часто она бы срабатывала.

Цитата:

Сообщение от devote
Но это мое ИМХО, и если все же приватные свойства были бы не к чему, их бы не использовали бы нигде.

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

devote 29.01.2013 16:00

Всем снова привет!

Сегодня влил новую стабильную версию библиотеки. В новой версии были внесены некоторые изменения и нововведения.

Что нового?
1. Была переработана система наследования, теперь множественное наследование это, все равно что обычное наследование.
2. Была введена отдельная инкапсуляция для аксессоров.
3. Теперь конструкторы можно называть именем класса.

Что изменилось?
1. Теперь контекст классов по умолчанию всегда глобальный.
1. Теперь при обращении к аксессору не имеющего getter'а срабатывает исключение, то же самое и с setter'ом.

Эти добавления и изменения заставили переписать систему построения наследования почти с нуля. Так же это сказалось на оптимизации, скорость построения классов увеличилась, размер кода остался почти без изменений (то есть мне удалось внести нововведения и изменения без огромного увеличения размера библиотеки).

Примеры:
  • Множественное наследование
    /**
     * @class Foo
     */
    Class("Foo", {
        /**
         * @constructor
         * @constructs
         */
        Foo: function() {
            alert("Executed constructor: " + this.__class__.className);
        }
    });
    
    /**
     * @class Bar
     * @extends Foo
     */
    Class("Bar", "Foo", { // наследуем от Foo
        /**
         * @constructor
         * @constructs
         */
        Bar: function() {
            this.parent.constructor();
            alert("Executed constructor: " + this.__class__.className);
        }
    });
    
    /**
     * @class Biz
     */
    Class("Biz", {
        /**
         * @constructor
         * @constructs
         */
        Biz: function() {
            alert("Executed constructor: " + this.__class__.className);
        }
    });
    
    /**
     * @class Baz
     * @extends Biz
     */
    Class("Baz", "Biz", {
        /**
         * @constructor
         * @constructs
         */
        Baz: function() {
            this.parent.constructor();
            alert("Executed constructor: " + this.__class__.className);
        }
    });
    
    
    /**
     * @class Mix
     * @extends Bar
     * @implements Baz
     */
    Class("Mix", "Bar, Baz", {
        /**
         * @constructor
         * @constructs
         */
        Mix: function() {
            this.parent.constructor();
            alert("Executed constructor: " + this.__class__.className);
    
            // вызываем коструктор класса указанного в параметре implements
            this.Baz();
        }
    });
    
    var obj = new Mix(); // alerts Foo, Bar, Mix, Biz, Baz
    

    Структура в отладчике:


  • отдельная инкапсуляция для аксессоров
    /**
     * @class Foo
     */
    Class("Foo", {
    
        /**
         * @type {String}
         * @property
         * @public
         */
        name: {
            // инкапсуляция
            value: "DefaultValue", // снаружи нет доступа к этому значению
    
            set: function(value, self) { // self это ссылка на объект свойства name
                self.value = value;
            },
            get: function(self) {
                return self.value;
            }
        }
    });
    
    var obj = new Foo();
    alert(obj.name); // alerts "DefaultValue"
    
    obj.name = "NewValue"; // executed setter
    
    alert(obj.name); // alerts "NewValue"
    
  • конструкторы можно называть именем класса.
    Это вы уже увидели из примеров выше

Основную документацию вы найдете в первом топике этой темы.

На любые вопросы с большим удовольствием отвечу. Пожелания, выслушаю. Всем спасибо!

Скачать как всегда тут: https://github.com/devote/jsClasses

devote 29.01.2013 16:12

В пост выше, добавил рисунок структуры объекта, получившегося в отладчике Dragonfly.

x-yuri 30.01.2013 14:02

Цитата:

Сообщение от devote
спорить не будут, в чем то ты и прав, но это все равно что говорить, зачем изоляция на проводах, ведь и без нее провода могут делать то что делают.

Безусловно, какая-то защита от дурака должна присутствовать. Но ее можно значительно снизить, выбирая окружение.

devote 30.01.2013 14:36

x-yuri,
решил спустя пол года ответить на пост?)))

x-yuri 30.01.2013 18:03

ну ты ж вчера воскресил тему, вот я и наткнулся на твою фразу ;)

devote 30.01.2013 18:52

Цитата:

Сообщение от x-yuri
вот я и наткнулся на твою фразу

странно что ранее на нее не ответил, хотя ты ее видел ранее

devote 01.02.2013 17:17

Снова всем привет!

Для тех кому не похуйфиг, вылил очередное обновление:
  1. Теперь функция Class вернет экземпляр класса если перед ней будет стоять оператор new, в противном случае вернет сам класс. Пример:
    // стандартное использование, вернет класс
    var MyClass = Class({
        test: "Hello"
    });
    var instance = new MyClass; // создаем экземпляр
    alert(instance.test); // alerts "Hello"
    
    // новая возможность, не нужно создавать экземпляр
    var instance = new Class({
        test: "Hello"
    });
    alert(instance.test); // alerts "Hello"
    
  2. Добавлен параметр compact, который дает возможность убрать/удалить из объекта внутренние свойства __class__ и parent. Пример:
    // при обычном использовании без параметра "compact"
    var obj = new Class({
        myProperty: "ololo"
    });
    for(var key in object) {
        alert(key); // alerts "myProperty", "parent", "__class__"
    }
    
    // новая возможность, с использованием параметра "compact"
    var obj = new Class({compact: true}, {
        myProperty: "ololo"
    });
    for(var key in object) {
        alert(key); // alerts "myProperty"
    }
    
При использовании параметра compact, свойство parent не будет доступно, и конечно же нельзя будет вызвать перекрытый метод родительского класса. В нормальных браузерах можно будет использовать метод .getPrototypeOf() но для ИЕ с его VBScript'ом вряд ли получится.

Все прочее как всегда в первом посте этого треда.


Часовой пояс GMT +3, время: 16:26.