суббота, 8 октября 2011 г.

Обогащение (enrichment) или псевдо-конструктор (pseudo-constructor) как JavaScript-pattern

Вторым открытым при разработке библеотеки Drag and Drop приёмом, который я бы назвал шаблоном, является обогащение (enrichment). Ещё я иногда про себя называю его псевдо-конструктор (pseudo-constructor), что считаю менее удачным, но так же подходящим для него названием.

Он служит для ситуаций, когда нужно придать некоторую дополнительную функциональность группе объектов одинакового вида.

Я столкнулся с этой проблемой при работе с тем же самым объектом Event для IE. Дело в том, что продемонстрированный в позапрошлой статье приём "Заплатка" (Patch) был несколько неполным. Напомню приведённый код:

(function setAddEventListener(){
    if ('attachEvent' in this && !('addEventListener' in this))
        addEventListener = function(eventName, handler, isCapturing) {
            if (isCapturing)
                throw new Error("We are in IE, so we haven`t way to set event listener on capturing phase of any event to any of HTML-elements");
            attachEvent("on" + eventName, handler);
    }
})(); //IE patch for window.addEventListener
В этом коде, к сожалению, не было учтено, что для придания стандартного поведения функции addEventListener нужно, что бы она вызывала слушателя, передавая ему объект Event, при чём это должен быть объект Event стандартного вида.

Но в случае, если выполнение сценария происходит в IE, у нас есть только IE`шный Event, который отличается от стандартного достаточно сильно. Так что не достаточно просто подставить при вызове его - нужно обязательно придать ему нужную функциональность.

Имитация функциональности стандартного объекта Event на почве IE`шной реализации сама по себе является сложной задачей и у меня нет уверенности, что она действительно представляет для читателей практический интерес, так что я просто проиллюстрирую принцип, показав скелет решения, а уже "наполнение мясом", каждый может делать для себя сам - по крайней мере для меня эта задача пока не актуальна. Так что упростим задачу - возьмём лишь один аспект нестандартного поведения и его сэмулируем. Например, отмена поведения браузера по-умолчанию для данного события. В стандартном W3C`шном объекте Event это действие производится вызовом метода preventDefault, а в IE - установкой значения false в свойство returnValue этого кода.

Итак, имеем нестандартный Event, который нужно сделать стандартным Event`ом. Для выбранного нами аспекта это означает, что ему нужно приделать такую функцию:

/** Отменяет поведение браузера для данного события по-умолчанию. */
event.preventDefault = function(){
    this.returnValue = false;
};
Это нельзя сделать раз и навсегда, поскольку сам объект постоянно (при наступления каждого нового события) меняется. Тут мы, кстати, видимо, что в основу этой модели уже заложена однопоточность выполнения JavaScript-сценариев, которую сейчас так активно критикует JavaScript-сообщество. Так же однопоточность заложена в основу на этот раз стандартного конструктора RegExp. Первое, что приходит на ум, это просто приписать этому объекту несколько новых свойств "не отходя от кассы" - т.е. там, где это нам понадобится. Однако это будет иная логика, которая тем самым замусорит код - будет удобнее собрать все эти операции в одном, отдельном месте.

