如何在不使用 innerHTML 的情况下向列表添加项目?

huangapple go评论81阅读模式
英文:

How can I add items to a list without innerHTML?

问题

我在HTML中有一个空的ul元素,每次我添加或删除颜色时,都会调用ShowColors函数,该函数使用JavaScript的.innerHTML添加li项。如何在不使用.innerHTML的情况下优化这段代码?

const colorList = document.querySelector(".all-colors");

function ShowColors() {
    colorList.innerHTML = savedColorsArray.map(color => `
        <li class="color">
            <span class="rect" data-color="${color}" style="background: ${color};"></span>
        </li>`).join("");
};

我尝试使用.insertAdjacentHTML,但我将不得不更改代码的工作方式以保存每个元素,以便稍后可以删除它,并且可能必须将其保存到localStorage以使其持久化。

savedColorsArray.forEach(color => {
    colorList.insertAdjacentHTML('beforeend', `<li class="color">
        <span class="rect" data-color="${color}" style="background: ${color};"></span>
    </li>`);
});
英文:

I have an empty ul in html and everytime I add or remove a color I call ShowColors which adds li items via JavaScript using .innerHTML. How can I optimize this code without using .innerHTML?

<!-- language: lang-js -->
const colorList = document.querySelector(".all-colors");

function ShowColors() {
    colorList.innerHTML = savedColorsArray.map(color =&gt; `
        &lt;li class=&quot;color&quot;&gt;
            &lt;span class=&quot;rect&quot; data-color=&quot;${color}&quot; style=&quot;background: ${color};&quot;&gt;&lt;/span&gt;
        &lt;/li&gt;`).join(&quot;&quot;);
};

<!-- language: lang-html -->

&lt;ul class=&quot;all-colors&quot;&gt;&lt;/ul&gt;

I tried .insertAdjacentHTML but I would have to change how the code works to save each element so I can remove it later and also probably have to save it to localStorage so it's persistent.

