как программировать хранение данных (объектов, пользовательских атрибутов) на узлах DOM - PullRequest
0 голосов
/ 16 марта 2012

Я хотел написать какой-то чистый javascript, чтобы лучше понять его (в «реальной практике» я понимаю, что фреймворки, такие как jQuery, гораздо более советуемы и применимы, но на самом деле речь идет не о том, как использовать фреймворки, а о том, как чисто javascript работает и лучшие практики).

В любом случае, я написал простой код javascript. Я хотел создать набор групп кнопок, которые имели бы одно состояние за один раз из набора {вкл, выкл}, и каждое состояние соответствовало бы соответствующей функции, которая будет запускаться при входе в это состояние. Каждая группа кнопок в основном наборе может одновременно содержать только одну кнопку во включенном состоянии. Концепция похожа на идею радио кнопок. Почему бы тогда не использовать радио-кнопку? Ну, семантически, это просто предположить, что это несколько кнопок для некоторых элементов управления, но в любом случае, я полагаю, что это возможно, но вопрос не в этом.

Суть в том, что для этого я добавил множество пользовательских атрибутов к конкретным button элементам id в моем Javascript. Я провел некоторое исследование и нашел этот вопрос и вопрос , касающиеся использования пользовательских атрибутов на узле DOM (объектах). Похоже, они выступают против такой практики, можно даже сказать, что это может привести к потенциальной утечке памяти в зависимости от реализации браузера.

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

Для меня не было очевидным, как это сделать без минимального сохранения ссылки на объект, расположенный на расстоянии от имени скважины, до элемента DOM node button.

Мне удалось увидеть, что из этого вопроса jQuery есть какой-то способ сделать это, но я хочу знать, как это сделать только с чистым javascript.

Вот полный пример кода, с которым я работаю:

<!DOCTYPE html>
<html>
<head>
    <title>Button Test Script</title>

<script language="javascript" type="text/javascript">

window.button_groups = {};

function isset( type ) {
    return !(type==='undefined');   
}

function debug( txt ) { 
    if( !isset(typeof console) ) {
        alert( txt );   
    } else {
        console.log(txt);
    }
}

function img( src ) {
    var t = new Image();
    t.src = src;
    return t;       
}

function turnGroupOff( group ) {
    if( isset(typeof window.button_groups[group]) ) {               
        for( var i = 0; i < window.button_groups[group].length; i++ ) {         
            if( window.button_groups[group][i].toggle == 1)
                window.button_groups[group][i].click();
        }               
    }
}
/**
 * buttonId = id attribute of <button>
 * offImg = src of img for off state of button
 * onImg = src of img for on state of button
 * on = function to be fired when button enters on state
 * off = function to be fired when button enters off state
 */
function newButton( buttonId, offImg, onImg, group, on, off ) {

    var b = document.getElementById(buttonId);
    b.offImg = img( offImg );
    b.onImg = img( onImg );
    b.on = on;
    b.off = off;
    b.img = document.createElement('img');  
    b.appendChild(b.img);
    b.img.src = b.offImg.src;
    b.group = group;

    b.toggle = 0;

    b.onclick = function() {
        switch(this.toggle) {
        case 0:                                             
            turnGroupOff( this.group );
            this.on();
            this.toggle = 1;
            this.img.src = this.onImg.src;
            break;
        case 1:
            this.off();
            this.toggle = 0;
            this.img.src = this.offImg.src;
            break;
        }       
    }

    if( !isset(typeof window.button_groups[group]) )
        window.button_groups[group] = [];
    window.button_groups[group].push(b);                    

}


function init() {

    var on = function() { debug(this.id + " turned on") };

    newButton( 'button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1',
        on,
        function() { debug(this.id + " turned off"); }
        );
    newButton( 'button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1',
        on,
        function() { debug(this.id + " turned off (diff then usual turn off)"); }
        );

    newButton( 'button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2',
        on,
        function() { debug(this.id + " turned off (diff then usual turn off2)"); }
        );
    newButton( 'button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2',
        on,
        function() { debug(this.id + " turned off (diff then usual turn off3)"); }
        );

}

window.onload = init;
</script>

</head>

<body>


<button id="button1" type="button"></button>
<button id="button2" type="button"></button>
<br/>
<button id="button3" type="button"></button>
<button id="button4" type="button"></button>

</body>
</html>

UPDATE

JQuery был немного излишним для моих целей. Мне не нужно расширять произвольный элемент. У меня есть хорошее представление о том, как это делается сейчас для jQuery (с произвольным атрибутом со случайным именем, хранящим целое число индекса кэша).