Следующая мысль, уже более зрелая - создать отдельный конструктор, который наследовался бы от нестандартного Event`а и расширял бы его функционал стандартными свойствами и методами:

/** Конструктор, служащий для придания не совместимым с W3C DOM level 3 объектам
 * Event стандартного поведения.
 * @constructor
 * @extends Event */
function EventW3C() {
    /** Отменяет поведение браузера для данного события по-умолчанию. */
    this.preventDefault = function() {
        this.returnValue = false;
    };
};
Вызов же этого конструктора в нужном месте предполагается примерно такой:
EventW3C.prototype = event; //Сначала присваиваем правильный прототип конструктору
var w3cEvent = new EventW3C(); //Затем создаём экземпляр, который будет ссылаться ссылкой __proto__ куда надо.
После этого получим объект, поведение которого соответствует стандартному и который уже можно спокойно передавать функции, не подозревающей, что она выполняется в нестандартном (IE6-8) браузере.

Однако нетрудно увидеть, что этот приём излишне-расточителен. Фактически, для каждого события он создаёт не один, а два объекта в памяти, так что сборщику мусора будет в два раза больше работы. Кроме того, само по себе создание объектов - не такая уж быстрая операция и хотелось бы по возможности её избегать, если это не сильно оправданно ситуацией. Не говоря уже о том, что механизм вызова из двух выражений выглядит излишне-громоздким.

Гораздо более экономным является такой вариант - можно вызвать эту функцию не при помощи оператора new, а при помощи метода call:

EventW3C.call(event);
и объект сам, без создания наследника, получит нужное свойство. Именно из-за того, что по форме это очень напоминает использование конструктора, я про себя иногда и называю этот шаблон псевдо-конструктором (pseudo-constructor).

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

function EventW3C() {
    /** Отменяет поведение браузера для данного события по-умолчанию. */
    this.preventDefault = function() {
        this.returnValue = false;
    };
    return this;
};
Для использования этой функции в качестве реального конструктора это ровным счётом ничего не изменит, а вот для использования её в качестве псевдо-конструктора это может сделать вызов более удобным - тогда само выражение вызова будет возвращать результат, прямо как у реального конструктора:
var w3cEvent = EventW3C.call(event);
На последок приведу полный вариант реализации pattern`а "Заплатка" ("Patch") для эмуляции addEventListener`а, который использует приведённый pattern Обогащение (Enrichment) или псевдо-конструктор (Pseudo-Constructor):
//IE patch for window.addEventListener
function setAddEventListener()
{
    if ('attachEvent' in this && !('addEventListener' in this))
        addEventListener = function(eventName, handler) {
            if (isCapturing)
                throw new Error("We are in IE, so we haven`t way to set event listener on capturing phase of any event to any of HTML-elements");
            attachEvent("on" + eventName, function() {
                handler(EventW3C.call(event) //Здесь используется pattern "Enrichment"
            });
        }
}
setAddEventListener.call(document);// Применяем заплатку для объекта document
Конечно, полученные применением этого шаблона объекты не являются в строгом смысле потомками этого конструктора, о чём не применит сообщить операция instanceof, но для практического применения это чаще всего не нужно, в то время как с полученными в результате этого объектами фактически можно обращаться как с наследниками. Так что для с одной стороны - экономии ресурсов, а с другой - удобства написания красивого и лаконичного кода без засорения логики, этот шаблон, на мой взгляд, прекрасно подходит.

P.S. Думается, что данный шаблон имеет так же большой потенциал применения для добавления свойств объектам DOM, если нужно придать им нужную функциональность. По крайней мере сейчас я прорабатываю эту идею как раз в контексте своей библиотеки - она поможет пользователям навешивать несколько обработчиков событий в ходе Drag&Drop-перемещения.

P.P.S. Пришло в голову, когда уже собирался опубликовать сообщение - а может, лучше будет назвать этот приём - Псевдо-наследование ("Pseudo-Inheritance")? Пишите в комментах Ваши версии :)

пятница, 7 октября 2011 г.

Самонастраивающаяся функция

