2.png 0
8i98

家事,国事,天下事,事事关心

2022-03-13

js笔记06-DOM对象-事件

2022-05-22 0 62 web前端社区

2.png 0 8i98

DOM对象

DOM

DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。

浏览器会根据 DOM 模型,将结构化文档(比如 HTML 和 XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口。

DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript 最常见的任务,离开了 DOM,JavaScript 就无法控制网页。另一方面,JavaScript 也是最常用于 DOM 操作的语言。后面介绍的就是 JavaScript 对 DOM 标准的实现和用法。

节点

DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。每个节点可以看作是文档树的一片叶子。

节点的类型有七种。

  • Document:整个文档树的顶层节点
  • DocumentTypedoctype标签(比如<!DOCTYPE html>
  • Element:网页的各种HTML标签(比如<body><a>等)
  • Attr:网页元素的属性(比如class="right"
  • Text:标签之间或标签包含的文本
  • Comment:注释
  • DocumentFragment:文档的片段

浏览器提供一个原生的节点对象Node,上面这七种节点都继承了Node,因此具有一些共同的属性和方法。

节点树

一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,又像一棵树。

浏览器原生提供document节点,代表整个文档。

  1. document
  2. // 整个文档树

文档的第一层有两个节点,第一个是文档类型节点(<!doctype html>),第二个是 HTML 网页的顶层容器标签<html>。后者构成了树结构的根节点(root node),其他 HTML 标签节点都是它的下级节点。

除了根节点,其他节点都有三种层级关系。

  • 父节点关系(parentNode):直接的那个上级节点
  • 子节点关系(childNodes):直接的下级节点
  • 同级节点关系(sibling):拥有同一个父节点的节点

DOM 提供操作接口,用来获取这三种关系的节点。比如,子节点接口包括firstChild(第一个子节点)和lastChild(最后一个子节点)等属性,同级节点接口包括nextSibling(紧邻在后的那个同级节点)和previousSibling(紧邻在前的那个同级节点)属性。

Document 对象

当浏览器加载一个 HTML 文档时,会创建一个 Document 对象,Document 对象是 DOM 树中所有节点的根节点。通过 Document 对象我们可以访问 HTML 文档中的所有元素。

提示:Document 对象是 Window 对象的一部分,所以您可以通过 window.document 来访问 Document 对象。

Document 对象中的属性

下表中列举了 Document 对象中提供的属性及其描述:

属性 描述
document.activeElement 返回当前获取焦点的元素
document.anchors 返回对文档中所有 Anchor 对象的引用
document.applets 返回对文档中所有 Applet 对象的引用。注意: HTML5 已不支持 元素
document.baseURI 返回文档的基础 URI
document.body 返回文档的 body 元素
document.cookie 设置或返回与当前文档有关的所有 cookie
document.doctype 返回与文档相关的文档类型声明 (DTD)
document.documentElement 返回文档的根节点
document.documentMode 返回浏览器渲染文档的模式
document.documentURI 设置或返回文档的位置
document.domain 返回当前文档的域名
document.domConfig 已废弃,返回 normalizeDocument() 被调用时所使用的配置
document.embeds 返回文档中所有嵌入内容(embed)的集合
document.forms 返回文档中所有 Form 对象的引用
document.images 返回文档中所有 Image 对象的引用
document.implementation 返回处理该文档的 DOMImplementation 对象
document.inputEncoding 返回文档的编码方式
document.lastModified 返回文档的最后修改日期
document.links 返回对文档中所有 Area 和 Link 对象的引用
document.readyState 返回文档状态(载入中)
document.referrer 返回载入当前文档的 URL
document.scripts 返回页面中所有脚本的集合
document.strictErrorChecking 设置或返回是否强制进行错误检查
document.title 返回当前文档的标题
document.URL 返回文档的完整 URL

Document 对象中的方法

下表中列举了 Document 对象中提供的方法及其描述:

方法 描述
document.addEventListener() 向文档中添加事件
document.adoptNode(node) 从另外一个文档返回 adapded 节点到当前文档
document.close() 关闭使用 document.open() 方法打开的输出流,并显示选定的数据
document.createAttribute() 为指定标签添加一个属性节点
document.createComment() 创建一个注释节点
document.createDocumentFragment() 创建空的 DocumentFragment 对象,并返回此对象
document.createElement() 创建一个元素节点
document.createTextNode() 创建一个文本节点
document.getElementsByClassName() 返回文档中所有具有指定类名的元素集合
document.getElementById() 返回文档中具有指定 id 属性的元素
document.getElementsByName() 返回具有指定 name 属性的对象集合
document.getElementsByTagName() 返回具有指定标签名的对象集合
document.importNode() 把一个节点从另一个文档复制到该文档以便应用
document.normalize() 删除空文本节点,并合并相邻的文本节点
document.normalizeDocument() 删除空文本节点,并合并相邻的节点
document.open() 打开一个流,以收集来自 document.write() 或 document.writeln() 方法的输出
document.querySelector() 返回文档中具有指定 CSS 选择器的第一个元素
document.querySelectorAll() 返回文档中具有指定 CSS 选择器的所有元素
document.removeEventListener() 移除文档中的事件句柄
document.renameNode() 重命名元素或者属性节点
document.write() 向文档中写入某些内容
document.writeln() 等同于 write() 方法,不同的是 writeln() 方法会在末尾输出一个换行符

示例代码如下:

  1. document.addEventListener("click", function(){ document.body.innerHTML = document.activeElement; var box = document.createElement('div'); document.body.appendChild(box); var att = document.createAttribute('id'); att.value = "myDiv"; document.getElementsByTagName('div')[0].setAttributeNode(att); document.getElementById("myDiv").innerHTML = Math.random(); var btn = document.createElement("button"); var t = document.createTextNode("按钮"); btn.appendChild(t); document.body.appendChild(btn); var att = document.createAttribute('onclick'); att.value = "myfunction()"; document.getElementsByTagName('button')[0].setAttributeNode(att);});function myfunction(){ alert(document.title);}

运行上面的代码,点击页面的空白区域,即可输出如下图所示的内容

Element 对象中的属性

Element节点对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一个Element节点对象(以下简称元素节点)。

元素节点的nodeType属性都是1

  1. var p = document.querySelector('p');
  2. p.nodeName // "P"
  3. p.nodeType // 1

Element对象继承了Node接口,因此Node的属性和方法在Element对象都存在。

此外,不同的 HTML 元素对应的元素节点是不一样的,浏览器使用不同的构造函数,生成不同的元素节点,比如<a>元素的构造函数是HTMLAnchorElement()<button>HTMLButtonElement()。因此,元素节点不是一种对象,而是许多种对象,这些对象除了继承Element对象的属性和方法,还有各自独有的属性和方法。

下表中列举了 JavaScript Element 对象中提供的属性及其描述:

属性 描述
element.accessKey 设置或返回一个访问单选按钮的快捷键
element.attributes 返回一个元素的属性数组
element.childNodes 返回元素的一个子节点的数组
element.children 返回元素中子元素的集合
element.classList 返回元素中类名组成的对象
element.className 设置或返回元素的 class 属性
element.clientHeight 返回内容的可视高度(不包括边框,边距或滚动条)
element.clientWidth 返回内容的可视宽度(不包括边框,边距或滚动条)
element.contentEditable 设置或返回元素的内容是否可编辑
element.dir 设置或返回一个元素中的文本方向
element.firstChild 返回元素中的第一个子元素
element.id 设置或者返回元素的 id
element.innerHTML 设置或者返回元素的内容
element.isContentEditable 返回元素内容是否可编辑,如果可编辑则返回 true,否则返回 false
element.lang 设置或者返回一个元素的语言
element.lastChild 返回元素的最后一个子元素
element.namespaceURI 返回命名空间的 URI
element.nextSibling 返回指定元素之后的兄弟元素,两个元素在 DOM 树中位于同一层级(包括文本节点、注释节点)
element.nextElementSibling 返回指定元素之后的兄弟元素,两个元素在 DOM 树中位于同一层级(不包括文本节点、注释节点)
element.nodeName 返回元素名称(大写)
element.nodeType 返回元素的节点类型
element.nodeValue 返回元素的节点值
element.offsetHeight 返回元素的高度,包括边框和内边距,但不包括外边距
element.offsetWidth 返回元素的宽度,包括边框和内边距,但不包括外边距
element.offsetLeft 返回元素在水平方向的偏移量
element.offsetParent 返回距离该元素最近的进行过定位的父元素
element.offsetTop 返回元素在垂直方向的偏移量
element.ownerDocument 返回元素的根元素(文档对象)
element.parentNode 返回元素的父节点
element.previousSibling 返回元素之前的兄弟元素,两个元素在 DOM 树中位于同一层级(包括文本节点、注释节点)
element.previousElementSibling 返回元素之前的兄弟元素,两个元素在 DOM 树中位于同一层级(不包括文本节点、注释节点)
element.scrollHeight 返回元素的完整高度(包括被滚动条隐蔽的部分)
element.scrollLeft 设置或返回元素滚动条距离元素左侧的距离
element.scrollTop 设置或返回元素滚动条距离元素上方的距离
element.scrollWidth 返回元素的完整宽度(包括被滚动条隐蔽的部分)
element.style 设置或返回元素的样式属性
element.tabIndex 设置或返回元素的标签顺序
element.tagName 以字符的形式返回元素的名称(大写)
element.textContent 设置或返回某个元素以及其中的文本内容
element.title 设置或返回元素的 title 属性
element.length 返回对象的长度

Element 对象中的方法

下表中列举了 JavaScript Element 对象中提供的方法及其描述:

方法 描述
element.addEventListener() 为指定元素定义事件
element.appendChild() 为元素添加一个新的子元素
element.cloneNode() 克隆某个元素
element.compareDocumentPosition() 比较当前元素与指定元素在文档中的位置,返回值如下:1:表示两个元素没有关系,不属于同一文档;2:表示当前元素在指定元素之后;4:当前元素在指定元素之前;8:当前元素在指定元素之内;16:指定元素在当前元素之内;32:两个元素没有关系,或者它们是同一元素的两个属性。
element.focus() 使元素获得焦点
element.getAttribute() 通过属性名称获取指定元素的属性值
element.getAttributeNode() 通过属性名称获取指定元素得属性节点
element.getElementsByTagName() 通过标签名获取当前元素下的所有子元素的集合
element.getElementsByClassName() 通过类名获取当前元素下的子元素的集合
element.hasAttribute() 判断元素是否具有指定的属性,若存在则返回 true,不存在则返回 false
element.hasAttributes() 判断元素是否存在任何属性,若存在则返回 true,不存在则返回 false
element.hasChildNodes() 判断一个元素是否具有子元素,有则返回 true,没有则返回 false
element.hasFocus() 判断元素是否获得了焦点
element.insertBefore() 在已有子元素之前插入一个新的子元素
element.isDefaultNamespace() 如果指定 namespaceURI 是默认的则返回 true,否则返回 false。
element.isEqualNode() 检查两个元素是否相等
element.isSameNode() 检查当前元素与指定元素是否为同一元素
element.isSupported() 判断当前元素是否支持某个特性
element.normalize() 合并相邻的文本节点,并删除空的文本节点
element.querySelector() 根据 CSS 选择器,返回第一个匹配的元素
document.querySelectorAll() 根据 CSS 选择器,返回所有匹配的元素
element.removeAttribute() 从元素中删除指定的属性
element.removeAttributeNode() 从元素中删除指定的属性节点
element.removeChild() 删除一个子元素
element.removeEventListener() 移除由 addEventListener() 方法添加的事件
element.replaceChild(newnode,oldnew ) 替换一个子元素
element.setAttribute(key,value) 设置或者修改指定属性的值
element.setUserData() 在元素中为指定键值关联对象
element.toString() 将元素转换成字符串
element.dataset.index 获取data-index的属性

示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body onload="accesskey()">
  8. <a id="r" class="aaa bbb ccc" href="javascript:;">使用 Alt + r 访问该元素</a><br>
  9. <a id="g" href="javascript:;">使用 Alt + g 访问该元素</a>
  10. <script type="text/javascript">
  11. function accesskey(){
  12. document.getElementById('r').accessKey="r"
  13. document.getElementById('g').accessKey="g"
  14. }
  15. var ele = document.getElementById('r');
  16. console.log(ele.attributes); // 输出:NamedNodeMap {0: id, 1: href, id: id, href: href, length: 2}
  17. console.log(document.body.childNodes); // 输出:NodeList(7) [text, a#r, br, text, a#g, text, script]
  18. console.log(ele.classList); // 输出:DOMTokenList(3) ["aaa", "bbb", "ccc", value: "aaa bbb ccc"]
  19. console.log(ele.className); // 输出:aaa bbb ccc
  20. console.log(ele.clientHeight); // 输出:DOMTokenList(3) ["aaa", "bbb", "ccc", value: "aaa bbb ccc"]
  21. console.log(ele.tagName); // 输出:A
  22. console.log(ele.compareDocumentPosition(document.getElementById('g'))); // 输出:4
  23. console.log(ele.getAttribute('href')); // 输出:javascript:;
  24. console.log(ele.getAttributeNode('href')); // 输出:href="javascript:;"
  25. </script>
  26. </body>
  27. </html>

js事件

JS 事件(event)是当用户与网页进行交互时发生的事情,例如单机某个链接或按钮、在文本框中输入文本、按下键盘上的某个按键、移动鼠标等等。当事件发生时,您可以使用 JavaScript 中的事件处理程序(也可称为事件监听器)来检测并执行某些特定的程序。

一般情况下事件的名称都是以单词

  1. on

开头的,例如点击事件 onclick、页面加载事件 onload 等。下表中列举了一些 JavaScript 中常用的事件:

事件 描述
鼠标、键盘事件 onclick 点击鼠标时触发此事件
ondblclick 双击鼠标时触发此事件
onmousedown 按下鼠标时触发此事件
onmouseup 鼠标按下后又松开时触发此事件
onmouseover 当鼠标移动到某个元素上方时触发此事件
onmousemove 移动鼠标时触发此事件
onmouseout 当鼠标离开某个元素范围时触发此事件
onkeypress 当按下并松开键盘上的某个键时触发此事件
onkeydown 当按下键盘上的某个按键时触发此事件
onkeyup 当放开键盘上的某个按键时触发此事件
窗口事件 onabort 图片在下载过程中被用户中断时触发此事件
onbeforeunload 当前页面的内容将要被改变时触发此事件
onerror 出现错误时触发此事件
onload 页面内容加载完成时触发此事件
onmove 当移动浏览器的窗口时触发此事件
onresize 当改变浏览器的窗口大小时触发此事件
onscroll 当滚动浏览器的滚动条时触发此事件
onstop 当按下浏览器的停止按钮或者正在下载的文件被中断时触发此事件
oncontextmenu 当弹出右键上下文菜单时触发此事件
onunload 改变当前页面时触发此事件
表单事件 onblur 当前元素失去焦点时触发此事件
onchange 当前元素失去焦点并且元素的内容发生改变时触发此事件
onfocus 当某个元素获得焦点时触发此事件
onreset 当点击表单中的重置按钮时触发此事件
onsubmit 当提交表单时触发此事件

一.Dom对象属性——距离

  1. element.scrollTop 设置或返回元素滚动条距离元素上方的距离

1.obj.offsetWidth//返回元素的宽度(包括元素宽度,内边距和边框);
2.obj.offsetHeight//返回元素的高度(包括元素的高度,内边距和边距);
3.obj.offsetLeft//元素到开定位属性的父级元素的左偏移量;
4.obj.offsetTop//元素到开定位属性的父级元素的上偏移量;
5.obj.clientWidth//返回元素的可视宽(包括元素宽度,内边距,不包括边框);
6.obj.clientHeight//返回元素的可视高(包括元素的高,内边距,不包括边框);
7.e.offsetX//鼠标点击处相对于事件源左上角位置的水平距离;
8.e.offsetY//鼠标点击处相对于事件源左上角位置的竖直距离;
9.e.clientX//鼠标点击处相对于浏览器窗口的水平偏移量;
10.e.clientY//鼠标点击处相对于浏览器窗口的竖直偏移量;
11.e.pageX//鼠标点击处相对于页面的水平偏移量(没有滚动条时与e.clientX相同,有滚动条时相当于e.clientX+scrollLeft)
12.e.pageY//鼠标点击处相对于页面的竖直偏移量(没有滚动条时与e.clientY相同,有滚动条时相当于e.clientY+scrollTop)

事件绑定

事件只有与 HTML 元素绑定之后才能被触发,为 HTML 元素绑定事件处理程序的方法由很多,最简单的就是通过

HTML 事件属性

来直接绑定事件处理程序,例如 onclick、onmouseover、onmouseout 等属性。

以 onclick 属性为例,通过该属性我们可以为指定的 HTML 元素定义鼠标点击事件(即在该元素上单击鼠标左键时触发的事件),示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <button type="button" onclick="myBtn()">按钮</button>
  9. <script type="text/javascript">
  10. function myBtn(){
  11. alert("Hello World!");
  12. }
  13. </script>
  14. </body>
  15. </html>

除了上述方法外,我们也可以直接使用 JavaScript 中提供的内置函数来为指定元素绑定事件处理程序,如下例所示:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <button type="button" id="myBtn">按钮</button>
  9. <script>
  10. function sayHello() {
  11. alert('Hello World!');
  12. }
  13. document.getElementById("myBtn").onclick = sayHello;
  14. </script>
  15. </body>
  16. </html>

JS 事件示例

一般情况下,事件可以分为四大类——鼠标事件、键盘事件、表单事件和窗口事件,另外还有一些其它事件。下面通过几个示例来简单介绍一些比较常用的事件。

1) onmouseover 事件

onmouseover 事件就是指当用户鼠标指针移动到元素上时触发的事件,示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <button type="button" onmouseover="alert('您的鼠标已经移动到了该按钮上');">请将鼠标移动至此处</button><br>
  9. <a href="#" onmouseover="myEvent()">请将鼠标移动至此处</a>
  10. <script>
  11. function myEvent() {
  12. alert('您的鼠标已经移动到了该链接上');
  13. }
  14. </script>
  15. </body>
  16. </html>

2) onmouseout 事件

onmouseout 事件与 onmouseover 事件正好相反,onmouseout 事件会在鼠标从元素上离开时触发,示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <div style="width: 350px; height: 200px; border:1px solid black" id="myBox"></div>
  9. <script>
  10. function myEvent() {
  11. alert('您的鼠标已经离开指定元素');
  12. }
  13. document.getElementById("myBox").onmouseout = myEvent;
  14. </script>
  15. </body>
  16. </html>

3) onkeydown 事件

