8 мар. 2011 г.

Семь полезных техник JavaScript

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


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



1. Разделяйте если это возможно.


Если важна производительность, очень полезно разделять ваши функции таким образом, чтобы действия, сильно загружающие процессор или требующие много памяти, не повторялись часто. Одна из распространенных ситуаций, когда возникает такая необходимость, это обработка различий браузеров. Посмотрите на следующие два примера, демонстрирующие как мы можем увеличить скорость обработки XMLHttpRequest и привязку обработчика событий.
var asyncRequest = function() { 
    function handleReadyState(o, callback) { 
        var poll = window.setInterval(function() { 
            if(o && o.readyState == 4) { 
                window.clearInterval(poll); 
                if ( callback ){ 
                    callback(o); 
                } 
            } 
        }, 
        50); 
    } 
    var http; 
    try { 
        http = new XMLHttpRequest(); 
    } 
    catch(e) { 
        var msxml = [ 
            'MSXML2.XMLHTTP.3.0',  
            'MSXML2.XMLHTTP',  
            'Microsoft.XMLHTTP' 
        ]; 
        for ( var i=0, len = msxml.length; i < len; ++i ) { 
            try { 
                http = new ActiveXObject(msxml[i]); 
                break; 
            } 
            catch(e) {} 
        } 
    } 
    return function(method, uri, callback, postData) { 
        http.open(method, uri, true); 
        handleReadyState(http, callback); 
        http.send(postData || null); 
        return http; 
    }; 
}();


В первом примере мы создаем функцию asyncRequest которая демонстрирует разделение действий на практике.

Обратите внимание как, используя закрытие, мы хитро разделяем действия, прежде чем возвратить значение asyncRequest. Преимущество, этого метода заключается в том, что нам не нужно проверять доступность объекта XMLHttpRequest перед каждым запросом, так же нам не нужно выполнять три проверки доступности ActiveX компонента в IE. Также обратите внимание как, закрытие используется для инкапсуляции нашей логики, мы не загрязняем глобальное пространство имен, переменными которые нужны только для выполнения этой задачи.

Дин Эдвардс затронул интересную технику почти год назад в обсуждении ускорения детекции объектов давайте, применим ее в нашем примере функции asyncRequest при присоединении обработчиков событий.

var addListener = function() { 
    if ( window.addEventListener ) { 
        return function(el, type, fn) { 
            el.addEventListener(type, fn, false); 
        }; 
    } else if ( window.attachEvent ) { 
        return function(el, type, fn) { 
            var f = function() { 
                fn.call(el, window.event); 
            }; 
            el.attachEvent('on'+type, f); 
        }; 
    } else { 
        return function(el, type, fn) { 
            element['on'+type] = fn; 
        } 
    } 
}();

2. Используйте флаги


Использование флагов это еще один способ ускорения детекции объектов. Если в нескольких функциях проверяется наличие, какого либо объекта, просто используйте соответствующий флаг. Например, если проверяется поддержка браузером наиболее распространенные операции DOM, стоит использовать флаг отражающий этот факт:
var w3 = !!(document.getElementById && document.createElement);

Если вы проверяете, какой браузер использует ваш посетитель, то самый простой способ отличить Internet Explorer, это проверить наличие ActiveX:

var ie = !!window.ActiveXObject;

Операторы (! !) приводят переменную к типу Boolean. Первый оператор приводит тип объекта к Boolean, а второй инвертирует результат первого.

Следует обратить внимание на область видимости флагов, при работе с небольшим количеством скриптов, например, на блоге можно смело объявить их глобально, но при создании большого приложения (или при желании держать код чистым), стоит использовать для флагов отдельный объект.
var FLAGS = { 
    w3: function() { 
        return !!(document.getElementById
            && document.createElement); 
    }(), 
    ie: function() { 
        return !!window.ActiveXObject; 
    }() 
};

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


3. Используйте «мосты»


Мост это паттерн, используемый в разработке программного обеспечения для «разделения абстракции и ее реализации так чтобы они могли изменяться независимо». Мосты особенно полезны в управляемом событиями программировании. Если вы только вступаете в мир разработки API, независимо от того, предназначено ваше API для веб сервиса или это простое getSomething, setSomething API, используйте мосты чтобы сделать его более чистым.

Один из наиболее практичных способов использования мостов это функции обрабатывающие события. Представьте, что у вас есть API функция getUserNameById. И вы хотите, чтобы она срабатывала при событии click, и конечно id элемента, на который вы кликаете, содержит такую информацию. Вот худший путь:
addListener(element, 'click', getUserNameById); 
function getUserNameById(e) { 
    var id = this.id; 
    // работаем с 'id' 
}


Как вы видите, мы создали неприятное API, зависящее от реализации браузера, который передает объект события как первый аргумент, а учитывая область видимости мы можем получить id только через объект this. Желаю удачи, если вы захотите использовать это API в приложении, запускаемом из командной строки, или создать модуль для его тестирования. Вместо этого мы должны были начать с разработки функции API:
function getUserNameById(id) { 
    // работаем с'id' 
}