Я заранее знаю, какие элементы хоста мне нужно расширить и как; также я могу / хочу установить атрибут id для каждого из них на стороне HTML.

Итак, вдохновленный настройкой jQuery, я решил также создать глобальную переменную кэша, за исключением того, что я собираюсь использовать атрибут id узла DOM в качестве ключа кэша. Поскольку это должен быть уникальный идентификатор (по определению), и у меня нет планов динамически изменять id, это должно быть простой задачей. Он полностью отделяет мои объекты Javascript от объектов DOM, но делает мой код немного уродливым и трудным для чтения с многочисленными вызовами data. Я представляю изменения ниже:

<!DOCTYPE html>
<html>
<head>
    <title>Button Test Script</title>
<script language="javascript" type="text/javascript">

window.button_groups = {};

function isset( type ) { // For browsers that throw errors for !object syntax
    return !(type==='undefined');   
}

var c = { // For browsers without console support
    log: function( o ) {
        if( isset(typeof console) ) {
            console.log(o); 
        } else {
            alert( o );
        }
    },
    dir: function( o ) { 
        if( isset(typeof console) ) {
            console.dir(o);
        }
    }
};

function img( src ) { // To avoid repeats of setting new Image src
    var t = new Image();
    t.src = src;
    return t;       
}

var cache = {};
function data( elemId, key, data ) { // retrieve/set data tied to element id

    if(isset(typeof data)) {// setting data         
        if(!isset(typeof cache[elemId]))
            cache[elemId] = {};
        cache[elemId][key] = data;

    } else { // retreiving data
        return cache[elemId][key];      
    }   

}

var button_groups = {}; // set of groups of buttons

function turnGroupOff( group ) { // turn off all buttons within a group
    if( isset(typeof window.button_groups[group]) ) {               
        for( var i = 0; i < window.button_groups[group].length; i++ ) {         
            if( data(window.button_groups[group][i].id, 'toggle') == 1)
                window.button_groups[group][i].click();
        }               
    }
}


/**
 * buttonId = id attribute of <button>
 * offImg = src of img for off state of button
 * onImg = src of img for on state of button
 * on = function to be fired when button enters on state
 * off = function to be fired when button enters off state
 */
function newButton( buttonId, offImg, onImg, group, on, off ) {

    var b = document.getElementById(buttonId);  
    data( b.id, 'offImg', img( offImg ) );
    data( b.id, 'onImg', img( onImg ) );
    data( b.id, 'on', on );
    data( b.id, 'off', off );   
    var btnImg = document.createElement('img');
    btnImg.src = data( b.id, 'offImg' ).src;
    data( b.id, 'img', btnImg  );
    b.appendChild( btnImg );
    data( b.id, 'group', group );
    data( b.id, 'toggle', 0 );

    var click = function() {
        switch(data(this.id,'toggle')) {
        case 0:                                             
            turnGroupOff( data(this.id,'group') );
            (data(this.id,'on'))();
            data(this.id,'toggle',1);
            data(this.id,'img').src = data(this.id,'onImg').src;
            break;
        case 1:
            (data(this.id,'off'))();
            data(this.id,'toggle',0);
            data(this.id,'img').src = data(this.id,'offImg').src;
            break;
        }   

    }

    b.onclick = click;

    if( !isset(typeof window.button_groups[group]) )
        window.button_groups[group] = [];
    window.button_groups[group].push(b);                    
}


function init() {

    var on = function() { c.log(this.id + " turned on") };

    newButton( 'button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1',
        on,
        function() { c.log(this.id + " turned off"); }
        );
    newButton( 'button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1',
        on,
        function() { c.log(this.id + " turned off (diff then usual turn off)"); }
        );

    newButton( 'button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2',
        on,
        function() { c.log(this.id + " turned off (diff then usual turn off2)"); }
        );
    newButton( 'button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2',
        on,
        function() { c.log(this.id + " turned off (diff then usual turn off3)"); }
        );

}


window.onload = init;


</script>       
</head>

<body>


<button id="button1" type="button"></button>
<button id="button2" type="button"></button>
<br/>
<button id="button3" type="button"></button>
<button id="button4" type="button"></button>


</body>
</html>

ОБНОВЛЕНИЕ 2

Я обнаружил, что используя силу замыкания Мне действительно нужно хранить только один «специальный» атрибут, то есть группу, к которой принадлежала кнопка.

Я изменил функцию newButton на следующую, что благодаря закрытию избавляет от необходимости хранить многие из тех других вещей, которыми я был:

