如何安全地向DOM对象添加属性?

时间:2019-10-30 19:19:02

标签: javascript html css properties element

注意:存在类似的问题,但其目的仅在于可能性。 (Can I add arbitrary properties to DOM objects?)...从答案中看,副作用似乎仅是假设的,而没有直接提及。

我已经知道,我可以定义它们(属性)。

我也有一些特定的方法来限制错误。

我的问题更多关于:

如何安全地做这些事情?

在哪种情况下可以接受?

这个问题是针对普通JavaScript的,而不是jQuery或其他任何库。

为什么我问:

我的JavaScript代码生成大量的“节点实例”,并且我使用了大量的事件侦听器。为此,我开始向使用的节点添加新属性,在其中存储信息,主要是有关该节点附加了哪个功能以及添加了哪个事件侦听器以及将哪些参数传递给该侦听器的历史记录。 (我使用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;
}

现在,当我在“ span>”上方悬停“ Dog”文本时,我看到一个框,上面写着“ Canis”和“ span>”并显示“ Cat”:“此动物未指定属。”红色。

这是一个非常简单的案例,如果我想对悬停使用更复杂的分类法,则很难扩展。

我使用JavaScript来提高复杂性,但仍在元素上使用数据属性:

<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');

在实际情况下,我们很可能会首先使用'innerText'获取JSON对象,然后再使用document.createElement('span'),但这只是出于演示目的。

现在,我同时拥有Batfish和Honeybadger的JSON数据,并且希望将这些数据与元素配对。 这是我为什么要问以及我目前正在使用什么的主要部分。

我确实同意,不加任何注意地向元素添加属性可能会引起副作用,例如,如果我有:

<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]);
};

现在我可以肯定的是,它不会与现有属性冲突,即使有一天,某些浏览器将变得疯狂并使用完全相同的属性常量,我可以轻松地对其进行更改。

这种方法是否完美无瑕,还是我以后可以期待一些无法预测的行为?

仍然对海豚,吸血鬼乌贼,蝙蝠鱼和蜜interested感兴趣吗?

好的,您仍然可以按照本解决方案的其余部分进行操作。

现在,我们需要为心爱的蝙蝠鱼和蜜bad配对元素数据: ..正如我之前提到的,您可以适当地获取这些数据,然后自己创建元素。

{
    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 声明了词法变量,数据配对后我们不需要它的内存。它在{}作用域结束时离开。

现在,我们定义一个函数,该函数添加事件侦听器,以模拟悬停效果: 函数具有注释,它提供了见解,而不是此处带有文本的分段代码。

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>

最后,感谢您提供的见解和阅读如此大的问题。我认为,如果对我的方法没有答案,那是安全的。

0 个答案:

没有答案