onkeydown 事件是指当用户按下键盘上的某个按键时触发的事件,示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <input type="text" onkeydown="myEvent()">
  9. <script>
  10. function myEvent() {
  11. alert("您按下了键盘上的某个按钮");
  12. }
  13. </script>
  14. </body>
  15. </html>

4) onkeyup 事件

onkeyup 事件是指当用户按下键盘上的某个按键并将其释放(即按下并松开某个按键)时触发的事件,示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <input type="text" onkeyup="myEvent()">
  9. <script>
  10. function myEvent() {
  11. alert("您按下了键盘上的某个按钮,并将其释放了");
  12. }
  13. </script>
  14. </body>
  15. </html>

区别:onmouseover/onmouseout 触发子元素的事件时,子元素通过事件冒泡触发父元素对应的事件;
可以通过阻止冒泡 stopPropagation() 避免父元素事件触发;
onmouseenter/onmouseleave 触发子元素事件时,不会触发父元素对应的事件,内部已经进行了阻止事件冒泡的处理;

JS事件冒泡与事件捕获

在 JavaScript 中,我们将事件发生的顺序称为“事件流”,当我们触发某个事件时,会发生一些列的连锁反应,例如有如下所示的一段代码:

  1. <body>
  2. <div id="wrap">
  3. <p class="hint">
  4. <a href="#">Click Me</a>
  5. </p>
  6. </div>
  7. </body>

