MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

使用JavaScript进行DOM操作的最佳实践

2023-02-073.3k 阅读

理解 DOM 与 JavaScript 的关系

在深入探讨 JavaScript 进行 DOM 操作的最佳实践之前,我们需要对 DOM(文档对象模型)和 JavaScript 之间的关系有清晰的认识。

DOM 的本质

DOM 是一种将 HTML 或 XML 文档呈现为树状结构的编程接口。在这个树状结构中,每个节点代表文档中的一个元素、属性或文本片段。例如,一个简单的 HTML 文档:

<!DOCTYPE html>
<html>
<head>
    <title>DOM 示例</title>
</head>
<body>
    <div id="main">
        <p class="text">这是一段文本</p>
    </div>
</body>
</html>

在 DOM 树中,html 元素是根节点,headbody 是它的子节点。titlehead 的子节点,divbody 的子节点,而 p 又是 div 的子节点。每个节点都有其特定的属性和方法,通过这些属性和方法,我们可以对文档进行访问、修改和操作。

JavaScript 与 DOM 的结合

JavaScript 作为一种客户端脚本语言,与 DOM 紧密结合。JavaScript 提供了一系列的 API 来操作 DOM,使得我们能够动态地改变网页的结构、样式和内容。例如,我们可以使用 JavaScript 来获取文档中的某个元素,并修改其文本内容:

// 获取 id 为 main 的元素
const mainDiv = document.getElementById('main');
// 获取 mainDiv 中的第一个 p 元素
const pElement = mainDiv.getElementsByTagName('p')[0];
// 修改 p 元素的文本内容
pElement.textContent = '新的文本内容';

通过这样的方式,JavaScript 赋予了网页动态交互的能力,极大地提升了用户体验。

选择 DOM 元素的最佳实践

在进行 DOM 操作时,首先需要准确地选择要操作的元素。JavaScript 提供了多种选择 DOM 元素的方法,每种方法都有其适用场景。

getElementById

getElementById 方法是通过元素的 id 属性来获取单个元素。这是一种非常高效的选择元素的方式,因为 id 在文档中应该是唯一的。例如:

<div id="uniqueDiv">这是一个具有唯一 id 的 div</div>
const uniqueDiv = document.getElementById('uniqueDiv');
if (uniqueDiv) {
    // 在这里对 uniqueDiv 进行操作
    uniqueDiv.style.backgroundColor = 'lightblue';
}

使用 getElementById 时,要确保 id 的唯一性,避免出现重复 id 导致获取到错误的元素。

getElementsByTagName

getElementsByTagName 方法通过标签名来获取一组元素。它返回的是一个类似数组的 HTMLCollection 对象。例如:

<ul>
    <li>列表项1</li>
    <li>列表项2</li>
    <li>列表项3</li>
</ul>
const listItems = document.getElementsByTagName('li');
for (let i = 0; i < listItems.length; i++) {
    listItems[i].style.color ='red';
}

需要注意的是,HTMLCollection 是一个实时的集合,意味着当文档结构发生变化时,它会自动更新。

getElementsByClassName

getElementsByClassName 方法通过元素的类名来获取一组元素。它返回的也是一个类似数组的 HTMLCollection 对象。例如:

<p class="highlight">这是一段高亮文本</p>
<p class="highlight">这是另一段高亮文本</p>
const highlightParagraphs = document.getElementsByClassName('highlight');
for (let i = 0; i < highlightParagraphs.length; i++) {
    highlightParagraphs[i].style.fontWeight = 'bold';
}

同样,HTMLCollection 的实时性在使用 getElementsByClassName 时也需要考虑。

querySelector 和 querySelectorAll

querySelector 方法返回文档中与指定 CSS 选择器匹配的第一个元素,如果没有匹配的元素则返回 nullquerySelectorAll 方法返回文档中与指定 CSS 选择器匹配的所有元素,返回的是一个 NodeList 对象。例如:

<div class="container">
    <p class="text">这是一段文本</p>
    <a href="#" class="link">这是一个链接</a>
</div>
// 使用 querySelector 获取第一个匹配的元素
const firstText = document.querySelector('.container.ptext');
if (firstText) {
    firstText.style.textDecoration = 'underline';
}

// 使用 querySelectorAll 获取所有匹配的元素
const allLinks = document.querySelectorAll('.container.link');
for (let i = 0; i < allLinks.length; i++) {
    allLinks[i].style.color = 'blue';
}

querySelectorquerySelectorAll 非常强大,因为它们可以使用复杂的 CSS 选择器,如组合选择器、属性选择器等。而且 NodeList 是一个静态的集合,不会随着文档结构的变化而自动更新。

创建和插入 DOM 元素的最佳实践

除了选择现有的 DOM 元素,我们还经常需要创建新的 DOM 元素并将其插入到文档中。

创建元素

使用 document.createElement 方法可以创建一个新的 DOM 元素。例如,要创建一个新的 div 元素:

const newDiv = document.createElement('div');

