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

JavaScript事件处理与DOM操作

2023-07-127.5k 阅读

JavaScript事件处理基础

在JavaScript中,事件是指文档或浏览器窗口中发生的特定交互或行为。比如用户点击按钮、鼠标移动、页面加载完成等。事件处理机制允许我们编写代码来响应这些事件,从而实现动态交互的网页。

事件类型

  1. 鼠标事件
    • click:当用户点击元素时触发。例如,点击一个按钮:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>鼠标点击事件</title>
</head>

<body>
    <button id="myButton">点击我</button>
    <script>
        const myButton = document.getElementById('myButton');
        myButton.addEventListener('click', function () {
            alert('你点击了按钮');
        });
    </script>
</body>

</html>
- **mouseover**:当鼠标指针移动到元素上方时触发。可以用于实现悬停效果,比如改变元素的样式:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>鼠标悬停事件</title>
    <style>
        #box {
            width: 100px;
            height: 100px;
            background - color: lightblue;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <script>
        const box = document.getElementById('box');
        box.addEventListener('mouseover', function () {
            this.style.backgroundColor = 'lightgreen';
        });
        box.addEventListener('mouseout', function () {
            this.style.backgroundColor = 'lightblue';
        });
    </script>
</body>

</html>
- **mousedown**:当鼠标按钮在元素上按下时触发,**mouseup**:当鼠标按钮在元素上释放时触发。这两个事件可以用于实现类似绘图、拖放等功能。

2. 键盘事件 - keydown:当用户按下键盘上的某个键时触发。可以获取按下的键的信息,比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>键盘按下事件</title>
</head>

<body>
    <input type="text" id="myInput">
    <script>
        const myInput = document.getElementById('myInput');
        myInput.addEventListener('keydown', function (event) {
            console.log('你按下了键:', event.key);
        });
    </script>
</body>

</html>
- **keyup**:当用户释放键盘上的某个键时触发。与keydown配合,可以实现实时输入检测等功能。

3. 表单事件 - submit:当表单被提交时触发。通常用于验证表单数据,比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>表单提交事件</title>
</head>

<body>
    <form id="myForm">
        <input type="text" id="name" placeholder="姓名">
        <input type="submit" value="提交">
    </form>
    <script>
        const myForm = document.getElementById('myForm');
        myForm.addEventListener('submit', function (event) {
            const nameInput = document.getElementById('name');
            if (nameInput.value === '') {
                alert('姓名不能为空');
                event.preventDefault();
            }
        });
    </script>
</body>

</html>
- **input**:当`<input>`或`<textarea>`元素的值发生变化时触发。可用于实时获取用户输入。

4. 文档加载事件 - load:当整个页面(包括所有资源,如图片、脚本等)加载完成后触发。常用于初始化页面所需的操作:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>页面加载事件</title>
</head>

<body>
    <script>
        window.addEventListener('load', function () {
            console.log('页面已完全加载');
        });
    </script>
</body>

</html>

事件处理程序的绑定方式

  1. HTML属性绑定 在HTML标签中直接使用事件属性来指定JavaScript代码。例如:
<button onclick="alert('你点击了按钮')">点击我</button>

这种方式简单直观,但当JavaScript代码复杂时,会使HTML代码变得混乱,不利于维护。 2. DOM对象属性绑定 通过获取DOM元素,然后为其事件属性赋值一个函数。比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>DOM对象属性绑定事件</title>
</head>

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

</html>

这种方式将JavaScript代码与HTML结构稍微分离,但一个元素的同一个事件只能绑定一个处理函数。如果多次赋值,后面的会覆盖前面的。 3. addEventListener方法绑定 这是现代JavaScript中推荐的方式。它允许为一个元素的同一个事件绑定多个处理函数,并且可以指定事件捕获或冒泡阶段。例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>addEventListener绑定事件</title>
</head>