如果给每个标签都定义事件,当我们点击其中的

  1. <a>

标签时,会发现绑定在

  1. <div>

  1. <p>

标签上的事件也被触发了,这到底是为什么呢?为了解答这一问题,微软和网景两公司提出了两种不同的概念,事件捕获与事件冒泡:

  • 事件捕获:由微软公司提出,事件从文档根节点(Document 对象)流向目标节点,途中会经过目标节点的各个父级节点,并在这些节点上触发捕获事件,直至到达事件的目标节点;
  • 事件冒泡:由网景公司提出,与事件捕获相反,事件会从目标节点流向文档根节点,途中会经过目标节点的各个父级节点,并在这些节点上触发捕获事件,直至到达文档的根节点。整个过程就像水中的气泡一样,从水底向上运动。

提示:上面提到的目标节点指的是触发事件的节点。

后来,W3C 为了统一标准,采用了一个折中的方式,即将事件捕获与事件冒泡合并,也就是现在的“先捕获后冒泡”,如下图所示:

事件捕获与事件冒泡
图:事件捕获与事件冒泡

事件捕获

在事件捕获阶段,事件会从 DOM 树的最外层开始,依次经过目标节点的各个父节点,并触发父节点上的事件,直至到达事件的目标节点。

  1. <a>

标签,则该事件将通过

  1. document -> div -> p -> a

