DOM树 ~ 这个很关键
<div>Hello world!</div> 复制代码
树的根节点是document, 不是window。 上图其实还少了一个节点,虽然作用不大,了解了解。
其树结构如下。
我们常用的document.head和document.body是对开发人员比较重要的两个节点快捷访问。
document.body === document.childNodes[1].childNodes[2] // true document.head === document.childNodes[1].childNodes[0] // true 复制代码
window 和 document的关系
- BOM (Browser Object Model): 浏览器对象模,没有相关标准,一些和网页无关的浏览器功能。如:window、location、navigator、screen、history等对象
- DOM (Document Object Model): 文档对象模型, W3C 的标准, HTML和XML文档的编程接口。
window 属于 BOM, document 是DOM中的核心对象。但是window引用着document,仅此而已。
DOM0级的事件
<button onclick="console.log('被点击了')">按钮</button> 复制代码
优点
- 效率高
没有捕获冒泡等概念。
- 节点上onclick属性被Node.cloneNode克隆,通过JS赋值的onclick不可以
相对而言,DOM2级别的事件并不能复制。
- 移除事件非常简单
直接设置 onclick = null
- 兼容性好
复制的例子:
可以看到按钮2, 通过JS的对象模型赋值的onclick事件不可以被复制。 其实也很好理解,在节点上的属性值是作为字符串被复制的。
<div id="sourceZone"> <div>原始区域:</div> <div id="sourceButtons"> <div> <button class="btn1" onclick="console.log('按钮1被点击了')"> 按钮1(代码) </button> </div> <div> <button class="btn2">按钮2(函数)</button> </div> </div> </div> <div id="copyZone"> <div>复制区域:</div> </div> <script> document.querySelector(".btn2").onclick = onClick2; copyZone.append(sourceButtons.cloneNode(true)); function onClick2() { console.log("按钮2被点击了"); } (function () { function onClick4() { console.log("按钮4被点击了"); } })(); </script> 复制代码
注意事项
- this是当前的节点
- 如果调用函数,会在全局作用域查找
- 唯一性,只能定义一个的事件回调函数
DOM2级的事件
事件机制
三个阶段
- 捕获阶段,从外向内
- 目标阶段,转折点
- 冒泡阶段,从内向外
事件注册
这三个阶段在代码层面怎么体现,答案就是注册事件监听函数addEventListener的参数。
target.addEventListener(type, listener, useCapture); target.addEventListener(type, listener, options); 复制代码
我们先看第一个语法
useCapture: true,捕获阶段传播到目标的时候触发,反之冒泡阶段传到目标的时候触发。默认值flase, 即冒泡时。
当然,你也可以捕获和冒泡时都触发。
看个例子:
输出顺序: 捕获:document => 捕获:btn =>冒泡:btn=>冒泡:document
<button id="btn">按钮</button> <script> btn.addEventListener("click", function (ev) { console.log("冒泡:btn"); }); btn.addEventListener( "click", function (ev) { console.log("捕获:btn"); }, true ); document.addEventListener("click", function(){ console.log("冒泡:document"); }) document.addEventListener("click", function(){ console.log("捕获:document"); }, true) 复制代码
强大的options
target.addEventListener(type, listener, options); 复制代码
options:
- capture: 是否是捕获阶段处理。
- once: 是否是只执行一次。
- passive:
- signal:
once
是否只执行一次。 这个参数非常有用,估计知道的人不多。 如果想让某个事件只执行一次,非你莫属。
最典型的应用就是 视频播放,现代浏览器可能需要用户参与后,视频才可以有声播放。
<button id="btn">按钮</button> <script> btn.addEventListener("click", function () { console.log("按钮被点击了") }, { once: true, }) </script> 复制代码
passive
某些触摸事件(以及其他)的事件监听器在尝试处理滚动时, 可能阻止浏览器的主线程,从而导致滚动处理期间性能可能大大降低。
某些浏览器(特别是Chrome和Firefox)已将文档级节点 Window,Document和Document.body的touchstart (en-US)和touchmove (en-US)事件的passive选项的默认值更改为true。
var elem = document.getElementById('elem'); elem.addEventListener('touchmove', function listener() { /* do something */ }, { passive: true }); 复制代码
signal
这个signal是AbortController的一部分,其主要作用是用来终止请求。
而在此处的作用,效果等同于移除监听器。
有趣的 useCapture
如果这个参数相同并且事件回调函数相同,事件不会被添加。
function onClick(){ console.log("按钮被点击了"); } // capture选项都是false, 只有一个添加成功 btn.addEventListener("click", onClick); btn.addEventListener("click", onClick); // capture选项都是true, 只有一个添加成功 btn.addEventListener("click", onClick, { capture: true, }); btn.addEventListener("click", onClick, { capture: true, once: true, }); 复制代码
阻止默认行为 event.preventDefault
- 阻止默认的行为,比如有href属性的a标签不会跳转,checkbox的选中不会生效等。
- 事件依旧还会继续传播
<div> <a id="linkMK" target="_blank" href="https://www.imooc.com/">跳转到慕课</a> </div> <div> <input id="ckBox" type="checkbox"><label for="ckBox">我统一</label> </div> <script> linkMK.addEventListener("click", function(ev){ ev.preventDefault(); }) ckBox.addEventListener("click", function(ev){ ev.preventDefault(); }) </script> 复制代码
停止事件传播 stopPropagation
阻止捕获和冒泡阶段中当前事件的进一步传播。 有些说法是停止冒泡,个人觉得不太精准哈。
一旦调用,后续的阶段的监听函数均不会响应。
此外还有一个名字很近似的的 stopImmediatePropagation
阻止监听同一事件的其他事件监听器被调用,如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行 stopImmediatePropagation() ,那么剩下的事件监听器都不会被调用。
target 和 currentTarget
- target: 触发事件的元素。 谁触发。
- currentTarget: 事件绑定的元素。 谁添加的事件监听函数。
<button id="btn">按钮</button> <script> btn.addEventListener("click", function (ev) { }); document.addEventListener("click", function(ev){ console.log("ev.target:", ev.target,) console.log("ev.currentTarget:", ev.currentTarget) }) 复制代码
事件委托
利用事件传播的机制,利用外层节点处理事件的思路。
优点:
- 减少内存消耗
- "动态性"更好
因为监听事件是在上层注册,如上增加了节点,我们不需要单独再去添加事件监听。
两点想说:
- 其性能相对于直接添加触发事件的节点,没有得到提升。收益是节约了内存。 也算时间换空间。
- 如果判断事件该不该响应,一般是判断标签,class, 或者属性。
<ul id="ulList"> <li> <div>白菜</div> <a class="btn-buy" data-id="1">购买</a> </li> <li> <div>萝卜</div> <a class="btn-buy" data-id="2">购买</a> </li> </ul> <script> ulList.addEventListener("click", function (ev) { // console.log("ev", ev) // 识别节点 if (ev.target.classList.contains("btn-buy")) { console.log("商品id:", ev.target.dataset.id) } }) </script> 复制代码
DOM3级事件
DOM3 Events在DOM2 Events基础上重新定义了事件,并增加了新的事件类型
- 用户界面事件(UIEvent):涉及与BOM交互的通用浏览器事件。 比如:load、scroll
- 焦点事件(FocusEvent):在元素获得和失去焦点时触发。比如focus, blur
- 鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
- 滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。比如:mousewheel
- 输入事件(InputEvent):向文档中输入文本时触发。
- 键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。比如如:keydown、keypress
- 合成事件(CompositionEvent):在使用某种IME(Input Method Editor,输入法编辑器)输入字符时触发。
注意事项和建议
- DOM0级事件一定程度上可以复制
- DOM2级别事件不可以复制
- 合理利用选项once
- 合理利用选型passive提升性能
- capture选项相同和并且事件回调函数相同,事件不会被添加
- 因为都是继承于EventTarget,任何一个节点都是事件中心
- 合理利用事件代理
写在最后
不忘初衷,有所得,而不为所累,如果你觉得不错,你的一赞一评就是我前行的最大动力。
技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,一起学习。