创建元素后,我们可以为其设置属性和添加子元素。

设置属性

可以使用 setAttribute 方法为元素设置属性。例如,为新创建的 div 设置 idclass 属性:

newDiv.setAttribute('id', 'newDivId');
newDiv.setAttribute('class', 'newDivClass');

也可以直接通过元素的属性来设置,例如:

newDiv.id = 'newDivId';
newDiv.className = 'newDivClass';

这两种方式都可以达到设置属性的目的,但 setAttribute 方法在设置一些特殊属性(如 data - * 属性)时更为方便。

添加子元素

要将一个元素添加为另一个元素的子元素,可以使用 appendChild 方法。例如,创建一个新的 p 元素并添加到之前创建的 div 中:

const newParagraph = document.createElement('p');
newParagraph.textContent = '这是新 div 中的段落';
newDiv.appendChild(newParagraph);

还可以使用 insertBefore 方法在指定元素之前插入新元素。例如,假设有一个 parentDiv 包含两个 childDiv,我们要在第二个 childDiv 之前插入一个新的 div

<div id="parentDiv">
    <div id="childDiv1">第一个子 div</div>
    <div id="childDiv2">第二个子 div</div>
</div>
const newInsertDiv = document.createElement('div');
newInsertDiv.textContent = '新插入的 div';
const parentDiv = document.getElementById('parentDiv');
const childDiv2 = document.getElementById('childDiv2');
parentDiv.insertBefore(newInsertDiv, childDiv2);

修改 DOM 元素内容和样式的最佳实践

修改 DOM 元素的内容和样式是实现网页动态效果的重要部分。

修改文本内容

可以使用 textContent 属性来修改元素的文本内容。例如:

<p id="textParagraph">原始文本</p>
const textParagraph = document.getElementById('textParagraph');
textParagraph.textContent = '修改后的文本';

innerHTML 不同,textContent 会将内容作为纯文本处理,不会解析其中的 HTML 标签,这在防止跨站脚本攻击(XSS)方面更为安全。

修改 HTML 内容

如果确实需要修改元素的 HTML 内容,可以使用 innerHTML 属性。例如:

<div id="htmlDiv"></div>
const htmlDiv = document.getElementById('htmlDiv');
htmlDiv.innerHTML = '<p>这是一段 <strong>加粗</strong> 的文本</p>';

但要注意,使用 innerHTML 时要确保内容的安全性,避免引入恶意脚本。

修改样式

可以通过修改元素的 style 属性来直接设置元素的样式。例如:

<button id="styledButton">点击我</button>
const styledButton = document.getElementById('styledButton');
styledButton.style.backgroundColor = 'green';
styledButton.style.color = 'white';

也可以通过添加或移除类名来间接修改样式,这样可以更好地管理样式。例如,定义一个 CSS 类:

.highlight {
    background - color: yellow;
    font - weight: bold;
}

然后在 JavaScript 中添加或移除这个类:

<p id="styledParagraph">这是一段文本</p>
const styledParagraph = document.getElementById('styledParagraph');
// 添加类
styledParagraph.classList.add('highlight');
// 移除类
styledParagraph.classList.remove('highlight');

删除 DOM 元素的最佳实践

有时我们需要从文档中删除不再需要的 DOM 元素。

使用 removeChild 方法

要删除一个元素,可以使用父元素的 removeChild 方法。例如,有一个 parentDiv 包含一个 childDiv

<div id="parentDiv">
    <div id="childDiv">要删除的子 div</div>
</div>
const parentDiv = document.getElementById('parentDiv');
const childDiv = document.getElementById('childDiv');
parentDiv.removeChild(childDiv);

使用 remove 方法

从 DOM 4 开始,元素本身提供了 remove 方法,可以直接调用该方法删除自身。例如:

<p id="removableParagraph">要删除的段落</p>
const removableParagraph = document.getElementById('removableParagraph');
removableParagraph.remove();

这种方式更为简洁,并且不需要获取父元素。

事件处理与 DOM 操作的最佳实践

事件处理是使网页具有交互性的关键,而在事件处理中常常伴随着 DOM 操作。

绑定事件的方式

  1. 直接在 HTML 中绑定:可以在 HTML 元素的属性中直接绑定事件处理函数。例如:
<button onclick="handleClick()">点击我</button>
<script>
    function handleClick() {
        alert('按钮被点击了');
    }
</script>

这种方式简单直观,但不利于代码的维护和分离,不推荐在大型项目中使用。 2. 通过 JavaScript 属性绑定:可以通过元素的事件属性来绑定事件处理函数。例如:

<button id="clickButton">点击我</button>
<script>
    const clickButton = document.getElementById('clickButton');
    clickButton.onclick = function () {
        alert('按钮被点击了');
    };
</script>

这种方式比在 HTML 中直接绑定要好一些,但一个元素只能绑定一个相同类型的事件处理函数。 3. 使用 addEventListener 方法:这是推荐的绑定事件的方式。它可以为一个元素绑定多个相同类型的事件处理函数,并且支持捕获和冒泡阶段。例如:

<button id="multiClickButton">点击我</button>
<script>
    const multiClickButton = document.getElementById('multiClickButton');
    multiClickButton.addEventListener('click', function () {
        alert('第一次点击');
    });
    multiClickButton.addEventListener('click', function () {
        alert('第二次点击');
    });
</script>

事件委托

事件委托是一种优化的事件处理方式,它利用了事件冒泡的原理。例如,有一个包含多个列表项的 ul

<ul id="list">
    <li>列表项1</li>
    <li>列表项2</li>
    <li>列表项3</li>
</ul>

如果要为每个列表项添加点击事件处理函数,不使用事件委托的话,需要为每个列表项分别绑定事件。而使用事件委托,可以在 ul 上绑定一个点击事件处理函数,然后根据事件目标来判断是哪个列表项被点击了:

const list = document.getElementById('list');
list.addEventListener('click', function (event) {
    if (event.target.tagName === 'LI') {
        alert('点击了列表项:' + event.target.textContent);
    }
});

事件委托不仅减少了事件处理函数的数量,提高了性能,还方便动态添加或删除列表项时,不需要重新绑定事件。

性能优化与 DOM 操作的最佳实践

在进行 DOM 操作时,性能是一个重要的考虑因素。以下是一些性能优化的最佳实践。

批量操作 DOM

尽量减少对 DOM 的直接操作次数。例如,如果要对一个元素进行多次样式修改,不要每次修改都直接操作 DOM,而是先在 JavaScript 中进行组合,然后一次性应用到 DOM 上。例如:

<div id="performanceDiv">这是一个 div</div>
const performanceDiv = document.getElementById('performanceDiv');
// 不好的做法,多次操作 DOM
performanceDiv.style.color ='red';
performanceDiv.style.fontSize = '16px';
performanceDiv.style.backgroundColor = 'lightgray';

// 好的做法,批量操作 DOM
const newStyles = {
    color:'red',
    fontSize: '16px',
    backgroundColor: 'lightgray'
};
const styleString = Object.entries(newStyles).map(([key, value]) => `${key}:${value}`).join(';');
performanceDiv.style.cssText = styleString;

使用文档片段(DocumentFragment)

文档片段是一个轻量级的 DOM 容器,它存在于内存中,不会直接影响文档的渲染。当需要大量创建和插入元素时,可以先将元素添加到文档片段中,然后一次性将文档片段插入到 DOM 中。例如:

<ul id="largeList"></ul>
const largeList = document.getElementById('largeList');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const listItem = document.createElement('li');
    listItem.textContent = `列表项 ${i}`;
    fragment.appendChild(listItem);
}
largeList.appendChild(fragment);

这样可以减少文档重排和重绘的次数,提高性能。

避免频繁读取和修改布局属性

当读取元素的布局属性(如 offsetWidthclientHeight 等)时,浏览器可能需要重新计算布局。如果在一个循环中频繁读取和修改布局属性,会导致性能问题。例如:

<div id="layoutDiv"></div>
const layoutDiv = document.getElementById('layoutDiv');
// 不好的做法,频繁读取和修改布局属性
for (let i = 0; i < 100; i++) {
    layoutDiv.style.width = (layoutDiv.offsetWidth + 10) + 'px';
}

// 好的做法,先计算,再一次性修改
let width = layoutDiv.offsetWidth;
for (let i = 0; i < 100; i++) {
    width += 10;
}
layoutDiv.style.width = width + 'px';

跨浏览器兼容性与 DOM 操作

不同的浏览器在对 DOM 标准的实现上可能存在差异,因此在进行 DOM 操作时需要考虑跨浏览器兼容性。

特性检测

特性检测是一种判断浏览器是否支持某个 DOM 特性的方法。例如,要检测浏览器是否支持 querySelector 方法:

if ('querySelector' in document) {
    const element = document.querySelector('.test');
    if (element) {
        // 进行操作
    }
} else {
    // 使用其他兼容的方法
}

通过特性检测,可以确保代码在不同浏览器中都能正确运行。

兼容性库

在一些复杂的项目中,可以使用兼容性库,如 jQuery。jQuery 提供了统一的 API 来操作 DOM,屏蔽了不同浏览器之间的差异。例如,使用 jQuery 选择元素和修改样式:

<script src="https://code.jquery.com/jquery - 3.6.0.min.js"></script>
<div class="testDiv">测试 div</div>
<script>
    $('.testDiv').css('background - color', 'blue');
</script>

虽然使用兼容性库可以简化开发,但也会增加项目的体积,需要根据项目的实际情况来选择是否使用。

通过遵循以上这些关于 JavaScript 进行 DOM 操作的最佳实践,可以编写出高效、健壮且易于维护的前端代码,提升网页的性能和用户体验。无论是小型项目还是大型复杂的应用,这些实践都具有重要的指导意义。在实际开发中,需要不断实践和总结,根据具体的需求和场景灵活运用这些方法和技巧。