Ну что я могу сказать? JsTestDriver — та еще поделка. Всё работает хорошо до тех пор, пока в браузере не возникнет исключение. В этом случае всё подвисает и не отвечает, пока не перезапустишь сервер. Например, если у тебя в коде стоит вызов a.b()
и a === undefined
, то пиши пропало. Будешь искать ошибку до скончания веков. Хотя вроде в новых IDEA и PhpStorm обещают поддержку дебаггера юнит-тестов для JsTestDriver. Посмотрим, что из этого выйдет.
Итак, я написал некоторое количество тестов, поправил несколько багов. Пришла пора задуматься: что же делать дальше? Как должно выглядеть API будущей библиотеки? Пожалуй, я выложу несколько вариантов своего видения, чтобы вы могли высказать свои за и против. Очень надеюсь на ваши комментарии.
Вариант 1:
NewClass = Class.extend("OptionalDisplayName", {
constructor: function () {},
properties: {
property1: {
set: function(value) {this.properties_.property1 = value;},
get: function() {return this.properties_.property1;},
initial: 'Initial value'
},
property2: { // Property with default getter and no setter
get: true
}
},
statics: {
staticField1: null,
staticMethod1: function(arg1) {return arg1;},
properties: {
staticProperty1: {get: true, set: true}
}
},
field1: null,
method1: function() {}
});
NewClass2 = NewClass.extend({
constructor: function () { this.inherited(); },
method1: function() { this.inherited(); },
method2: function() {}
});
Вариант 2:
NewClass = createClass({
__name__: "OptionalDisplayName",
constructor: function () {},
property1: __property__({
set: function(value) {this.properties_.property1 = value;},
get: function() {return this.properties_.property1;},
initial: 'Initial value'
}),
property2: __property__({ // Property with default getter and no setter
get: true
}),
staticField1: __static__(null),
staticMethod1: __static__(function(arg1) {return arg1;}),
staticProperty1: __staticProperty__({get: true, set: true}),
field1: null,
method1: function() {}
});
NewClass2 = createClass({
constructor: function () { this.inherited(); },
extend: NewClass,
method1: function() { this.inherited(); },
method2: function() {}
});
Какой из вариантов кажется более логичным и удобным? Может, составить какой-то смешанный вариант или у вас есть предложения, кардинально отличающиеся от этих двух? Не стесняемся, пишем комментарии.
Поступило предложение переименовать главную функцию в inherited.js cо слишком длинного createClass
на более короткое __o__
или $_$
. Как вам идея? Может, будут другие предложения?
Как я и обещал, выкладываю в общий доступ библиотеку для создания классов в JavaScript. Исходный код можно посмотреть на Github.
Пример использования:
TestA = createClass(
/** @lends TestA */
{
name: 'st.a.very.long.namespace.TestA',
/** @constructs */
constructor: function () {
console.log('Constructor TestA');
},
testMethod1: function () {
console.log('TestA.testMethod1');
},
/**
* @param {number} param
*/
testMethod2: function (param) {
console.log('TestA.testMethod2 ' + param);
}
}
);
/**
* @extends TestA
*/
TestB = createClass(
/** @lends TestB */
{
name: 'st.TestB',
/** @constructs */
constructor: function TestB() {
console.log('Constructor TestB');
this.inherited();
},
extend: TestA,
testMethod1: function () {
this.inherited();
console.log('TestB.testMethod1');
},
/**
* @param {number} param
*/
testMethod2: function (param) {
this.inherited(param + 1);
console.log('TestB.testMethod2 ' + param);
}
}
);
var testB = new TestB;
console.log(testB);
testB.testMethod1();
testB.testMethod2(1);
Как видно, функция createClass
возвращает класс. В нее передается объект с описанием класса. На данный момент у этого объекта есть три зарезервированных поля: name
, constructor
и extend
. Всё остальное переносится в результирующий класс в своем первоначальном виде. В будущем я планирую расширить список зарезервированных полей еще несколькими, например, static
, properties
и singleton
.
Теперь немного пройдемся по этим самым специальным полям. Поле name
— это имя класса, которое будет отображаться в веб-инспекторе (смотрите предыдущий пост); constructor
— функция, которая будет выполнять роль конструктора. Ну и extend
— класс-предок. К каждому классу добавляется служебный метод inherited
, с помощью которого можно вызвать соответствующий метод предка.
Кажется всё. Если есть вопросы, мне всегда можно написать.
Чтобы вы имели представление, как это выглядит, вот несколько скриншотов:
В самом ближайшем обновлении библиотеки я планирую подружить ее с JsTestDriver. Слишком уж много у библиотеки мелких деталей, с которыми приходится постоянно ладить.
Вы же помните предыдущий пост про библиотеку для создания объектов на JavaScript? Так вот, я начал разбираться, откуда браузер может брать имена для классов. Как и ожидалось, разработчики браузеров даже и не пытались создать какой-нибудь общий стандарт, и каждый работает так, как ему хочется. Мне удалось обнаружить 2 способа задания имени класса (или 3, смотря как считать). Я, конечно же, могу заблуждаться, и вполне себе возможно существование каких-нибудь других недокументированных вариантов задания имени класса. В комментариях можно мне про это сказать.
Лучше всего дела обстоят у Chrome. Кто бы мог подумать? Он берет имя из функции конструктора вне зависимости от вида записи.
some.namespace.ClassA = function () {};
console.log(new some.namespace.ClassA()); // some.namespace.ClassA
function ClassB () {}
console.log(new ClassB()); // ClassB
var ClassC = function ClassD () {}
console.log(new ClassC()); // ClassD
console.log(new ClassD()); // error: undefined
Вслед за ним идет Firefox с Firebug. Он понимает только указание имени после слова function.
some.namespace.ClassA = function () {};
console.log(new some.namespace.ClassA()); // Object
function ClassB () {}
console.log(new ClassB()); // ClassB
var ClassC = function ClassD () {}
console.log(new ClassC()); // ClassD
console.log(new ClassD()); // error: undefined
С IE и Opera дела обстоят одинаково. Они подписывают только названия встроенных объектов.
some.namespace.ClassA = function () {};
console.log(new some.namespace.ClassA()); // Object
function ClassB () {}
console.log(new ClassB()); // Object
var ClassC = function ClassD () {}
console.log(new ClassC()); // Object
console.log(new ClassD()); // error: undefined
Всё это наводит на мысль, что простым и очевидным способом создавать объекты не получится. Сначала нам понадобится определить браузеров, так как каких-то конкретных доступных фич в данном случае нет. А поскольку единственная возможность задать имя класса — напрямую его указать, то eval
будет нашим лучшим другом. И уже из этого последует тонкая настройка Google Closure Compiler, чтобы он не трогал важные для выполнения скрипта переменные.
Поделюсь секретом: создание классов уже работает, и в скором времени, после еще некоторых правок, я выложу версию 0.1 библиотеки на Github.