В ходе написания второй версии библиотеки для Drag&Drop`а (уже скоро выложу и опубликую, ждите - осталось недолго :) ) была найдена парочка интересных JavaScript`овых решений, которые тоже вполне могут претендовать на звание Pattern`ов. Рискуя тем, что, возможно, изобретаю велосипед, всё-таки распишу, как я их использую.

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

Конечно, можно сделать в этой функции большой оператор ветвления if с тем, что бы в случае одних параметров выполнять одну логику, а в случае других - другую. Но эта операция проверки будет отнимать дополнительное время, так что это приемлемо лишь для не очень критичной к скорости работы логики.

Именно с такой задачей я имел дело при написании второй версии своей библиотечки. Мне нужна была функция, которой передавался бы объект pos, имеющий два свойства - 'x' и 'y' и от неё требовалось, что бы она проставила в них значения x и y-координат курсора мыши. Я назвал её refreshMousePos.

Саму эту функцию должен вызывать обработчик того или иного события, который в W3C-совместимых браузерах получает ссылку на объект Event, соответствующий данному событию. Для вычисления координат курсора мыши этот объект нужен, так что в этом случае ссылку на него нужно передать в refreshMousePos вторым аргументом. В случае же W3C-несовместимого браузера (в основном, IE6-8), обработчику не передаётся этот объект, по-этому он и не может быть передан в функцию refreshMousePos.

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

Но заранее определить точно, будет или не будет передан объект Event в функцию, затруднительно - гораздо удобнее это сделать в процессе работы, по факту проверив содержимое переданной переменной. При чём, это достаточно сделать один раз и сразу будет понятно, как функция должна выполняться впоследствии.

Сам IE меняет в соответствии с обрабатываемым событием объект event, который всегда находится у него в глобальном контексте. И интерфейс этого объекта несколько иной, что проявляется в логике вычисления координат курсора мыши.

Так что я сделал разделение логики следующим образом:

/** Обновить координаты мыши.
 * @param {Position} pos координаты курсора мыши
 * @param {Event} [evt] объект события */
function refreshMousePos(pos, evt) {
    return (refreshMousePos = typeof evt !== 'undefined' ?
        function(pos, evt) { //W3C realization
            var body = document.body;
            pos.x = evt.clientX + body.scrollLeft - body.clientLeft;
            pos.y = evt.clientY + body.scrollTop - body.clientTop;
            return pos;
        } :
        function(pos) { //IE realization
            pos.x = event.pageX;
            pos.y = event.pageY;
            return pos;
        }
    )(pos, evt); //вызываем тот вариант функции, которую присвоили и возвращаем результат выполнения
};
Как видим, здесь переменной, которая содержит главную функцию, в зависимости от содержания ссылки evt присваивается различное значение-функция, при этом выполняющаяся в данный момент функция автоматически затирается. Т.е. функция, будучи вызванной, как бы более тонко настраивает себя под конкретную среду в которой оказалась для более производительной работы в ней.

P.S. Спросите, зачем я возвращаю объект pos, ведь его поля итак уже изменены и значит, необходимый внешний эффект достигнут? Отвечу - для того, что бы можно было после вызова функции сразу же в той же конструкции обратиться к объекту pos:

alert(
    refreshMousePos(pos, evt).x
);

пятница, 16 сентября 2011 г.

"Заплатка" ("Patch") как JavaScript шаблон проектирования

Недавно пришла в голову мысль, что "Заплатка" вполне заслуживает звания JavaScript-шаблона проектирования. По-англицки можно было бы назвать "Patch". Суть его не собственно в хаке, а скорее в том, что бы отделять код, написанный в соответствии со стандартом, от кода, который приспосабливает браузер, не поддерживающий стандарты, корректно работать со стандартным кодом при помощи того или иного хака.

Сам я использую заплатки уже достаточно давно. Разумеется, основные заплатки ставятся на IE6-8, с которыми, по-видимому, ещё предстоит много возни, пока они, наконец, отомрут. Однако так же не помешает установить их и для старых версий иных браузеров.

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

function onLoadListener() {
    //some code...
}
if ('addEventListener' in window)
    addEventListener("load", onLoadListener, false);
else if ('attachEvent' in window)
    attachEvent("onload", onLoadListener)
В принципе, ничего нет плохого в том, что бы так делать, однако проблема заключается в том, что при написании сложных сценариев ваша голова итак забита сложностью решаемой задачи и Вам не захочется отвлекаться ещё и на все эти глупые браузеросовместимости - вам бы хоть как-то её решить. И потом, при отладке и чтении кода вам всё время будет попадаться на глаза этот фрагмент и мозолить глаза. С точки зрения алгоритма он не несёт никакого смысла, а на экране занимает место, которое могли бы занять более полезные смысловые фрагменты кода - видя их одновременно, без прокрутки, Вам могла бы придти в голову ценная мысль, которая не пришла бы, если бы вам пришлось вращать колесо мыши, что бы всё увидеть.

Тем не менее, сократить эту конструкцию до стандартного кода

addEventListener("load", function() {
    //some code...
}, false);
мы не можем, поскольку стандарты поддерживают не все браузеры, в которых нам хотелось бы, что бы наш сценарий успешно выполнялся. Для остальных же браузеров у нас есть многочисленные хаки. Так как же быть?

Как только я в своих проектах сталкиваюсь с несовместимостью работы того или иного браузера со стандартными методами, первая мысль у меня возникает о том, что бы вынести обеспечение совместимости в отдельный от основного код, что бы он его не захламлял. У меня уже накопилось достаточно много таких заплаток, латающих различные дыры в поддержке стандартов различным браузерами - главным образом IE. Обычно я помещаю их в отдельный файл проекта, называя его "commons.js" (есть и исключения - например в случае, если это нужно только для тестирования, я считаю не зазорным вставлять такой код просто в начало тестового файла).

В данном случае полезной была бы следующая заплатка:

(function setAddEventListener()
{
    if ('attachEvent' in this && !('addEventListener' in this))
        addEventListener = function(eventName, handlerб isCapturing) {
            if (isCapturing)
                throw new Error("We are in IE, so we haven`t way to set event listener on capturing phase of any event to any of HTML-elements");
            attachEvent("on" + eventName, handler);
        }
})(); //IE patch for window.addEventListener
Здесь элегантно используется то обстоятельство, что ссылка this при вызове в глобальном контексте в функции указывает на объект window. По-этому сразу же после объявления функция просто вызывается.

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

Если необходима реализация парного метода removeEventListener, её легко написать по аналогии, реализовав через метод detachEvent.

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

