Как можно безопасно добавить свойства к объекту DOM? - PullRequest
0 голосов
/ 30 октября 2019

Примечание: есть похожий вопрос, но его цель только в отношении возможности. ( Могу ли я добавить произвольные свойства к объектам DOM? ) ... и, исходя из ответов, побочные эффекты кажутся только предполагаемыми, но прямо не упоминаются.

Я уже знаю, что я могу определить их (свойства).

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

Мой вопрос больше о:

Как сделать это безопасно?

В каких случаях это приемлемо?

Этот вопрос касается ванильного JavaScript, а не jQuery или какой-либо другой библиотеки.

Почему я спрашиваю:

Мой код JavaScript генерирует множество «instanceof Node», и я использую много слушателей событий. Для этого я начал добавлять новые свойства к используемым узлам, где я храню информацию, в основном историю того, какая функция добавила этот узел и какой прослушиватель событий был добавлен и какие параметры были переданы этому слушателю. (Я передаю им параметры, используя IIFE). И это действительно иногда помогает при отладке.

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

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

И я не хочу, чтобы позже меня съел динозавр, как люди, которые обычно используют операторы goto. (Google Goto Dinosaur)

Демонстрация:

Допустим, у меня есть веб-сайт о животных, и я хотел бы дать каждому с именем животного коробку с информациейпоявляется при наведении курсора:

<span class="animal" data-genus="Canis">Dog</span>
<span class="animal">Cat</span>

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

.animal:hover::after {
    position: absolute;
    left: 0;

    display: block;
    margin-top: 5px;
    padding: 1px 2px;

    border: black 1px solid;
    background-color: white;

    white-space: nowrap;
}

.animal[data-genus]:hover::after {
    content: attr(data-genus);
}

.animal:not([data-genus]):hover::after {
    content: 'No genus specified for this animal.';
    color: red;
}

Теперь, когда я нахожусь выше с текстом «Собака», я вижу поле с надписью «Canis 'и с надписью «Cat»: «Для этого животного не указан род». красный.

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

Я использовал JavaScript для большей сложности и все еще использую данные-attribute для элемента:

<span class="animal-string-object" data-taxonomy='{"class":"mammalia","family":"delphinidae","genus":"tursiops"}'>Bottlenose Dolphin</span>
<span class="animal-string-object" data-taxonomy='{"class":"cephalopoda","family":"vampyroteuthidae","genus":"vampyroteuthis"}'>Vampire squid</span>

Как видите, коды этих элементов уже довольно длинные, и если я хочу отредактировать или использовать один из этих строк, мне нужно использовать JSON.parse () исохраните его снова. Мне нужно использовать JSON.stringify (), он также подвергается некоторому снижению производительности преобразования.

Обработка этих элементов и создание их блоков находится в конце этого вопроса.

Давайте добавим еще двух животных:

<span class="animal-dom-object">Batfish</span>
<span class="animal-dom-object">Honeybadger</span>

Теперь давайте подберем ссылки на эти элементы и сохраним их в NodeList:

const animalStringObjects = document.querySelectorAll('.animal-string-object');
const animalDomObjects = document.querySelectorAll('.animal-dom-object');

В реальном случаеВ этом сценарии мы, скорее всего, сначала получим объект JSON с «innerText», а затем - document.createElement («span»), но это только для демонстрации.

Теперь у меня есть данные JSON дляи Batfish иHoneybadger и я хотим связать эти данные с элементами. Это основная часть того, почему я спрашиваю и что я сейчас использую.

Я согласен, что добавление свойства к элементу без каких-либо предосторожностей может вызвать побочные эффекты, например, если у меня было:

<p id="restInPeaceId">Hello, I am element written by unattentive coder!</p>
document.getElementById('restInPeaceId').id = 'dead';
document.getElementById('restInPeaceId').innerText = 'Farewell, you served well.';

Это даже рифмуется, но это вызовет ошибку.

Мой подход к этому:

Я установил константу , которая будет представлять свойствоимя, которое будет использоваться для всех элементов:

const ELEMENT_DATA_ACCESSOR_PROPERTY = 'propertyThatWillNeverBeUsedInAnyFutureReleasesOfAnyBrowser';

У меня есть функция , которая всегда будет обращаться к этому самому свойству:

function accessElementData (element, closure) {

    if (!element[ELEMENT_DATA_ACCESSOR_PROPERTY])
        element[ELEMENT_DATA_ACCESSOR_PROPERTY] = new Object();

    closure(element[ELEMENT_DATA_ACCESSOR_PROPERTY]);
};

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

Является ли этот подход безупречным или я могу ожидать какое-то непредсказуемое поведение позже?

Все еще заинтересованы в дельфинах, кальмарах-вампирах, летучих мышах и приманках?

Хорошо, вы все еще можете следовать остальнымэто решение.

Теперь нам нужно объединить данные элементов для наших любимых batfish и honeybadger: .. как я уже говорил, вы, вероятно, получите эти данные, а после этого создадите элемент самостоятельно.

{
    const animalDomData = [
        {'class':'actinopterygii', 'family':'ephippidae', 'genus':'platax'},
        {'class':'mammalia', 'family':'mustelidae', 'genus':'mellivora'}
    ];

    for (let index = 0; index < animalDomObjects.length; index++) {

        accessElementData(animalDomObjects[index], (data) => {
            data.taxonomy = animalDomData[index];
        });
    }
}

const объявляет лексическую переменную, нам не нужна ее память после сопряжения данных. Он уходит, когда заканчивается {} scope.

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

