JsTestDriver

Ну что я могу сказать? JsTestDriver — та еще поделка. Всё работает хорошо до тех пор, пока в браузере не возникнет исключение. В этом случае всё подвисает и не отвечает, пока не перезапустишь сервер. Например, если у тебя в коде стоит вызов a.b() и a === undefined, то пиши пропало. Будешь искать ошибку до скончания веков. Хотя вроде в новых IDEA и PhpStorm обещают поддержку дебаггера юнит-тестов для JsTestDriver. Посмотрим, что из этого выйдет.

Inherited.js design solutions

Итак, я написал некоторое количество тестов, поправил несколько багов. Пришла пора задуматься: что же делать дальше? Как должно выглядеть 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

Поступило предложение переименовать главную функцию в inherited.js cо слишком длинного createClass на более короткое __o__ или $_$. Как вам идея? Может, будут другие предложения?

inherited.js v0.1

Как я и обещал, выкладываю в общий доступ библиотеку для создания классов в 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, с помощью которого можно вызвать соответствующий метод предка.

Кажется всё. Если есть вопросы, мне всегда можно написать.

Чтобы вы имели представление, как это выглядит, вот несколько скриншотов:

Консоль Web Inspector в Google Chrome

Консоль Web Inspector в Google Chrome

Консоль Firebug в Firefox

Консоль Firebug в Firefox

Просмотр объекта в Firebug

Просмотр объекта в Firebug

В самом ближайшем обновлении библиотеки я планирую подружить ее с JsTestDriver. Слишком уж много у библиотеки мелких деталей, с которыми приходится постоянно ладить.

Именование объектов в JavaScript

Вы же помните предыдущий пост про библиотеку для создания объектов на 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.

← СтаршеМоложе →