Выглядит более практично, мы можем программировать API, опираясь на наш интерфейс, а не на реализацию браузера. Теперь нам осталось создать мост, чтобы соединить две функции:
addListener(element, 'click', getUserNameByIdBridge); 
function getUserNameByIdBridge (e) { 
    getUserNameById(this.id); 
} 

4. Попробуйте делегацию событий



Попробуйте здесь ключевое слово, это не всегда практично, но стоит попробовать. Первый раз я услышал об этом в разговоре с одним из программистов Yahoo! и видел, как работает эта техника в Menu widget, блоггер и разработчик Yahoo! Кристиан Хеилманн, тоже поднимал эту тему в его статье Event Delegation.

Делегация событий это простой способ сэкономить на назначении обработчиков событий. Она работает с помощью назначения обработчика событий к элементу контейнеру, и уточнения элемента с которым произошло событие в обработчике, вместо назначения нескольких обработчиков к каждому дочернему элементу и доступа к элементам через объект this. Например, если у вас есть список с пятью элементами, и вы хотите обрабатывать клики на эти li, гораздо более эффективно добавление одного обработчика на весь список, и последующее получение объекта на который был клик.
<ul id="example"> 
    <li>foo</li> 
    <li>bar</li> 
    <li>baz</li> 
    <li>thunk</li> 

</ul> 
  
var element = document.getElementById('example'); 
addListener(element, 'click', handleClick); 
function handleClick(e) { 
    var element = e.target || e.srcElement; 
    // работаем с 'element' 
}

Узнать, какой из элементов li был источником события можно с помощью свойства target объекта события e, в Internet Explorer это свойство называется srcElement. Используйте логический оператор OR, чтобы проверить существует ли свойство target, и если нет, используйте свойство srcElement для IE.


5. Используйте callback функции


Одно из наиболее примечательных упущений всех JavaScript библиотек, это отсутствие возможности использовать callback, для функций коллекторов элементов. Будь то getElementsByClassName, getElementsByAttribute, или даже функции, использующие CSS селекторы, логично иметь возможность задать callback функцию.

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

Рассмотрим функцию, которая выбирает элементы на основе CSS селектора:
function getElementsBySelector(selector) { 
    var elements = []; 
    for ( ... ) { 
        if ( ... ) { 
            elements.push(el); 
        } 
    } 
    return elements; 
}


После того как мы получили массив соответствующих элементов, мы хотим произвести с ними какие-то действия:
var collection = getElementsBySelector('#example p a'); 
for ( var i = collection.length - 1; i >=0; --i ) { 
    // do stuff with collection[i] 
}

В результате нам пришлось использовать два цикла, когда можно было обойтись одним. Давайте с учетом этого изменим функцию, выбирающую элементы так чтобы она позволяла задать callback функцию.
function getElementsBySelector(selector, callback) { 
    var elements = []; 
    for ( ... ) { 
        if ( ... ) { 
            elements.push(el); 
            callback(el); 
        } 
    } 
    return elements; 
} 
  
getElementsBySelector('#example p a', function(el) { 
    // do stuff with 'el' 
});


6. Используйте инкапсуляцию


Это старая, но редко используемая практика. Если вы разрабатываете JavaScript, который становиться большим и неуправляемым (а это случается с каждым, поверьте мне), самый надежный способ защитить ваш код и сохранить его совместимым с другими библиотеками, это использовать закрытие. Вместо страницы полной глобальных переменных:
var a = 'foo'; 
var b = function() { 
    // do stuff... 
}; 
var c = { 
    thunk: function() { 
         
    }, 
    baz: [false, true, false] 
};

Мы можем добавить закрытие:
(function() { 
    var a = 'foo'; 
    var b = function() { 
        // do stuff... 
    }; 
    var c = { 
        thunk: function() { 
         
        }, 
        baz: [false, true, false] 
    }; 
})();

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

// глобальное пространство есть доступ только к a,
// код внутри a скрыт 
function a() { 
    // есть доступ к a и b, но код внутри b, c и d скрыт 
    function b() { 
        // есть доступ к a, b и c, но код внутри c и d скрыт 
        function c() { 
            // есть доступ к a, b, c и d, но код внутри d скрыт 
            function d() { 
                // есть доступ ко всем функциям, и коду 
            } 
        } 
    } 
}

7. Изобретайте колесо.


Последнее, но не менее важное, изобретайте колесо.

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

Ссылки


  1. http://dean.edwards.name/weblog/2005/12/js-tip1/
  2. http://en.wikipedia.org/wiki/Bridge_pattern
  3. http://developer.yahoo.com/yui/menu/
  4. http://icant.co.uk/sandbox/eventdelegation/
  5. http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Operators:Logical_Operators