Pull to refresh

Ведро и не ядро

Reading time 6 min
Views 4.1K
Что плохо на примере jq.

Рассмотрим на задаче и её реализации: Есть x элементов, по клику на элемент он должен поменять состояние. Кликов на элементе может быть, и чаще всего будет, не одни.


Вёрстка.
Элементов много — нахождение по классу.
<body>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
<div class="testDiv"></div>
</body>

// бесспорно ок.

JS.
Найти по классу, повесить клик, обработать клик, отмечен — снять, не отмечен — отметить

jQ
подключили jQ
Решение Задачи
$(function(){
    $('.testDiv').on('click', function(){
        var vedroCache = $(this);
        if (vedroCache.hasClass('isActiveTestDiv')){
            vedroCache.removeClass('isActiveTestDiv');
        } else{
            vedroCache.addClass('isActiveTestDiv');
        }
    });
});


JS
Вместо jq
//писать ≈2—3ч в с чаем и твиттером в выходные
(function(u){
    var J = {},
        W = window,
        D = document,
        intCache;
    W.J = J;
//    search elements
    if ('getElementsByClassName' in document){
        J.$c = function(className, from){
            return (from || D).getElementsByClassName(className);
        }
    } else{
        J.$c = function(className, from){
            var cache = (from || D).getElementsByTagName('*'),
                result = [],
                i = cache.length,
                j = -1;
            for (; i-- ;){
                if (cache[i].className.indexOf(className) !== -1){
                    result[j += 1](cache[i]);
                }
            }
            return result;
        }
    }
//    events
    if ('addEventListener' in W){
        J.eventAdd = function(object, eventName, callback){
            object.addEventListener(eventName, callback);
        };
        J.eventDel = function(object, eventName, callback){
            object.removeEventListener(eventName, callback);
        };
    } else{
        var ieFixEventsNameObjectsCallbacks = [],
            ieFixEventsObjectsAndCallbacksLength = 0,
            ieFixEventsHandlers = {},
            fixEvent = function(e){
                e.preventDefault = function(){
                    e.returnValue = false;
                };
                e.stopPropagation = function(){
                    e.cancelBubble = true;
                };
                e.target= e.srcElement;
                return e;
            },
            ieAddFixEvent = function(object, eventName, callback){
                function fix(){
                    callback.call(object, fixEvent(W.event));
                }
                intCache = ieFixEventsObjectsAndCallbacksLength;
                ieFixEventsNameObjectsCallbacks[ieFixEventsObjectsAndCallbacksLength] = eventName;
                ieFixEventsObjectsAndCallbacksLength += 1;
                intCache += ieFixEventsObjectsAndCallbacksLength;
                ieFixEventsNameObjectsCallbacks[ieFixEventsObjectsAndCallbacksLength] = callback;
                ieFixEventsObjectsAndCallbacksLength += 1;
                intCache += ieFixEventsObjectsAndCallbacksLength;
                ieFixEventsNameObjectsCallbacks[ieFixEventsObjectsAndCallbacksLength] = object;
                ieFixEventsObjectsAndCallbacksLength += 1;
                ieFixEventsHandlers[intCache] = fix;
                object.attachEvent('on' + eventName, fix);
            },
            ieRemoveFixEvent = function(object, eventName, callback){
                for (var i = ieFixEventsObjectsAndCallbacksLength; i-- ;){
                    if ((ieFixEventsNameObjectsCallbacks[i] === object) && (ieFixEventsNameObjectsCallbacks[i - 1] === callback) && (ieFixEventsNameObjectsCallbacks[i - 2] === eventName)){
                        ieFixEventsNameObjectsCallbacks[i] = ieFixEventsNameObjectsCallbacks[i - 1] = ieFixEventsNameObjectsCallbacks[i - 2] = u;
                        i = i * 3 - 3;
                        break;
                    }
                    i -= 2;
                }
                if (i !== -1){
                    object.detachEvent('on' + eventName, ieFixEventsHandlers[i]);
                    ieFixEventsHandlers[i] = u;
                }
            };
        J.eventAdd = function(object, eventName, callback, isNotNeedFix){
            if (isNotNeedFix === true){
                object.attachEvent('on' + eventName, callback);
            } else{
                ieAddFixEvent(object, eventName, callback);
            }
        };
        J.eventDel = function(object, eventName, callback, isNotNeedFix){
            if (isNotNeedFix === true){
                object.detachEvent('on' + eventName, callback);
            } else{
                ieRemoveFixEvent(object, eventName, callback);
            }
        };
    }
//    classes
    J.classHas = function(object, className){
        return object.className.indexOf(className) !== -1; //да без регулярок*
    };
    J.classAdd = function(object, className){
        if (!J.classHas(object, className)){
            object.className += ' ' + className;
        }
    };
    J.classDel = function(object, className){
        if (J.classHas(object, className)){
            object.className = object.className.replace(className, '');  //да без регулярок*
        }
    };
    //*проектирование уникальных классов для DOM элементов вьюшек — простая стандартизируемая (автоматизируемая) задача!
//    fast bad ready
    J.ready = function(callback){
        var callbacks = [callback];
        function ready(){
            var iMax = callbacks.length,
                i = 0;
            J.eventDel(D, 'DOMContentLoaded', ready);
            J.eventDel(W, 'load', ready, true);
            for (;i < iMax; i += 1){
                callbacks[i].call(J);
            }
            ready = callbacks = null;
            J.ready = function(callback){
                callback.call(J);
            }
        }
        if ('addEventListener' in W){
            J.eventAdd(D, 'DOMContentLoaded', ready);
        }
        J.eventAdd(W, 'load', ready, true);
        J.ready = function(callback){
            callbacks.push(callback);
        }
    };
}());