<!-- language: lang-js -->
savedColorsArray.forEach(color => {
colorList.insertAdjacentHTML('beforeend', &lt;li class=&quot;color&quot;&gt;
&lt;span class=&quot;rect&quot; data-color=&quot;${color}&quot; style=&quot;background: ${color};&quot;&gt;&lt;/span&gt;
&lt;/li&gt;
);

答案1

得分: 1

以下是翻译好的部分:

// JavaScript 代码部分
const colorList = document.querySelector(".all-colors");

const deleteItem = item =>
savedColorsArray.splice([...colorList.children].indexOf(item.closest('.color')), 1);

let updatePromise;

let updateCount = 0;

const savedColorsArray = new Proxy([], {
  set(target, prop, val){
    // 在微任务中延迟执行  
    updatePromise ??= Promise.resolve().then(() => {
      updatePromise = null;
      const log = document.createElement('div');
      log.textContent = `已更新 ${++updateCount} 次`;
      document.querySelector('.log').appendChild(log);
      let idx = 0;
      const $children = [...colorList.children];
      for(const color of target){
        let $item = $children[idx++];
        if(!$item){
          const div = document.createElement('div');
          div.innerHTML = `<li class="color">
            <span class="rect" data-color="${color}" style="background: ${color};"><b onclick="deleteItem(this)">✕</b></span>
        </li>`;
          colorList.appendChild(div.children[0]);
        }else{
          const $span = $item.querySelector('span');
          $span.dataset.color = color;
          $span.style.background = color;
        }

      }
      // 清除未使用的项目
      while(idx < $children.length){
        $children[idx++].remove();
      }
    });
  
    return Reflect.set(target, prop, val);
  }
});

savedColorsArray.push(...'red orange yellow lime green blue'.split(' '));
/* CSS 代码部分 */
.color span{
  display:block;
  height: 30px;
  width: 150px;
  border-radius:4px;
  transition: background .5s;
  position:relative;
}
.all-colors{
  padding:0;
  margin:0;
}
b{
  position:absolute;
  right:5px;
  top:3px;
  cursor:pointer;
}
.color{
  padding: 5px;
  display:block;
  list-style:none;
}
body{
  display:flex;
  gap:30px;
}
<!-- HTML 代码部分 -->
<ul class="all-colors"></ul>
<div>
  <input>
  <button onclick="document.querySelector('input').value.match(/\w+/g)?.forEach(color => savedColorsArray.unshift(color))">添加颜色</button>
</div>
<div class="log"></div>
英文:

A modern approach could be a reactive solution with Proxy:
you just modify an array of colors and the list is updated automatically.
You just iterate the color array and try to reuse existing &lt;li&gt;. If there's a &lt;li&gt; with the same index as a color, update it with dataset.color = color and style.background = color. Otherwise create an &lt;li&gt; with innerHTML like you did. The reusing of DOM elements seems like optimization like you requested.

如何在不使用 innerHTML 的情况下向列表添加项目?

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const colorList = document.querySelector(&quot;.all-colors&quot;);
const deleteItem = item =&gt; 
savedColorsArray.splice([...colorList.children].indexOf(item.closest(&#39;.color&#39;)), 1);
let updatePromise;
let updateCount = 0;
const savedColorsArray = new Proxy([], {
set(target, prop, val){
// postpone in a microtask  
updatePromise ??= Promise.resolve().then(() =&gt; {
updatePromise = null;
const log = document.createElement(&#39;div&#39;);
log.textContent = `updated ${++updateCount}`;
document.querySelector(&#39;.log&#39;).appendChild(log);
let idx = 0;
const $children = [...colorList.children];
for(const color of target){
let $item = $children[idx++];
if(!$item){
const div = document.createElement(&#39;div&#39;);
div.innerHTML = `&lt;li class=&quot;color&quot;&gt;
&lt;span  class=&quot;rect&quot; data-color=&quot;${color}&quot; style=&quot;background: ${color};&quot;&gt;&lt;b onclick=&quot;deleteItem(this)&quot;&gt;✕&lt;/b&gt;&lt;/span&gt;
&lt;/li&gt;`;
colorList.appendChild(div.children[0]);
}else{
const $span = $item.querySelector(&#39;span&#39;);
$span.dataset.color = color;
$span.style.background = color;
}
}
// clear unused items
while(idx &lt; $children.length){
$children[idx++].remove();
}
});
return Reflect.set(target, prop, val);
}
});
savedColorsArray.push(...&#39;red orange yellow lime green blue&#39;.split(&#39; &#39;));

<!-- language: lang-css -->

.color span{
display:block;
height: 30px;
width: 150px;
border-radius:4px;
transition: background .5s;
position:relative;
}
.all-colors{
padding:0;
margin:0;
}
b{
position:absolute;
right:5px;
top:3px;
cursor:pointer;
}
.color{
padding: 5px;
display:block;
list-style:none;
}
body{
display:flex;
gap:30px;
}

<!-- language: lang-html -->

&lt;ul class=&quot;all-colors&quot;&gt;&lt;/ul&gt;
&lt;div&gt;
&lt;input&gt;
&lt;button onclick=&quot;document.querySelector(&#39;input&#39;).value.match(/\w+/g)?.forEach(color =&gt; savedColorsArray.unshift(color))&quot;&gt;Add color&lt;/button&gt;
&lt;/div&gt;
&lt;div class=&quot;log&quot;&gt;&lt;/div&gt;

<!-- end snippet -->

答案2

得分: 0

.innerHTML 应尽量避免使用,因为调用它会触发 HTML 解析器,重新构建带有新内容的 DOM,因此使用它会带来性能问题。

在重新构建带有新内容的 DOM 时,很容易清除先前添加的事件处理程序,因此存在引入错误的可能性。

众所周知,当传递给 .innerHTML 的字符串不完全受开发人员控制时,它可能会在代码中引发安全漏洞。

来自 MDN

警告:如果您的项目将接受任何形式的安全审查,那么在大多数情况下使用 innerHTML 可能会导致您的代码被拒绝。例如,如果您在浏览器扩展中使用 innerHTML 并将扩展提交到 addons.mozilla.org,则在审查过程中可能会被拒绝。请参阅将外部内容安全地插入页面以获取替代方法。

所以,故事的寓意是,只有在没有其他选择时才使用 .innerHTML。幸运的是,通常还有其他选择,其中首选方法之一是创建新的 DOM 元素,配置它们,然后将它们附加到 DOM。

以下是如何实现的示例:

const colorList = document.querySelector(".all-colors");

function ShowColors() {
  // 创建新的 DOM 元素
  const li = document.createElement("li");
  const span = document.createElement("span");  

  // 通过 DOM 属性配置元素
  li.classList.add("color");
  span.classList.add("rect");
  span.style.backgroundColor = color;
  span.dataset.dataColor = color;

  // 将新的 DOM 元素附加到适当的父元素上
  li.appendChild(span);
  colorList.appendChild(li);
};
英文:

.innerHTML should be avoided whenever possible because calling it invokes the HTML parser, which rebuilds the DOM with the new content, so there are performance implications to using it.

While the DOM is being rebuilt with new content, it's very easy to wipe out previously added event handlers, so there is an opportunity to introduce bugs.

It is well known that using .innerHTML can potentially open up a security vulnerability in code as well, when the string that is passed to it is not 100% within the control of the developer.

From MDN:

> Warning: If your project is one that will undergo any form of security
> review, using innerHTML most likely will result in your code being
> rejected. For example, if you use innerHTML in a browser extension and
> submit the extension to addons.mozilla.org, it may be rejected in the
> review process. Please see Safely inserting external content into a
> page for alternative methods.

So, the moral of the story is, only use .innerHTML when you have no other choice. Fortunately, there often are other choices, the first among them being to create new DOM elements, configure them, and then attach them to the DOM.

Here's how that is done:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const colorList = document.querySelector(&quot;.all-colors&quot;);
function ShowColors() {
// Create a new DOM elements
const li = document.createElement(&quot;li&quot;);
const span = document.createElement(&quot;span&quot;);  
// Configure the elements via DOM properties
li.classList.add(&quot;color&quot;);
span.classList.add(&quot;rect&quot;);
span.style.backgroundColor = color;
span.dataset.dataColor = color:
// Append the new DOM element to the appropriate parent elements
li.appendChild(span);
colorList.appendChild(li);
};

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年7月3日 22:06:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76605542.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定