🌈前言
大家好,我是问啥啥都会,干啥啥不行的XiaoLin
,后端人眼中的前端是什么模样呢?我Lin某人给你答案。
三、DOM
3.1、DOM简介
文档对象模型(Document Object Model,简称DOM),是 W3C 组织推荐的处理 可扩展标记语言(html或xml文档) 的标准 编程接口。它是一种与平台和语言无关的 API,它可以动态操作HTML文档,如 对html标签作增删改查操作。DOM 是一种基于树的 API 文档,在处理html文档的过程中,DOM以对象的形式存储在内存中。因为DOM是基于树结构存储在内存中的,所以DOM又称为文档树模型。
3.2、DOM树
DOM树 又称为文档树模型,把文档映射成树形结构,通过节点对象对其处理,处理的结果可以加入到当前的页面。他有三个组成部分:
- 文档:一个页面就是一个文档,DOM中使用document表示
- 节点:网页中的所有内容,在文档树中都是节点(标签、属性、文本、注释等),使用node表示
- 标签节点:网页中的所有标签,通常称为元素节点,又简称为“元素”,使用element表示
3.4、获取元素
我们获取元素的目的是,比如我们想要操作页面上的某部分(显示/隐藏,动画),需要先获取到该部分对应的元素,再对其进行操作。
3.4.1、根据ID获取
- 语法:
document.getElementById(id)
。 - 参数:id的值,是区分大小写的字符串。
- 返回值:元素对象或null。
- 作用:根据ID获取元素对象。
<body> <div id="name">XiaoLin</div> <script> // 因为我们文档页面从上往下加载,所以先得有标签 所以我们script写到标签的下面 var timer = document.getElementById('name'); console.log(timer); console.log(typeof timer); // console.dir 打印我们返回的元素对象 更好的查看里面的属性和方法 console.dir(timer); </script> </body> 复制代码
3.4.2、根据标签名获取元素
- 语法:
document.getElementsByTagName('标签名')
或者element.getElementsByTagName('标签名')
。 - 参数:标签名。
- 返回值:元素对象集合(伪数组,数组元素是元素对象)。
- 作用:根据标签名获取元素对象。
<body> <ul> <li>知否知否,应是等你好久11</li> <li>知否知否,应是等你好久22</li> <li>知否知否,应是等你好久33</li> <li>知否知否,应是等你好久44</li> <li>知否知否,应是等你好久55</li> </ul> <ul id="nav"> <li>生僻字</li> <li>生僻字</li> <li>生僻字</li> <li>生僻字</li> <li>生僻字</li> </ul> <script> // 1.返回的是 获取过来元素对象的集合 以伪数组的形式存储的 var lis = document.getElementsByTagName('li'); console.log(lis); console.log(lis[0]); // 2. 我们想要依次打印里面的元素对象我们可以采取遍历的方式 for (var i = 0; i < lis.length; i++) { console.log(lis[i]); } // 3. element.getElementsByTagName() 可以得到这个元素里面的某些标签 var nav = document.getElementById('nav'); // 这个获得nav 元素 var navLis = nav.getElementsByTagName('li'); console.log(navLis); </script> </body> 复制代码
对于根据标签名获取到的元素,我们需要注意:
- 因为得到的是一个对象的集合,所以我们想要操作里面的元素就需要先遍历。
- 得到的元素对象是动态的,即:当页面增加了标签,这个集合中也就增加了元素。
3.4.3、通过其他方式获取
H5还有其他的几种方式获取:
- 通过类名返回元素对象集合:
document.getElementByClassName('类名')
。 - 通过指定选择器返回第一个元素对象:
document.querySelector('选择器')
。 - 通过指定选择器返回:
document.querySelectorAll('选择器')
<body> <div class="box">XiaoLin_Java</div> <div class="box">XiaoLin_Python</div> <div id="nav"> <ul> <li>首页</li> <li>产品</li> </ul> </div> <script> // 1. getElementsByClassName 根据类名获得某些元素集合 var boxs = document.getElementsByClassName('box'); console.log(boxs); // 2. querySelector 返回指定选择器的第一个元素对象 切记 里面的选择器需要加符号 .box #nav var firstBox = document.querySelector('.box'); console.log(firstBox); var nav = document.querySelector('#nav'); console.log(nav); var li = document.querySelector('li'); console.log(li); // 3. querySelectorAll()返回指定选择器的所有元素对象集合 var allBox = document.querySelectorAll('.box'); console.log(allBox); var lis = document.querySelectorAll('li'); console.log(lis); </script> </body> 复制代码
3.4.4、获取特殊元素
3.4.4.1、获取Body
我们可以获取到整个Body元素。
document.body // 返回一个body元素对象 复制代码
3.4.4.2、获取Html元素
document.documentElement // 返回Html元素对象 复制代码
3.5、事件基础
3.5.1、事件概述
JavaScript 使我们有能力创建动态页面,而事件是可以被 JavaScript 侦测到的行为。网页中的每个元素都可以产生某些可以触发 JavaScript 的事件,例如,我们可以在用户点击某按钮时产生一个 事件,然后去执行某些操作。
简单来说就是点击了某个按钮,发生了某件事情。
3.5.2、事件三要素
- 事件源(谁):触发事件的元素。
- 事件类型(什么事件): 例如 click 点击事件。
- 事件处理程序(做了啥):事件触发后要执行的代码(函数形式),事件处理函数。
<body> <button id="btn">XiaoLin</button> <script> // 点击一个按钮,弹出对话框 // 1. 事件是有三部分组成 事件源 事件类型 事件处理程序 我们也称为事件三要素 //(1) 事件源 事件被触发的对象 谁 按钮 var btn = document.getElementById('btn'); //(2) 事件类型 如何触发 什么事件 比如鼠标点击(onclick) 还是鼠标经过 还是键盘按下 //(3) 事件处理程序 通过一个函数赋值的方式 完成 btn.onclick = function() { alert('点秋香'); } </script> </body> 复制代码
3.5.3、执行事件的步骤
- 获取事件源。
- 注册事件(绑定事件)。
- 才有函数赋值形式来添加事件处理程序。
<body> <div>123</div> <script> // 执行事件步骤 // 点击div 控制台输出 我被选中了 // 1. 获取事件源 var div = document.querySelector('div'); // 2.绑定事件 注册事件 // div.onclick // 3.添加事件处理程序 div.onclick = function() { console.log('我是XiaoLin,我被选中了'); } </script> </body> 复制代码
3.5.4、常见的鼠标事件
鼠标事件 | 触发条件 |
onclick | 鼠标点击左键触发 |
onmouseover | 鼠标经过触发 |
onmouseout | 鼠标离开触发 |
onfocus | 获得鼠标焦点时触发 |
onblur | 失去鼠标焦点触发 |
onmousemove | 鼠标移动触发 |
onmouseup | 鼠标弹起触发 |
onmousedown | 鼠标按下触发 |
3.5.5、事件的书写位置
3.5.5.1、行内式
所谓的行内是指的就是直接把事件写在html代码中,不建议使用这种方式,虽然写起来简单,但是这种方式仅仅适合写一些简单的事件,比如说alert
之类的,如果写一些复杂的就看起来很冗余,而且后期维护起来及其困难。
<button onclick="alert('hello,我是XiaoLin')">行内式</button> 复制代码
3.5.5.2、内嵌式
内嵌式指的是在html最下面写JavaScript代码,这样不仅仅可以写一些复杂的事件,同时还便于维护,但是还是不推荐这种方式,因为JavaScript代码和html代码没有完全解耦分离
<button onclick="fun()">内嵌式</button> <script type="text/javascript"> function fun(){ alert("hello,我是XiaoLin"); } 复制代码
3.5.5.3、外部JavaScript文件
这种是目前最主流也是推崇的方式,他是将所有的JavaScript都抽离出去,放在了一个单独的文件里面,然后在页面里面引用即可。
3.6、操作元素
JavaScript的DOM操作可以改变网页内容、结构和样式,我们可以利用DOM操作元素来改变元素里面的内容、属性等。
3.6.1、改变元素内容
方法 | 描述 |
element.innerText | 从起始位置到终止位置的内容,但他去除html标签,同时空格和换行也会被去掉。 |
element.innerHTML | 从起始位置到终止位置的内容,包括html标签,同时空格和换行也会被去掉。 |
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <p id="demo">my first demo</p> </body> <script> document.getElementById("demo").innerHTML="<b>hello XiaoLin_Java</b>" </script> </html> 复制代码
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <p id="demo">my first demo</p> </body> <script> document.getElementById("demo").innerText="<h1>My First XiaoLin_Java</h1>"; </script> </html> 复制代码
通过运行的结果,我们可以很好的看出这两者的区别:
- innerText会去除空格和换行,而innerHTML会保留空格和换行。
- innerText不会识别html,而innerHTML会识别。
3.6.2、获取属性的值
我们可以使用JavaScript来获取属性的值,他的基本语法为元素对象.属性名
。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <img src="imges/ldh.jpg" alt="" id="imgBtn" title="刘德华" > </body> </html> <script> // 获取元素 var img = document.getElementById('imgBtn'); // 2. 注册事件 处理程序 img.onclick = function() { // 获取元素的值 alert(img.title) } </script> 复制代码
3.6.3、设置属性的值
设置属性的值的语法为:元素对象.属性名 = 值
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <img src="imges/ldh.jpg" alt="" id="imgBtn" title="刘德华" > </body> </html> <script> // 获取元素 var img = document.getElementById('imgBtn'); // 2. 注册事件 处理程序 img.onclick = function() { // 给元素设置值 alert("设置之前:"+img.title) img.title = 'XiaoLin'; alert("设置之后:"+img.title) } </script> 复制代码
3.6.4、布尔类型的值设值
<body> <button>按钮</button> <input type="text" value="输入内容"> <script> // 1. 获取元素 var btn = document.querySelector('button'); var input = document.querySelector('input'); // 2. 注册事件 处理程序 btn.onclick = function() { // 表单里面的值 文字内容是通过 value 来修改的 input.value = '被点击了'; // 如果想要某个表单被禁用 不能再点击 disabled 我们想要这个按钮 button禁用 // btn.disabled = true; this.disabled = true; // this 指向的是事件函数的调用者 btn } </script> </body> 复制代码
3.6.5、排他操作
如果有同一组元素,我们想要某一个元素实现某种样式, 需要用到循环的排他思想算法:
- 所有元素全部清除样式(干掉其他人)
- 给当前元素设置样式 (留下我自己)
- 注意顺序不能颠倒,首先干掉其他人,再设置自己,如果先设值自己再干掉其他人会导致所有都没有样式了。
<button>按钮1</button> <button>按钮2</button> <button>按钮3</button> <button>按钮4</button> <button>按钮5</button> <script> // 1. 获取所有按钮元素 var btns = document.getElementsByTagName('button'); // btns得到的是伪数组 里面的每一个元素 btns[i] for (var i = 0; i < btns.length; i++) { btns[i].onclick = function() { // (1) 我们先把所有的按钮背景颜色去掉 干掉所有人 for (var i = 0; i < btns.length; i++) { btns[i].style.backgroundColor = ''; } // (2) 然后才让当前的元素背景颜色为pink 留下我自己 this.style.backgroundColor = 'pink'; } } </script> 复制代码
3.6.6、一键换肤
<body> <ul class="baidu"> <li><img src="images/1.jpg"></li> <li><img src="images/2.jpg"></li> <li><img src="images/3.jpg"></li> <li><img src="images/4.jpg"></li> </ul> <script> // 1. 获取元素 var imgs = document.querySelector('.baidu').querySelectorAll('img'); // console.log(imgs); // 2. 循环注册事件 for (var i = 0; i < imgs.length; i++) { imgs[i].onclick = function() { // this.src 就是我们点击图片的路径 images/2.jpg // console.log(this.src); // 把这个路径 this.src 给body 就可以了 document.body.style.backgroundImage = 'url(' + this.src + ')'; } } </script> </body> 复制代码
3.6.7、表格隔行变色
<script> // 1.获取元素 获取的是 tbody 里面所有的行 var trs = document.querySelector('tbody').querySelectorAll('tr'); // 2. 利用循环绑定注册事件 for (var i = 0; i < trs.length; i++) { // 3. 鼠标经过事件 onmouseover trs[i].onmouseover = function() { // console.log(11); this.className = 'bg'; } // 4. 鼠标离开事件 onmouseout trs[i].onmouseout = function() { this.className = ''; } } </script> 复制代码
3.6.8、全选
<script> // 1. 全选和取消全选做法: 让下面所有复选框的checked属性(选中状态) 跟随 全选按钮即可 // 获取元素 var j_cbAll = document.getElementById('j_cbAll'); var j_tbs = document.getElementById('j_tb').getElementsByTagName('input'); // 全选按钮注册事件 j_cbAll.onclick = function() { // this.checked 当前复选框的选中状态 console.log(this.checked); for (var i = 0; i < j_tbs.length; i++) { j_tbs[i].checked = this.checked; } } // 给所有的子复选框注册单击事件 for (var i = 0; i < j_tbs.length; i++) { j_tbs[i].onclick = function() { // flag 控制全选按钮是否选中 var flag = true; // 每次点击下面的复选框都要循环检查者4个小按钮是否全被选中 for (var i = 0; i < j_tbs.length; i++) { if (!j_tbs[i].checked) { flag = false; break; } } // 设置全选按钮的状态 j_cbAll.checked = flag; } } </script> 复制代码
3.7、自定义属性操作
3.7.1、设置属性值
我们有两种方式来设置属性值:
- element.属性 = '值';
- element.setAttribute ('属性','值');
他们俩的区别是:
- 方式一为内置属性设置值。
- 方式二主要是给自定义属性设置值。
3.7.2、移除属性
移除属性的语法格式为:
element.removeAttribute('属性'); div.removeAttribute('index'); 复制代码
3.8、节点操作
3.8.1、节点概述
网页中的所有内容都是节点(标签、属性、文本、注释等),在DOM 中,节点使用 node 来表示。HTML DOM 树中的所有节点均可通过 JavaScript 进行访问,所有 HTML 元素(节点)均可被修改,也可以创建或删除。在日常开发中,我们最常使用的是元素节点。
一般一个节点至少拥有nodeType(节点类型)、nodeName(节点名称)和nodeValue(节点值)这三个基本属性。
- 元素节点的节点类型为1
- 属性节点的节点类型为2
- 文本节点的节点类型为3
3.8.2、节点层级
利用 DOM 树可以把节点划分为不同的层级关系,常见的是父子兄层级关系。
3.8.3、获取父级节点
我们可以通过parentNode来获取某节点的父节点,他返回的是最近的一父节点,如果没有就返回null。
<div class="demo"> <div class="box"> <span class="erweima">×</span> </div> </div> <script> // 1. 父节点 parentNode var erweima = document.querySelector('.erweima'); // 得到的是离元素最近的父级节点(亲爸爸) 如果找不到父节点就返回为 null console.log(erweima.parentNode); </script> 复制代码
3.8.4、子节点
获取子节点的语法为:
parentNode.childNodes 复制代码
这种写法是标准写法,返回包含指定节点的所有子节点,包括元素节点、文本节点,如果只想获得里面的元素节点,就需要专门处理,所以我们一般是不会用的,我们一般使用下面这个:
parentNode.childrent 复制代码
parentNode.childrent是一个只读属性,返回所有的子元素节点,他只返回子元素节点,其他节点不返回。
<ul> <li>我是XiaoLin</li> <li>我是XiaoLin</li> <li>我是XiaoLin</li> <li>我是XiaoLin</li> </ul> <script> // DOM 提供的方法(API)获取 var ul = document.querySelector('ul'); var lis = ul.querySelectorAll('li'); // 1. 子节点 childNodes 所有的子节点 包含 元素节点 文本节点等等 console.log(ul.childNodes); console.log(ul.childNodes[0].nodeType); console.log(ul.childNodes[1].nodeType); // 2. children 获取所有的子元素节点 也是我们实际开发常用的 console.log(ul.children); </script> 复制代码
还有几个获取常见节点的方法。
获取第一个节点
parentNode.firestChild 复制代码
他返回的的是第一个子节点的所有节点,如果找不到就返回null。
获取第一子元素节点
parentNode.firstElementChild 复制代码
他返回的是第一个子元素节点,找不到就返回NULL。这个方法有兼容性问题,IE9以上才支持。
获取最后一个节点
parentNode.lastChild 复制代码
获取最后一个子元素节点
parentNode.lastElementChild 复制代码
他返回的是最后一个子元素节点,找不到就返回null。这个方法有兼容性问题,IE9以上才支持。
总结
在实际开发中,firstChild和lastChild会包含其他节点,对于我们的操作是十分不方便的,而firstElementChild 和 lastElementChild又有兼容性问题,如果我们想要获取第一子元素节点和最后一个子元素节点,我们可以采用的办法是:
- 获取第一个子元素节点:
parentNode.childrent[0]
- 获取最后一个子元素节点:
parentNode.children[parentNode.children.length-1]
<ol> <li>我是li1</li> <li>我是li2</li> <li>我是li3</li> <li>我是li4</li> <li>我是li5</li> </ol> <script> var ol = document.querySelector('ol'); // 1. firstChild 第一个子节点 不管是文本节点还是元素节点 console.log(ol.firstChild); console.log(ol.lastChild); // 2. firstElementChild 返回第一个子元素节点 ie9才支持 console.log(ol.firstElementChild); console.log(ol.lastElementChild); // 3. 实际开发的写法 既没有兼容性问题又返回第一个子元素 console.log(ol.children[0]); console.log(ol.children[ol.children.length - 1]); </script> 复制代码
3.8.5、创建节点
如果元素原先不存在,我们可以使用document.createElement('标签名称')
,来创建指定的标签的HTML元素,这些元素原先是不存在的,是根据我们的需求动态生成的,所以称为动态创建节点。
3.8.6、添加节点
我们有两种添加节点的方式:
- 尾插法
- 头插法
3.8.6.1、头插法
node.insertBefore(chile,指定元素)
方法将以节点添加到父节点指定子节点的前面,类似于CSS里面的before伪元素。
<html> <body> <ul> <li>123</li> </ul> </body> </html> <script> // 添加节点 node.insertBefore(child, 指定元素); var lili = document.createElement('li'); ul.insertBefore(lili, ul.children[0]); // 我们想要页面添加一个新的元素 : 1. 创建元素 2. 添加元素 </script> 复制代码
3.8.6.2、尾插法
node.appendChind(child)
方法将一个节点添加到指定的父节点的子节点列表的末尾,类似于CSS里面的after伪元素。
<html> <body> <ul> <li>123</li> </ul> </body> </html> <script> // 创建节点元素节点 var li = document.createElement('li'); // 添加节点 node.appendChild(child) node 父级 child 是子级 后面追加元素 var ul = document.querySelector('ul'); ul.appendChild(li); </script> 复制代码
3.8.7、删除节点
node.removeChild()
方法从 node节点中删除一个子节点,返回删除的节点。
<html> <body> <button>删除</button> <ul> <li>熊大</li> <li>熊二</li> <li>光头强</li> </ul> <script> // 1.获取元素 var ul = document.querySelector('ul'); var btn = document.querySelector('button'); // 2. 删除元素 node.removeChild(child) // ul.removeChild(ul.children[0]); // 3. 点击按钮依次删除里面的孩子 btn.onclick = function() { if (ul.children.length == 0) { this.disabled = true; } else { ul.removeChild(ul.children[0]); } } </script> </body> </html> 复制代码
3.8.8、复制节点
node.cloneNode()
方法返回调用该方法的节点的一个副本,成为复制节点。他里面可以传参数:
- 如果参数为空或者是false的话,就只是浅拷贝,即只复制节点本身,不会复制里面的子节点。
- 如果参数为true的话,就是深拷贝,会复制该节点以及该节点的所有子节点。
- 注意和Java中的深/浅拷贝区分。
<html> <body> <ul> <li>1111</li> <li>2</li> <li>3</li> </ul> <script> var ul = document.querySelector('ul'); // 1. node.cloneNode(); 括号为空或者里面是false 浅拷贝 只复制标签不复制里面的内容 // 2. node.cloneNode(true); 括号为true 深拷贝 复制标签复制里面的内容 var lili = ul.children[0].cloneNode(true); ul.appendChild(lili); </script> </body> </html> 复制代码
3.9、元素操作
3.9.1、创建元素
我们创建元素主要有三种方式:
- document.write
- inner.innerHTML
- document.createElement
document.write
document.write
是直接将内容写入页面的内容流。
document.write('<div>123</div>'); 复制代码
innerHTML
innerHTML是将内容写入某个DOM节点,inner.innerHTML += '<a href="#">百度</a>'
如果我们需要创建多个元素可以使用innerHTML
效率更高,但是这个时候不要去拼接字符串,可以采用数组的结构拼接。
createElement
createElement
可以直接创建一个元素,虽然他在创建多个元素的效率稍微低一点点,但是他的结构更清晰明了。
var create = document.querySelector('.create'); for (var i = 0; i <= 100; i++) { var a = document.createElement('a'); create.appendChild(a); } 复制代码
3.9.2、效率比较
效率低:innerHTML字符串拼接方式
innerHTML
的方式是效率最低的,而且他需要去拼接字符串,较为繁琐。
<script> function fn() { var d1 = +new Date(); var str = ''; for (var i = 0; i < 1000; i++) { document.body.innerHTML += '<div style="width:100px; height:2px; border:1px solid blue;"></div>'; } var d2 = +new Date(); console.log(d2 - d1); } fn(); </script> 复制代码
效率一般:createElement
<script> function fn() { var d1 = +new Date(); for (var i = 0; i < 1000; i++) { var div = document.createElement('div'); div.style.width = '100px'; div.style.height = '2px'; div.style.border = '1px solid red'; document.body.appendChild(div); } var d2 = +new Date(); console.log(d2 - d1); } fn(); </script> 复制代码
效率高:innerHTML数组方式
<script> function fn() { var d1 = +new Date(); var array = []; for (var i = 0; i < 1000; i++) { array.push('<div style="width:100px; height:2px; border:1px solid blue;"></div>'); } document.body.innerHTML = array.join(''); var d2 = +new Date(); console.log(d2 - d1); } fn(); </script> 复制代码
3.10、事件高级
3.10.1、addEventListener()
addEventListener()
方法将指定的监听器注册到目标对象上,一般是目标对象直接调用该方法,当该对象触发指定的事件时,就会执行事件处理函数。该方法有三个参数:
- type:事件的类型,他是一个字符串,常见的有click、mouseover。
- listener:事件处理函数,事件发生时,会调用该监听函数。
- useCapture:是一个可选参数,类型为布尔值,默认为false。
<html> <body> <button>addEventListener方法测试</button> </body> <script> var btns = document.querySelectorAll('button'); btns[0].addEventListener('click', function() { alert(22); }) </script> </html> 复制代码
3.10.2、attacheEvent()
attachEvent()
方法将指定的监听器注册到目标对象上,由目标对象调用该方法,当该对象触发指定的事件时,指定的回调函数就会被执行。该方法有两个参数:
- eventNameWithOn:事件类型的类型,他是一个字符串,常见的有onclick、onmouseover。
- callback:事件处理函数,当目标事件触发时,回调函数被调用。
<html> <body> <button>attacheEvent方法测试</button> </body> <script> var btns = document.querySelectorAll('button'); btns[0].attachEvent('onclick', function() { alert(11); }) </script> </html> 复制代码
3.10.3、DOM事件流
事件流描述的是从页面接受事件的顺序。当事件发生时,会在元素节点之间按照特定的顺序传播,这个传播过程就叫做DOM事件流。
那么事件流的顺序是怎么样的呢?互联网两大巨头公司有了不同的纷争:
- 事件冒泡:由IE提出,事件开始时是由最具体的元素接受,然后逐渐向上传播到DOM最顶层节点的过程。
- 事件捕获:由网景公司提出,他表示的是由DOM最顶层节点开始,然后逐渐向下传播到最具体的元素接受的过程。
我们可以发现,这两家公司好像是天生的仇人一样,他们连提出的标准的方向都不同。IE 提出从目标元素开始,然后一层一层向外接收事件并响应,也就是冒泡型事件流。而Netscape(网景公司)提出从最外层开始,然后一层一层向内接收事件并响应,也就是捕获型事件流。他们俩谁也不服谁,为了一统江湖,W3C采用了这种的方式,制定了一个新的标准:先捕获再冒泡。当事件发生的时候,会经历3个阶段:
- 捕获阶段
- 当前目标阶段
- 冒泡阶段
他有点类似于我们往水里抛一个石头:
- 首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程。
- 之后会产生泡泡,会在最低点( 最具体元素),有点类似于处于当前目标阶段。
- 最后会漂浮到水面上,这个过程相当于事件冒泡。
他有几个注意的点:
- JavaScript代码只能执行捕获或者冒泡其中的一个阶段。
- onclick和attachEven只能到冒泡阶段。
- addEventListener(type、listener、useCapture)中的第三个参数如果是true,则表示在事件捕获阶段调用事件处理程序,如果使用默认的false,表示事件在冒泡阶段调用事件处理函数。
- 实际的开发中我们很少使用事件捕获,更多的是关注事件冒泡。
- 有一些事件是没有冒泡的,比如onblur、onfocus、onmouseenter、onmouseleave。
3.10.4、事件对象
3.10.4.1、概述
事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象。这一系列信息的数据集合包括:
- 谁绑定了这个事件。
- 鼠标触发事件的话,还可以拿到鼠标的相关信息,比如鼠标的位置。
- 键盘触发事件话,会得到键盘相关的信息,比如我按了哪个按键。
3.10.4.2、属性和方法
事件对象 | |
e.target | 返回触发事件的对象(标准) |
e.srcElement | 返回触发事件的对象(非标准) |
e.type | 返回事件的类型,例如click、mouseover |
e.cancelBubble | 阻止冒泡(非标准) |
e.stopPropagation() | 阻止冒泡(标准) |
e.returnValue | 阻止默认事件(非标准) |
e.preventDefault | 阻止默认事件(标准) |
3.10.5、阻止默认行为
html中一些标签有默认行为,例如a标签被单击后,默认会进行页面跳转。我们如果不想让a标签跳转的话可以阻止他的默认行为。
<html> <body> <a href="http://www.baidu.com">百度</a> </body> <script> // 2. 阻止默认行为 让链接不跳转 var a = document.querySelector('a'); a.addEventListener('click', function(e) { e.preventDefault(); // dom 标准写法 }); // 3. 传统的注册方式 a.onclick = function(e) { // 普通浏览器 e.preventDefault(); 方法 e.preventDefault(); // 低版本浏览器 ie678 returnValue 属性 e.returnValue = false; // 我们可以利用return false 也能阻止默认行为 没有兼容性问题 return false; } </script> </html> 复制代码
3.10.6、阻止事件冒泡
事件冒泡可能会带来好处也有可能会带来坏处,如果我们想阻止事件冒泡的话有两种写法:
- 标准写法:e.stopPropagation()
- 非标准写法:e.cancelBubble = true;
3.10.7、事件委托
事件委托也成为了事件代理,把事情委托给别人,代为处理。简单来说就是不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行。
举个简单的例子,有10个班,1000名学生,每个学生要领一个swtich手柄,如果一个个由配送员来送的话,不仅仅花费的时间很长,配送员会累死,学生还要大排场龙,如果使用了代理的话,配送员只需要将每一个switch分给每个班的班主任,同学找班主任领取即可。
我们来看下面这个需求,我们点击每一个li的时候都会有一个弹窗,按照普通的思想,我们需要给每一个li单独注册事件,是非常麻烦的,不仅麻烦,而且页面加载的时候,由于访问DOM的次数变多了,会延长整个页面的响应时间。
<html> <body> <ul> <li>switch1</li> <li>switch2</li> <li>switch3</li> <li>switch4</li> </body> </html> 复制代码
这个时候我们就需要采用事件委托,我们将这个事件委托给他的父元素。利用事件泡沫,当子元素的事件触发时,会冒泡到父元素,然后再去控制响应的子元素。
采用了事件委托的优点很明显:
- 只操作了一次DOM,显著提升了性能。
- 如果我们新加或者动态创建一个子元素,新加的子元素也拥有这个事件。
<html> <body> <ul> <li>switch1</li> <li>switch2</li> <li>switch3</li> <li>switch4</li> </body> <script> // 事件委托的核心原理:给父节点添加侦听器, 利用事件冒泡影响每一个子节点 var ul = document.querySelector('ul'); ul.addEventListener('click', function(e) { // e.target 这个可以得到我们点击的对象 e.target.style.backgroundColor = 'pink'; }) </script> </html> 复制代码
3.10.8、常见鼠标事件
鼠标事件 | 触发条件 |
onclick | 鼠标点击左侧触发 |
onmouseover | 鼠标经过触发 |
onmouseout | 鼠标离开触发 |
onfoucs | 获得鼠标焦点时触发 |
onblur | 失去鼠标焦点时触发 |
onmousemove | 鼠标移动触发 |
onmouseup | 鼠标弹起触发 |
onmousedown | 鼠标按下触发 |
我们来做一个禁止选中文字和禁止右键菜单的联系。
<body> 我是一段不愿意分享的文字 <script> // 1. contextmenu 我们可以禁用右键菜单 document.addEventListener('contextmenu', function(e) { e.preventDefault(); }) // 2. 禁止选中文字 selectstart document.addEventListener('selectstart', function(e) { e.preventDefault(); }) </script> </body> 复制代码
3.10.9、鼠标事件对象
event事件对象是事件相关的一系列信息的集合,我们常用的是鼠标事件对象
MouseEvent和键盘事件对象KeyboardEvent。
鼠标事件对象 | 作用 |
e.clientX | 返回鼠标相对于浏览器可视区的X坐标 |
e.clientY | 返回鼠标相对于浏览器可视区的Y坐标 |
e.pageX | 返回鼠标相对于文档页面的X坐标 |
e.pageY | 返回鼠标相对于文档页面的Y坐标 |
e.screenX | 返回鼠标相对于电脑屏幕的X坐标 |
e.screenY | 返回鼠标相对于电脑屏幕的Y坐标 |
<script> // 鼠标事件对象 MouseEvent document.addEventListener('click', function(e) { // 1. client 鼠标在可视区的x和y坐标 console.log(e.clientX); console.log(e.clientY); console.log('---------------------'); // 2. page 鼠标在页面文档的x和y坐标 console.log(e.pageX); console.log(e.pageY); console.log('---------------------'); // 3. screen 鼠标在电脑屏幕的x和y坐标 console.log(e.screenX); console.log(e.screenY); }) </script> 复制代码
我们来做一个练手的案例:跟随鼠标的猫。大致的思路是这样:
- 鼠标不断地移动,我们可以使用鼠标移动事件:mousemove。
- 鼠标在页面中移动的时候,给document注册事件。
- 图片要移动距离。而且不能占位置,所以我们使用绝对定位。
- 每次鼠标移动,我们都获得一个新的鼠标坐标,然后将这个x、y值作为图片的top和left偏移就可以移动图片了。
<html> <body> <img src="images/cat.gif" alt=""> <script> var pic = document.querySelector('img'); document.addEventListener('mousemove', function(e) { // 1. mousemove只要我们鼠标移动1px 就会触发这个事件 // 2.核心原理: 每次鼠标移动,我们都会获得最新的鼠标坐标, // 把这个x和y坐标做为图片的top和left 值就可以移动图片 var x = e.pageX; var y = e.pageY; console.log('x坐标是' + x, 'y坐标是' + y); //3 . 千万不要忘记给left 和top 添加px 单位 pic.style.left = x - 50 + 'px'; pic.style.top = y - 40 + 'px'; }); </script> </body> </html> 复制代码
3.10.11、常见的键盘事件
3.10.11.1、键盘事件
键盘事件 | 触发条件 |
onkeyup | 某个键盘按键被松开时触发 |
onkeydown | 某个键盘按键被按下时触发 |
onkeypress | 某个键盘被按下时触发,但是他不能识别功能键,如ctrl、shift等 |
需要注意的是如果使用addEventListener
方法时,前面不需要加on。
<script> // 常用的键盘事件 //1. keyup 按键弹起的时候触发 document.addEventListener('keyup', function() { console.log('我弹起了'); }) //3. keypress 按键按下的时候触发 不能识别功能键 比如 ctrl shift 左右箭头啊 document.addEventListener('keypress', function() { console.log('我按下了press'); }) //2. keydown 按键按下的时候触发 能识别功能键 比如 ctrl shift 左右箭头啊 document.addEventListener('keydown', function() { console.log('我按下了down'); }) </script> 复制代码
3.10.11.1、键盘事件对象
键盘事件对象属性 | 说明 |
keyCode | 返回该对象的ASCII码 |
需要注意的是:
- onkeydown和onkeyup不区分字母大小写,但是onkeypress区分字母大小写。
- 在实际开发中,用的更多的还是keydown和keyup,因为他可以识别键盘上所有的按键,包含了功能键。
- keypress虽然不能识别功能键,但是keyCode属性可以区分大小写,返回不能的ASCII码。
比如我们写一个小demo,判断用户按下的是哪一个按键?
<script> // 键盘事件对象中的keyCode属性可以得到相应键的ASCII码值 document.addEventListener('keyup', function(e) { console.log('up:' + e.keyCode); // 我们可以利用keycode返回的ASCII码值来判断用户按下了那个键 if (e.keyCode === 65) { alert('您按下的a键'); } else { alert('您没有按下a键') } }) document.addEventListener('keypress', function(e) { // console.log(e); console.log('press:' + e.keyCode); }) </script>