Решение задачи
J.ready(function(){
    var J = this,
        elems = J.$c('testDiv'),
        i = elems.length;
    function clickListener(){
        J[J.classHas(this, 'isActiveTestDiv') ? 'classDel' : 'classAdd'](this, 'isActiveTestDiv');
    }
    for (;i--;){
        J.eventAdd(elems[i], 'click', clickListener);
    }
});


До объяснений проблем ведра хотелось бы заметмть, инит решения при 350 дивах и следующей эмуляции console.time
if (!('console' in window) || !('time' in console)){
    (function(w){
        var times = {},
            C;
        if (!('console' in w)){
            w.console = C =  {};
            C.log = function(data){
                alert(data);
            }
        } else{
            C = w.console;
        }
        C.time = function(name){
            times[name] = new  Date().getTime();
        };
        C.timeEnd = function(name){
            if (name in times){
                return new Date().getTime() - times[name];
            }
        };
    }(window));
}

на машинке
masinka
и в фоксе 18.0.1
jQ ≈16ms
J ≈2ms

А в чём разница?
По мимо кучи кода, основная разница в подходах.

Подход «ведро»
Концепт — осознать, применить, т.е.

$(/*всё что угодно*/)./*сделай с ним что то*/

Мы можем менять jq на любые другие штуки, но концепт остаётся один и тот же.

Есть функция готовая ко всему (ведро), которая возвращает результат своей обработки в своей оболочке с набором действий.

В следствии чего
//мы должны надеятся на то, что ведро нас поймёт
$('.testDiv').on('click', function(){
        var vedroCache = $(this); // и во многих элементарных случая мы должны обрабатывать через ведро каждый раз
        if (vedroCache.hasClass('isActiveTestDiv')){
            vedroCache.removeClass('isActiveTestDiv');
        } else{
            vedroCache.addClass('isActiveTestDiv');
        }
    });
    console.log(console.timeEnd('divs'));
});


Подход «не ведро» (набор решений)

/*название решения*/(/*параметры задачи :)*/)

Считаю правильным, если решение валится от неправильных параметров.

J.ready(function(){
    var J = this,
        elems = J.$c('testDiv'), //найти по классу
        i = elems.length;
    function clickListener(){
        J[J.classHas(this, 'isActiveTestDiv') ? 'classDel' : 'classAdd'](this, 'isActiveTestDiv'); //нет поставить, есть убрать
    }
    for (;i--;){
        J.eventAdd(elems[i], 'click', clickListener); //повесить событие на конкретный элемент
    }
});

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

Результаты

Отредактированная часть.
Попросили добавить выводы.
О чём хотелось сказать:
1. Основная мысль.
Логически плохо думать и делать следующим образом.

Волшебная коробочка, вот мой элемент сделай с ним действие моей мечты 5ого уровня.
Именно это вы говорите когда пишите
$('.myElement').doDoingOfMyDrem(5);
Коробочка должна понять что от неё просят и загнать результат в обёртку.

Более правильным мне кажется следующее:
Выполнить действие моей мечты с элементом, который можно найти, по классу, применив действие 5ого уровня.
С помощью хелперов J, это будет выглядеть так
J.doDoingOfMyDrem(J.$c('myElement'), 5);

А если бы вы имели ссылку на нужный элемент, например this в контексте обработчика события DOM элемента, было бы так:
$(this).doDoingOfMyDrem(5);
против
J.doDoingOfMyDrem(this, 5);

2. Время в хорошой среде на примере 350 дивов результат отличается на 14мс и не стоит ни чего, НО на мобильных отличие другое. На киосках самообслуживания типа киви или новоплат, который на сколько мне известно на HTML, третье. А на телевизоре с андройдом?

3. Уже давно настала эпоха серьёзных веб приложений которые состоят из огромного количества таких мелких деталей, объединяя их во всё более крупные и крупные, связки. Зачастую действительные проблемы с производительностью ощущаются уже в крупных связках связок, поэтому проектирование действительно серьёзной вещи нужно начинать с минимально допустимых единиц. На мой взгляд ни jQ, ни множество других фреймвоков не ставило перед собой задачи быть серьёзной основой, но они легли в основу в следствии ряда причин, и что самое ужасное родили за собой фреймворковых программистов, которые не понимают цены своих волшебных ящичков. В следствии чего хочется показать как нужно можно по другому.
Tags:
Hubs:
-24
Comments 22
Comments Comments 22

Articles