<body>
    <button id="myButton">点击我</button>
    <script>
        const myButton = document.getElementById('myButton');
        myButton.addEventListener('click', function () {
            alert('第一个点击处理函数');
        });
        myButton.addEventListener('click', function () {
            alert('第二个点击处理函数');
        });
    </script>
</body>

</html>

语法为element.addEventListener(eventType, callback, useCapture),其中eventType是事件类型字符串(如'click'),callback是事件触发时执行的函数,useCapture是一个可选的布尔值,默认值为false,表示在事件冒泡阶段处理事件,若为true,则在事件捕获阶段处理事件。

事件流与事件委托

事件流

事件流描述了从页面中接收事件的顺序。在DOM中,事件流分为三个阶段:

  1. 捕获阶段:事件从最外层的祖先元素开始,向内层元素传播,直到到达目标元素。例如,当点击一个按钮时,事件首先从document对象开始,依次经过htmlbody等祖先元素,向按钮传播。
  2. 目标阶段:事件到达目标元素,此时事件处理程序在目标元素上执行。
  3. 冒泡阶段:事件从目标元素开始,向外层祖先元素传播,直到最外层的祖先元素。大多数情况下,我们在冒泡阶段处理事件,因为它更符合用户的直观感受,而且可以利用事件委托。

可以通过设置addEventListener的第三个参数useCapture来决定在捕获阶段还是冒泡阶段处理事件。例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>事件流示例</title>
</head>

<body>
    <div id="outer">
        <div id="inner">点击我</div>
    </div>
    <script>
        const outer = document.getElementById('outer');
        const inner = document.getElementById('inner');
        outer.addEventListener('click', function () {
            console.log('外层div(冒泡阶段)');
        });
        outer.addEventListener('click', function () {
            console.log('外层div(捕获阶段)');
        }, true);
        inner.addEventListener('click', function () {
            console.log('内层div(目标阶段)');
        });
    </script>
</body>

</html>

在这个例子中,当点击内层div时,首先会触发外层div捕获阶段的处理函数,然后是内层div目标阶段的处理函数,最后是外层div冒泡阶段的处理函数。

事件委托

事件委托是利用事件冒泡的特性,将多个子元素的事件委托给它们共同的祖先元素来处理。这样可以减少事件处理程序的数量,提高性能。

假设我们有一个列表,每个列表项都需要添加点击事件:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>事件委托示例</title>
</head>

<body>
    <ul id="myList">
        <li>列表项1</li>
        <li>列表项2</li>
        <li>列表项3</li>
    </ul>
    <script>
        const myList = document.getElementById('myList');
        myList.addEventListener('click', function (event) {
            if (event.target.tagName === 'LI') {
                console.log('你点击了:', event.target.textContent);
            }
        });
    </script>
</body>

</html>

在这个例子中,我们没有为每个li元素单独绑定点击事件,而是将点击事件委托给了它们的父元素ul。当点击某个li元素时,事件会冒泡到ul元素,我们在ul元素的点击事件处理程序中通过检查event.target来确定具体点击的是哪个li元素。

事件委托不仅减少了内存开销,还便于动态添加或删除子元素。当动态添加新的li元素时,不需要重新为其绑定事件,因为父元素的事件处理程序已经可以处理它们的点击事件。

DOM操作基础

文档对象模型(DOM)是HTML和XML文档的编程接口。它将文档表示为节点树,允许我们通过JavaScript对网页的结构、样式和内容进行动态修改。

获取DOM元素

  1. 通过ID获取 使用document.getElementById(id)方法,其中id是元素的唯一标识符。例如:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>通过ID获取元素</title>
</head>

<body>
    <div id="myDiv">这是一个div</div>
    <script>
        const myDiv = document.getElementById('myDiv');
        console.log(myDiv);
    </script>
</body>

</html>
  1. 通过标签名获取 使用document.getElementsByTagName(tagName)方法,它返回一个类似数组的对象,包含所有指定标签名的元素。例如:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>通过标签名获取元素</title>
</head>