function newButton( buttonId, offImg, onImg, group, on, off ) {

    var b = document.getElementById(buttonId);  
    offImg = img( offImg );
    onImg = img( onImg );
    var btnImg = document.createElement('img');
    btnImg.src = offImg.src;
    b.appendChild( btnImg );
    data( b.id, 'group', group );
    var toggle = 0;

    var click = function(event) {
        switch(toggle) {
        case 0:                                             
            turnGroupOff( data(this.id,'group') );
            if( on(event) ) {
                toggle = 1;
                btnImg.src = onImg.src;
            }
            break;
        case 1:
            if( off(event) ) {
                toggle = 0;
                btnImg.src = offImg.src;
            }
            break;
        }   

    }

    b.onclick = click;

    if( !isset(typeof window.button_groups[group]) )
        window.button_groups[group] = [];
    window.button_groups[group].push(b);    

    b = null;
}

Ответы [ 2 ]

1 голос
/ 16 марта 2012

Вы расширяете объекты (что плохо для хост-объектов) или переносите объекты, как это делает jQuery, используя обернутый объект для идентификации связанных данных в хэш-таблице.По сути, вы хешируете узел DOM и выполняете поиск в хеш-таблице для связанных данных.Конечно, вам все еще нужно расширять хост-объект, но вы добавляете только одно свойство, которое, как вы знаете, достаточно безопасно для разных браузеров, а не набор произвольных свойств.Если вы осмотрите элемент со связанными данными, вы можете увидеть что-то вроде element.jQuery171023696433915756643, которое содержит индекс внутреннего хранилища для этого элемента.Я бы порекомендовал прочитать исходный код jQuery, если вам это интересно, особенно функцию data ()

data: function( elem, name, data, pvt /* Internal Use Only */ ) {
        if ( !jQuery.acceptData( elem ) ) {
            return;
        }

        var privateCache, thisCache, ret,
            internalKey = jQuery.expando,
            getByName = typeof name === "string",

            // We have to handle DOM nodes and JS objects differently because IE6-7
            // can't GC object references properly across the DOM-JS boundary
            isNode = elem.nodeType,

            // Only DOM nodes need the global jQuery cache; JS object data is
            // attached directly to the object so GC can occur automatically
            cache = isNode ? jQuery.cache : elem,

            // Only defining an ID for JS objects if its cache already exists allows
            // the code to shortcut on the same path as a DOM node with no cache
            id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
            isEvents = name === "events";

        // Avoid doing any more work than we need to when trying to get data on an
        // object that has no data at all
        if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
            return;
        }

        if ( !id ) {
            // Only DOM nodes need a new unique ID for each element since their data
            // ends up in the global cache
            if ( isNode ) {
                elem[ internalKey ] = id = ++jQuery.uuid;
            } else {
                id = internalKey;
            }
        }

        if ( !cache[ id ] ) {
            cache[ id ] = {};

            // Avoids exposing jQuery metadata on plain JS objects when the object
            // is serialized using JSON.stringify
            if ( !isNode ) {
                cache[ id ].toJSON = jQuery.noop;
            }
        }

        // An object can be passed to jQuery.data instead of a key/value pair; this gets
        // shallow copied over onto the existing cache
        if ( typeof name === "object" || typeof name === "function" ) {
            if ( pvt ) {
                cache[ id ] = jQuery.extend( cache[ id ], name );
            } else {
                cache[ id ].data = jQuery.extend( cache[ id ].data, name );
            }
        }

        privateCache = thisCache = cache[ id ];

        // jQuery data() is stored in a separate object inside the object's internal data
        // cache in order to avoid key collisions between internal data and user-defined
        // data.
        if ( !pvt ) {
            if ( !thisCache.data ) {
                thisCache.data = {};
            }

            thisCache = thisCache.data;
        }

        if ( data !== undefined ) {
            thisCache[ jQuery.camelCase( name ) ] = data;
        }

        // Users should not attempt to inspect the internal events object using jQuery.data,
        // it is undocumented and subject to change. But does anyone listen? No.
        if ( isEvents && !thisCache[ name ] ) {
            return privateCache.events;
        }

        // Check for both converted-to-camel and non-converted data property names
        // If a data property was specified
        if ( getByName ) {

            // First Try to find as-is property data
            ret = thisCache[ name ];

            // Test for null|undefined property data
            if ( ret == null ) {

                // Try to find the camelCased property
                ret = thisCache[ jQuery.camelCase( name ) ];
            }
        } else {
            ret = thisCache;
        }

        return ret;
    }
1 голос
/ 16 марта 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...