的顺序传递到

  1. <a>

标签。

示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. <style type="text/css">
  7. div, p, a {
  8. padding: 15px 30px;
  9. display: block;
  10. border: 2px solid #000;
  11. background: #fff;
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <div id="wrap">DIV
  17. <p class="hint">P
  18. <a href="#">A</a>
  19. </p>
  20. </div>
  21. <script>
  22. function showTagName() {
  23. alert("事件捕获: " + this.tagName);
  24. }
  25. var elems = document.querySelectorAll("div, p, a");
  26. for (let elem of elems) {
  27. elem.addEventListener("click", showTagName, true);
  28. }
  29. </script>
  30. </body>
  31. </html>

运行上面的代码,单击最内层的

  1. <a>

标签,运行结果如下图所示:

事件捕获演示
图:事件捕获演示

事件冒泡

事件冒泡正好与事件捕获相反,事件冒泡是从目标节点开始,沿父节点依次向上,并触发父节点上的事件,直至文档根节点,就像水底的气泡一样,会一直向上。

示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. <style type="text/css">
  7. div, p, a {
  8. padding: 15px 30px;
  9. display: block;
  10. border: 2px solid #000;
  11. background: #fff;
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <div onclick="alert('事件冒泡: ' + this.tagName)">DIV
  17. <p onclick="alert('事件冒泡: ' + this.tagName)">P
  18. <a href="#" onclick="alert('事件冒泡: ' + this.tagName)">A</a>
  19. </p>
  20. </div>
  21. </body>
  22. </html>

运行上面的代码,单击最内层的

  1. <a>

标签,运行结果如下图所示:

事件冒泡演示
图:事件冒泡演示

阻止事件捕获和冒泡

了解了事件捕获和事件冒泡后会发现,这个特性并不友好,例如我们在某个节点上绑定了事件,本想在点击时触发这个事件,结果由于事件冒泡,这个节点的事件被它的子元素给触发了。我们要如何阻止这样的事情发生呢?

JavaScript 中提供了 stopPropagation() 方法来阻止事件捕获和事件冒泡的发生,语法格式如下:

event.stopPropagation();

注意:stopPropagation() 会阻止事件捕获和事件冒泡,但是无法阻止标签的默认行为,例如点击链接任然可以打开对应网页。

示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. <style type="text/css">
  7. div, p, a {
  8. padding: 15px 30px;
  9. display: block;
  10. border: 2px solid #000;
  11. background: #fff;
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <div id="wrap">DIV
  17. <p class="hint">P
  18. <a href="#">A</a>
  19. </p>
  20. </div>
  21. <script>
  22. function showAlert(event) {
  23. alert("您点击了 "+ this.tagName + " 标签");
  24. event.stopPropagation();
  25. }
  26. var elems = document.querySelectorAll("div, p, a");
  27. for(let elem of elems) {
  28. elem.addEventListener("click", showAlert);
  29. }
  30. </script>
  31. </body>
  32. </html>

此外,您也可以使用 stopImmediatePropagation() 方法来阻止同一节点的同一事件的其它事件处理程序,例如为某个节点定义了多个点击事件,当事件触发时,这些事件会按定义顺序依次执行,如果其中一个事件处理程序中使用了 stopImmediatePropagation() 方法,那么剩下的事件处理程序将不再执行。

stopImmediatePropagation() 方法的语法格式如下:

event.stopImmediatePropagation();

示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. <style type="text/css">
  7. div, p, a {
  8. padding: 15px 30px;
  9. display: block;
  10. border: 2px solid #000;
  11. background: #fff;
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <div onclick="alert('您点击了 ' + this.tagName + ' 标签')">DIV
  17. <p onclick="alert('您点击了 ' + this.tagName + ' 标签')">P
  18. <a href="#" id="link">A</a>
  19. </p>
  20. </div>
  21. <script>
  22. function sayHi() {
  23. alert("事件处理程序 1");
  24. event.stopImmediatePropagation();
  25. }
  26. function sayHello() {
  27. alert("事件处理程序 2");
  28. }
  29. // 为 id 为 link 的标签定义多个点击事件
  30. var link = document.getElementById("link");
  31. link.addEventListener("click", sayHi);
  32. link.addEventListener("click", sayHello);
  33. </script>
  34. </body>
  35. </html>

阻止默认操作

某些事件具有与之关联的默认操作,例如当您单击某个链接时,会自动跳转到指定的页面,当您单击提交按钮时,会将数据提交到服务器等。如果不想这样的默认操作发生,可以使用 preventDefault() 方法来阻止,其语法格式如下:

event.preventDefault();

示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <a href="http://c.biancheng.net/" id="link">链接</a>
  9. <script>
  10. var link = document.getElementById("link");
  11. link.addEventListener('click', function(event){
  12. event.preventDefault(); // 阻止链接跳转
  13. });
  14. </script>
  15. </body>
  16. </html>

注意:IE9 及以下的版本不支持 preventDefault() 方法,对于 IE9 及以下的浏览器您可以使用event.returnValue = false;

为什么要使用事件委托

在 JavaScript 中,页面内事件处理程序的个数会直接影响页面的整体性能,因为每个事件处理程序都是对象,对象会占用内存,内存中的对象越多,页面的性能则越差。此外,事件处理程序需要与 DOM 节点进行交互,访问 DOM 的次数越多,引起浏览器重绘和重排的次数也就越多,从而影响页面的性能。

重绘是指当元素样式改变时,浏览器会根据元素的新样式重新绘制元素的外观。重排是指当 DOM 树的一部分发生变化时(例如元素尺寸改变),浏览器会重新创建 DOM 树。

当页面中很多表格或列表需要添加事件时,如果逐个添加那就太麻烦了,但是使用事件委托就能极大的减轻我们的工作量,同时也能提高页面的性能。

事件委托实现原理

事件委托是利用事件的冒泡原理来实现的,大致可以分为三个步骤:

  1. 确定要添加事件元素的父级元素;
  2. 给父元素定义事件,监听子元素的冒泡事件;
  3. 使用 event.target 来定位触发事件冒泡的子元素。

注意:使用事件委托时,并不是说把事件委托给随意一个父元素就行。因为事件冒泡的过程也需要消耗时间,距离越远,所需的时间也就越长,所有最好在直接父元素上使用事件委托。

假如我们要为 ul 列表下的每个 li 标签添加点击事件,如果不使用事件委托,最简单的办法就是使用循环来为每个 li 标签绑定事件,示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <ul id="list">
  9. <li>1</li>
  10. <li>2</li>
  11. <li>3</li>
  12. <li>4</li>
  13. </ul>
  14. <script>
  15. window.onload = function(){
  16. var the_ul = document.getElementById('list');
  17. var the_li = the_ul.getElementsByTagName('li');
  18. for( var i=0; i < the_li.length; i++ ){
  19. the_li[i].onclick = function(){
  20. console.log(this.innerHTML)
  21. }
  22. }
  23. }
  24. </script>
  25. </body>
  26. </html>

通过上面的代码可以看出,要为每个 li 标签绑定点击事件,首先需要找到 ul 标签,然后通过 ul 标签找到所有 li 标签, 最后在通过遍历所有 li 标签来绑定事件。若使用事件委托的话,就会简单很多,示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <ul id="list">
  9. <li>1</li>
  10. <li>2</li>
  11. <li>3</li>
  12. <li>4</li>
  13. </ul>
  14. <script>
  15. window.onload = function(){
  16. var the_ul = document.getElementById('list');
  17. the_ul.onclick = function(e){
  18. console.log(e.target.innerHTML)
  19. }
  20. }
  21. </script>
  22. </body>
  23. </html>

通过代码可以看出,使用事件委托我们只需要为 ul 标签绑定事件,当 li 标签被点击时,由于事件冒泡的特性,会触发 ul 标签上的事件,我们只需要在事件中通过 event 对象中的 target 属性来找到被点击的 li 标签即可。不过这样做也有一个弊端,那就是当我们点击 ul 标签时,也会触发事件。

另外,如果我们需要动态的向 ul 标签中添加 li 标签,同时也需要在新添加的 li 标签中添加点击事件,就必须通过事件委托来实现了,示例代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>JavaScript</title>
  6. </head>
  7. <body>
  8. <ul id="list" style="width: 100px;margin:0;float: left;">
  9. <li>1</li>
  10. <li>2</li>
  11. <li>3</li>
  12. <li>4</li>
  13. </ul>
  14. <button style="float:left;" id="addli">添加一个 li</button>
  15. <button style="float:left;" id="delli">删除一个 li</button>
  16. <script>
  17. window.onload = function(){
  18. var the_ul = document.getElementById('list');
  19. var the_li = the_ul.getElementsByTagName('li');
  20. var sum = the_li.length
  21. the_ul.onclick = function(e){
  22. console.log(e.target.innerHTML)
  23. };
  24. document.getElementById('addli').onclick = function (){
  25. var newli = document.createElement("li");
  26. newli.innerHTML = ++sum;
  27. the_ul.appendChild(newli);
  28. };
  29. document.getElementById('delli').onclick = function (){
  30. the_ul.firstElementChild.remove();
  31. };
  32. }
  33. </script>
  34. </body>
  35. </html>

事件委托的优点

1) 减小内存消耗

使用事件委托可以大量节省内存,减少事件的定义,通过上面的示例可以看出,要为 ul 标签下的所有 li 标签添加点击事件,如果分别为每个 li 标签绑定事件,不仅写起来比较繁琐,而且对内存的消耗也非常大。而使用事件委托的方式将点击事件绑定到 ul 标签上,就可以实现监听所有 li 标签,简洁、高效。

2) 动态绑定事件

在网页中,有时我们需要动态增加或移除页面中的元素,比如上面示例中动态的在 ul 标签中添加 li 标签,如果不使用事件委托,则需要手动为新增的元素绑定事件,同时为删除的元素解绑事件。而使用事件委托就没有这么麻烦了,无论是增加还是减少 ul 标签中的 li 标签,即不需要再为新增的元素绑定事件,也不需要为删除的元素解绑事件。

所以使用事件委托动态绑定事件可以减少很多重复工作的。

总结

要使用事件委托,需要保证事件能够发生冒泡,适合使用事件委托的事件有 click、mousedown、mouseup、keydown、keyup、keypress 等。

需要注意的是,虽然 mouseover 和 mouseout 事件也会发生事件冒泡,但处理起来非常麻烦,所以不推荐在 mouseover 和 mouseout 事件中使用事件委托。

另外,对于不会发生事件冒泡的事件(例如 load、unload、abort、focus、blur 等),则无法使用事件委托。

函数节流和函数防抖之

一、概念解释**

函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段。

大家大概都知道旧款电视机的工作原理,就是一行行得扫描出色彩到屏幕上,然后组成一张张图片。由于肉眼只能分辨出一定频率的变化,当高频率的扫描,人类是感觉不出来的。反而形成一种视觉效果,就是一张图。就像高速旋转的风扇,你看不到扇叶,只看到了一个圆一样。

同理,可以类推到js代码。在一定时间内,代码执行的次数不一定要非常多。达到一定频率就足够了。因为跑得越多,带来的效果也是一样。倒不如,把js代码的执行次数控制在合理的范围。既能节省浏览器CPU资源,又能让页面浏览更加顺畅,不会因为js的执行而发生卡顿。这就是函数节流和函数防抖要做的事。

函数节流是指一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。

二、函数节流

函数节流应用的实际场景,多数在监听页面元素滚动事件的时候会用到。因为滚动事件,是一个高频触发的事件。以下是监听页面元素滚动的示例代码:

  1. // 节流和防抖,主要是优化高频率执行js提高性能的方法。
  2. // 1. 函数节流: 一定时间内js只运行一次。
  3. var canRun = true; // 用一个变量标识函数内的代码是否可以运行
  4. window.onscroll=function(e){
  5. if(canRun){
  6. canRun = false; // 现在代码运行一次
  7. console.log(document.documentElement.scrollTop);
  8. setTimeout(function(){ //用setTimeout设置canRun的值为true,类似控制函数执行的间隔时间。
  9. canRun=true;
  10. },500);
  11. }
  12. }

三、函数防抖

函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。

  1. // 函数防抖
  2. var timer = false;
  3. document.getElementById("debounce").onscroll = function(){
  4. clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
  5. timer = setTimeout(function(){
  6. console.log("函数防抖");
  7. }, 300);
  8. };

函数节流的要点,也是需要一个setTimeout来辅助实现。延迟执行需要跑的代码。

如果方法多次触发,则把上次记录的延迟执行代码用clearTimeout清掉,重新开始。

如果计时完毕,没有方法进来访问触发,则执行代码。

0

Copyright (C) 2021-2026 98社区 All Rights Reserved 蜀ICP备20012692号-3