setAddEventListener.call(document.getElementById('id1'));
В статье "JavaScript: Реализация pattern`а Singleton" я уже приводил ещё одну заплатку - для безпроблемного объявления объекта XMLHttpRequest (который я нашёл в англоязычной Wikipedia):
// Provide the XMLHttpRequest class for IE 5.x-6.x:
if (typeof XMLHttpRequest == "undefined")
    /** @constructor */
    XMLHttpRequest = function() {
        try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
        try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
        try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
        try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
        throw new TypeError( "This browser does not support XMLHttpRequest." )
    };
Напоследок приведу ещё одну заплатку. Она служит для безпроблемной реализации стандартного метода window.getComputedStyle, отсутствующего в IE вплоть до 8 версии включительно. Кто не знает, это очень удобный метод, позволяющий получать ссылки на объект, содержащий информацию о "вычислимых стилях" для HTML-элемента. Вычислимым называется стиль, специально не установленный разработчиком, однако получившийся в результате вывода браузером этого элемента на странице. IE вместо него имеет свойство computedStyle, доступное у каждого элемента. К сожалению, оно не может представить вычислимый стиль для псевдоклассов, по-этому заплатку можно вставить так же, как и для addActionListener`а - лишь частичную.

Таким образом, вот как можно решить данную проблему несовместимости IE со стандартом:

//IE patch for window.getComputedStyle
if (!('getComputedStyle' in window))
    getComputedStyle = function(element, pseudoclass) {
        if (pseudoclass === null || typeof pseudoclass === 'undefined')
            return element.currentStyle;
        else
            throw new Error("We are in IE, so we haven`t way to get pseudoclass styles of element");
    };

четверг, 21 июля 2011 г.

Я скачал ЕЁ!!!

Самая лучшая книга по JavaScript всех времён и народов! Лучше неё ничего нет!
O`Reilly - Девид Фленаган, «JavaScript. Definitive Guide, 6th edition»(2011) - туда вошли все новшества от HTML5 и ECMAScript 5! :))))

Вот тут она есть - http://uploading.com/files/3735d769/Oreilly.j

Фленаган пишет очень подробно, для профессионалов.

Искал в электронном виде уже месяца два, кучу времени убил – нигде не было, хотя куча вирья всякого пыталась под её видом пролезть на манер файлов «JavaScript_Definitive_Guide,6th_edition.pdf.exe» с иконкой pdf`а... И вот, наконец, она – вот она!!!)

вторник, 31 мая 2011 г.

__proto__ во всех браузерах

Ссылка "__proto__", как известно, указывает на прототип объекта в браузерах FireFox, Google Chrome и Safari. Обычно ссылку на прототип можно получить комбинацией ссылок constructor и prototype, но когда используется классическое для JavaScript наследование:
function A(){/*...*/};
function B(){/*...*/};
B.prototype = new A();
var b = new B();
, то в этом случае вызов
b.constructor.prototype
укажет на прототип конструктора A, а не на созданный объект конструктора A, который передан прототипу:
alert(b.constructor.prototype === A.prototype);//'true'
. Как же в этом случае можно обратиться именно к прототипу объекта b (естественно, не зная, каков его конструктор)? Можно переопределить ссылку constructor у объекта, созданного по конструктору A, указав ей на B:
B.prototype.constructor = B;
, но тогда мы потеряем возможность добраться до прототипа конструктора A, что может так же понадобиться.
Так что остаётся только ссылка "__proto__" - больше никак. Но этой ссылки нет в некоторых браузерах - например, в IE и в Opera`е.
Но если чего-то нету, то можно это недостающее создать самим. Для того, что бы всё заработало везде, надо вставить конструкцию
if (!this.hasOwnProperty('__proto__'))
    /** @type A */ this.__proto__ = arguments.callee.prototype;
внутрь нашего конструктора-наследничка. И тогда всё получится:
function A(){/*...*/};
function B() {
    /*...*/
    if (!this.hasOwnProperty('__proto__'))
        /** @type A */ this.__proto__ = arguments.callee.prototype;
};
B.prototype = new A();
var b = new B();
alert(b.__proto__ === B.prptotype);//'true'
alert(b.constructor.prototype === A.prototype);//'true'

Навигация в Google Cache

Интернет - это целый мир. Сайты в нём как люди в реальном мире - рождаются, живут и умирают. Иногда то, что они умирают, может кому-то не понравиться. Тогда на помощь тем, кто "хотел, но не успел" сохранить те или иные материалы у себя, приходит Google Cache.
При поиске тех или иных страниц в интернете Вы наверняка замечали под каждой ссылкой с результатами поиска ссылочку "Сохранённая копия". Видели? Это и есть Google Cache. Если страницы уже и след простыл, она может всё ещё оказаться в этом хранилище.

Точно не знаю, сколько живут страницы в Кэше`e Google, прежде чем окончательно проститься с Internet`ом, но если хотелось их сохранить, то искать их можно там. Но вот незадача - отдельную страницу найти-то можно, но вот если пропал целый сайт и хочется погулять по этому сайту, покликать на ссылочки почитать много страниц в правильном порядке, то Вас ждёт неприятный сюрприз - все внутренние ссылки будут битые. Т.е. страницы-то в Google Cach`е есть, но вот навигация по ним очень неудобная - нужно каждую из них искать с помощью поисковика Google`а и переходить по ссылке "Сохранённая копия".

Буквально сегодня я столкнулся с тем, что великолепный сайт, посвящённый реальной медицине (а не этой дурацкой официальной, которая не лечит, а наоборот - медленно убивает людей) - http://www.revici.ru/ - исчез и теперь при наборе его адреса попадаешь на какую-то дурацкую рекламу фильмов.
На этом сайте было большое количество интересных материалов, соединённых ссылками. И при его сохранении из этого Кэша передо мной встала проблема перемещения по этим ссылкам.
Но, т.к. я программист, я быстро нашёл удобное решение. Дело в том, что страница из кэша google подгружается, если перед её адресом в адресной строке браузера вставить строку: "http://webcache.googleusercontent.com/search?q=cache:"
Так что я быстренько написал следующий коротенький скриптик:
javascript:
var l=document.getElementsByTagName('a'),
    e=l.length;
for(var i=0;i<e;)
    l[i].href=
        'http://webcache.googleusercontent.com/search?q=cache:'+
        l[i++].href.replace(/http:\/\//gi,'');
void(0);//Писать всё нужно в одну строчку, здесь я разбил для удобства восприятия
Вбил его в панель закладок Google Chrome`а и стал нажимать на эту кнопку каждый раз, как перейду на какую-то страницу с висящими ссылками. Всё заработало! Теперь спокойно могу гулять по ссылкам и сохранять страничку за страничкой :)