<body>
    <p>段落1</p>
    <p>段落2</p>
    <script>
        const paragraphs = document.getElementsByTagName('p');
        for (let i = 0; i < paragraphs.length; i++) {
            console.log(paragraphs[i].textContent);
        }
    </script>
</body>

</html>
  1. 通过类名获取 使用document.getElementsByClassName(className)方法,返回所有具有指定类名的元素。例如:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>通过类名获取元素</title>
    <style>
       .myClass {
            color: red;
        }
    </style>
</head>

<body>
    <p class="myClass">红色文本1</p>
    <p class="myClass">红色文本2</p>
    <script>
        const elements = document.getElementsByClassName('myClass');
        for (let i = 0; i < elements.length; i++) {
            console.log(elements[i].textContent);
        }
    </script>
</body>

</html>
  1. 使用querySelector和querySelectorAll document.querySelector(selector)方法返回匹配指定CSS选择器的第一个元素,document.querySelectorAll(selector)方法返回所有匹配指定CSS选择器的元素组成的静态NodeList。例如:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>使用querySelector和querySelectorAll</title>
    <style>
        #myDiv {
            background - color: lightblue;
        }
       .myClass {
            color: green;
        }
    </style>
</head>

<body>
    <div id="myDiv">这是一个div</div>
    <p class="myClass">绿色文本</p>
    <script>
        const div = document.querySelector('#myDiv');
        console.log(div);
        const paragraphs = document.querySelectorAll('.myClass');
        for (let i = 0; i < paragraphs.length; i++) {
            console.log(paragraphs[i].textContent);
        }
    </script>
</body>

</html>

创建和插入新元素

  1. 创建元素 使用document.createElement(tagName)方法创建一个新的元素节点。例如,创建一个新的div元素:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>创建新元素</title>
</head>

<body>
    <script>
        const newDiv = document.createElement('div');
        newDiv.textContent = '这是新创建的div';
        console.log(newDiv);
    </script>
</body>

</html>
  1. 插入元素
    • appendChild:将一个节点添加到指定父节点的子节点列表末尾。例如,将上述创建的div添加到body中:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>插入新元素 - appendChild</title>
</head>

<body>
    <script>
        const newDiv = document.createElement('div');
        newDiv.textContent = '这是新创建的div';
        document.body.appendChild(newDiv);
    </script>
</body>

</html>
- **insertBefore**:在指定的现有子节点之前插入新节点。语法为`parentNode.insertBefore(newNode, referenceNode)`,其中`parentNode`是父节点,`newNode`是要插入的新节点,`referenceNode`是现有子节点,新节点将插入到该子节点之前。例如:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>插入新元素 - insertBefore</title>
</head>

<body>
    <div id="parent">
        <p id="ref">这是参考段落</p>
    </div>
    <script>
        const parent = document.getElementById('parent');
        const ref = document.getElementById('ref');
        const newDiv = document.createElement('div');
        newDiv.textContent = '这是新创建的div';
        parent.insertBefore(newDiv, ref);
    </script>
</body>

</html>

修改元素内容和属性

  1. 修改文本内容 可以通过element.textContentelement.innerText来修改元素的文本内容。textContent会返回或设置元素及其后代的纯文本内容,而innerText会考虑CSS样式,例如隐藏元素的文本不会被innerText获取或设置。例如:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>修改文本内容</title>
</head>

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

</html>
  1. 修改HTML内容 使用element.innerHTML可以修改元素的HTML内容。但要注意,使用innerHTML可能会带来安全风险,如跨站脚本攻击(XSS),如果内容来自用户输入,应进行适当的过滤。例如:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>修改HTML内容</title>
</head>

<body>
    <div id="myDiv"></div>
    <script>
        const myDiv = document.getElementById('myDiv');
        myDiv.innerHTML = '<p>这是通过innerHTML添加的段落</p>';
    </script>
</body>

</html>
  1. 修改元素属性 使用element.setAttribute(attributeName, attributeValue)方法来设置元素的属性,使用element.getAttribute(attributeName)方法来获取元素的属性值。例如,修改一个img元素的src属性:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>修改元素属性</title>