function processTaxonomyAlt (elements) {
    elements.forEach(element => {

        var taxonomy;

        //either get data from string from attribute of element
        //if there is no such attribute, then look into element's data, which we paired up recently
        {
            let dataTaxonomy = element.getAttribute('data-taxonomy');

            //no data attribute
            if (!dataTaxonomy) {

                //let's look into properties
                accessElementData(element, (data) => {
                    taxonomy = data.taxonomy;
                });

            } else {
                //data must be parsed from string
                taxonomy = JSON.parse(dataTaxonomy);
            }
        }

        //creates alt element
        var altTextElement = document.createElement('span');
        //alt text class will be visible in runnable code at the end
        altTextElement.classList.add('altText');

        //first push taxonomy data into array, which will be then joined into innerText string
        {
            let innerText = new Array();
            innerText.push('Animal ');

            const lastIndex = Object.keys(taxonomy).length;
            let index = 0;

            for (const taxon in taxonomy) {
                innerText.push(taxon);
                innerText.push(' is ');
                innerText.push(taxonomy[taxon]);

                index++;

                //will exit the loop on last index, so there will not be any 'and' appended, instead we append '.'
                if (index === lastIndex) {
                    innerText.push('.');
                    break;
                }

                //we separate taxa with 'and'
                innerText.push(' and ');
            }

            altTextElement.innerText = innerText.join('');
        }

        //adding event listeners
        element.addEventListener('mouseover', () => {
            element.appendChild(altTextElement);
        });

        element.addEventListener('mouseout', () => {
            element.removeChild(altTextElement);
        });
    });
};

И, конечно, нам нужно позвонить им:

processTaxonomyAlt(animalStringObjects);
processTaxonomyAlt(animalDomObjects);

Вот полный рабочий код:

const ELEMENT_DATA_ACCESSOR_PROPERTY = 'propertyThatWillNeverBeUsedInAnyFutureReleasesOfAnyBrowser';

const animalStringObjects = document.querySelectorAll('.animal-string-object');
const animalDomObjects = document.querySelectorAll('.animal-dom-object');

//data for dom element object
{
    const animalDomData = [
        {'class':'actinopterygii', 'family':'ephippidae', 'genus':'platax'},
        {'class':'mammalia', 'family':'mustelidae', 'genus':'mellivora'}
    ];

    for (let index = 0; index < animalDomObjects.length; index++) {
        
        accessElementData(animalDomObjects[index], (data) => {
            data.taxonomy = animalDomData[index];
        });
    }
}

//this function ensures, that you can always access property with static name
function accessElementData (element, closure) {

    if (!element[ELEMENT_DATA_ACCESSOR_PROPERTY])
        element[ELEMENT_DATA_ACCESSOR_PROPERTY] = new Object();

    closure(element[ELEMENT_DATA_ACCESSOR_PROPERTY]);
};

//adds alt text to spans
function processTaxonomyAlt (elements) {
    elements.forEach((element) => {

        var taxonomy;

        //either get data from string from attribute of element
        //if there is no such attribute, then look into element's data, which we paired up recently
        {
            let dataTaxonomy = element.getAttribute('data-taxonomy');

            //no data attribute
            if (!dataTaxonomy) {

                //let's look into properties
                accessElementData(element, (data) => {
                    taxonomy = data.taxonomy;
                });

            } else {
                //data must be parsed from string
                taxonomy = JSON.parse(dataTaxonomy);
            }
        }

        //creates alt element
        var altTextElement = document.createElement('span');
        //alt text class will be visible in runnable code at the end
        altTextElement.classList.add('altText');

        //first push taxonomy data into array, which will be then joined into innerText string
        {
            let innerText = new Array();
            innerText.push('Animal ');

            const lastIndex = Object.keys(taxonomy).length;
            let index = 0;

            for (const taxon in taxonomy) {
                innerText.push(taxon);
                innerText.push(' is ');
                innerText.push(taxonomy[taxon]);
                
                index++;

                //will exit the loop on last index, so there will not be any 'and' appended, instead we append '.'
                if (index === lastIndex) {
                    innerText.push('.');
                    break;
                }
                
                //we separate taxa with 'and'
                innerText.push(' and ');
            }
            
            altTextElement.innerText = innerText.join('');
        }

        //adding event listeners
        element.addEventListener('mouseover', () => {
            element.appendChild(altTextElement);
        });

        element.addEventListener('mouseout', () => {
            element.removeChild(altTextElement);
        });
    });
};

processTaxonomyAlt(animalStringObjects);
processTaxonomyAlt(animalDomObjects);
span {
    position: relative;
}

span[class*=animal] {
    padding-right: 20px;
}

.animal:hover::after, .altText{
    position: absolute;
    left: 0;

    display: block;
    margin-top: 5px;
    padding: 1px 2px;

    border: black 1px solid;
    background-color: white;

    white-space: nowrap;
}

.animal[data-genus]:hover::after {
    content: attr(data-genus);
}

.animal:not([data-genus]):hover::after {
    content: 'No genus specified for this animal.';
    color: red;
}
<span class="animal" data-genus="Canis">Dog</span>
<span class="animal">Cat</span>

<span class="animal-string-object" data-taxonomy='{"class":"mammalia","family":"delphinidae","genus":"tursiops"}'>Bottlenose Dolphin</span>
<span class="animal-string-object" data-taxonomy='{"class":"cephalopoda","family":"vampyroteuthidae","genus":"vampyroteuthis"}'>Vampire squid</span>

<span class="animal-dom-object">Batfish</span>
<span class="animal-dom-object">Honeybadger</span>

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

...