четверг, 5 мая 2011 г.

Список используемых программ, которые нужно купить

Приветствую!

Как и многие другие, грешен: пиратствую. Не во всём, конечно - Windows давно лицензионный (а Вы попробуйте сейчас купить ноутбук без лицензионной Wind`ы!), от MS Office`а в виду его большой дороговизны, отказался, перейдя на Open Office (ломка уже прошла))). Но другой хороший софт стоит дорого, а бесплатного на некоторые специфические задачи найти не удаётся.
Однако зарабатываю достаточно много и уже просто неприлично этим делом продолжать заниматься. И совесть всё больше по этому поводу свербит...

Решил опубликовать план по приобретению программ - вдруг кому-то пригодится:
  1. ABBYY FineRider - уже купил. Стоила в р-не полутора-двух тысяч, вроде. Распознавание отсканированных текстов на ура! Вобщем-то по-моему вообще монополист - по крайней мере для русского языка то уж точно. Нужна мне для сканирования некоторых редких старых книг, которые мне попались и теперь их нужно отсканировать и разместить в интернете.
  2. ABBYY Lingvo - нужна, http://translate.ru значительно менее удобен в повседневном использовании, да и обладает менее богатыми библиотеками, которые не всегда выдают нужный результат.
  3. Kaspersky Cristal - до конца года обеспечила контора-производитель, откуда я ушёл месяц назад. Продлять не буду, скорее куплю KIS - Kaspersky Internet Security.
  4. Sparx Systems Enterprise Architect 8.0 Professional. Хоть и не поддерживает JavaScript, для которой я его в основном использую, но прога незаменимая, подсел на неё, ещё когда работал в Лаборатории Касперского и теперь сложные объёмные решения без неё получается сильно дольше планировать и обдумывать, чем с ней - все аналоги либо намного дороже, либо намного хуже, либо и то и другое.
  5. SPKet IDE - плагин к Eclipse, платный для коммерческого использования. Очень удобная штука для JavaScript-программирования - лучшая, что я видел.
  6. Altova XMLSpy. Непревзойдённый XML-редактор, как без рук без него - все аналоги, которые видел, намного хуже.
  7. Saxon. Часто не хватает возможностей XSLT 1.0 для текущих задач, а бесплатных удобных библиотек XSLT 2.0-преобразований найти не удалось. Saxon - практически единственная полноценная библиотека для XSLT 2.0 преобразований.
  8. IntelliJ IDEA Corporate. Лучше среды для Java-разработки придумать, по-моему, просто невозможно.