</head>

<body>
    <img id="myImg" src="old.jpg">
    <script>
        const myImg = document.getElementById('myImg');
        myImg.setAttribute('src', 'new.jpg');
    </script>
</body>

</html>

综合应用:实现一个简单的待办事项列表

通过结合事件处理和DOM操作,我们可以实现一个简单的待办事项列表。

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>待办事项列表</title>
    <style>
        #input {
            width: 300px;
            padding: 10px;
            margin - bottom: 10px;
        }
        #addButton {
            padding: 10px 20px;
        }
        ul {
            list - style - type: none;
            padding: 0;
        }
        li {
            padding: 10px;
            border: 1px solid #ccc;
            margin - bottom: 5px;
        }
       .completed {
            text - decoration: line - through;
        }
    </style>
</head>

<body>
    <input type="text" id="input" placeholder="输入待办事项">
    <button id="addButton">添加</button>
    <ul id="todoList"></ul>
    <script>
        const input = document.getElementById('input');
        const addButton = document.getElementById('addButton');
        const todoList = document.getElementById('todoList');
        addButton.addEventListener('click', function () {
            const newItem = document.createElement('li');
            newItem.textContent = input.value;
            const deleteButton = document.createElement('button');
            deleteButton.textContent = '删除';
            const completeButton = document.createElement('button');
            completeButton.textContent = '完成';
            newItem.appendChild(completeButton);
            newItem.appendChild(deleteButton);
            todoList.appendChild(newItem);
            input.value = '';
            deleteButton.addEventListener('click', function () {
                todoList.removeChild(newItem);
            });
            completeButton.addEventListener('click', function () {
                newItem.classList.toggle('completed');
            });
        });
    </script>
</body>

</html>

在这个例子中,当用户在输入框中输入内容并点击“添加”按钮时,会创建一个新的li元素,包含待办事项内容以及“删除”和“完成”按钮。点击“删除”按钮会从列表中移除该项,点击“完成”按钮会为该项添加或移除“completed”类,从而实现文本的删除线效果,标记待办事项为已完成。

通过这样的方式,我们将JavaScript的事件处理和DOM操作紧密结合,实现了一个具有交互功能的简单应用。在实际开发中,类似的技术可以应用于更复杂的Web应用程序,如单页应用(SPA)等,为用户提供丰富的动态交互体验。

同时,在处理DOM操作时,要注意性能问题。频繁的DOM操作会导致浏览器重排和重绘,影响页面的性能。可以通过批量操作DOM,例如先创建文档片段(document.createDocumentFragment),在片段上进行多次操作,最后将片段添加到DOM树中,这样可以减少重排和重绘的次数,提高性能。例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>文档片段优化DOM操作</title>
</head>

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

</html>

在这个例子中,我们通过文档片段一次性创建并添加了10个li元素,而不是逐个添加,减少了对DOM树的多次修改,从而提高了性能。

在事件处理方面,合理使用事件委托不仅可以减少内存开销,还能提高代码的可维护性。特别是在处理大量相似元素的事件时,事件委托的优势更加明显。例如,在一个包含成百上千个列表项的列表中,如果为每个列表项单独绑定点击事件,会占用大量内存,而通过事件委托将点击事件处理程序绑定到列表的父元素上,可以显著优化性能。

另外,在处理复杂的交互逻辑时,要注意事件的顺序和相互影响。例如,在一个既有鼠标点击又有键盘操作的表单中,要确保各个事件处理程序之间不会产生冲突,并且能够正确地响应用户的操作。可以通过设置合适的事件监听器和合理的逻辑判断来实现这一点。

总之,JavaScript的事件处理和DOM操作是Web开发中非常重要的技能,通过深入理解和灵活运用它们,可以创建出功能丰富、交互性强的Web应用程序。同时,要时刻关注性能优化和代码的可维护性,以确保应用程序在各种场景下都能高效运行。