重学精进 DOM0 、 DOM2、DOM3 级事件
DOM0 级事件
优点
- 兼容性好
- 移除事件简单
- 节点上 onclick 属性被 Node.cloneNode 克隆,通过 JS 赋值的 onclick 不可以
注意
- 绑定的事件处理函数,函数里面的 this 指向绑定的节点
- 相同事件只能定义一个回调函数
- html 上绑定的函数,会在全局作用域查找
<body>
<button onclick="console.log('按钮1被点击了')">按钮1(html代码)</button>
<button onclick="onClick2()">按钮2(函数)</button>
<button onclick="console.log(this.className, this);" class="button3">
按钮3(this)
</button>
<button onclick="onClick4()" class="button3">按钮4(全局作用域)</button>
<script>
function onClick2() {
console.log("按钮2被点击了");
}
(function () {
function onClick4() {
console.log("按钮4被点击了");
}
})();
</script>
</body>
复制绑定事件
<body>
<div>原始区域:</div>
<div id="sourceButtons">
<button class="btn1" onclick="console.log('按钮1被点击了')">按钮1(html代码)</button>
<button class="btn2">按钮2(函数)</button>
</div>
<!-- 复制了绑定在按钮1的事件,但不会复制按钮2 -->
<div id="copyZone">
<div>复制区域:</div>
</div>
<script>
document.querySelector(".btn2").onclick = onClick2;
copyZone.append(sourceButtons.cloneNode(true));
function onClick2() {
console.log("按钮2被点击了");
}
</script>
</body>
DOM2 级的事件
注册
// useCapture:true,捕获阶段传播到目标的时候触发
// 反之冒泡阶段传到目标的时候触发。默认值 false,即冒泡时
target.addEventListener(type, listener, useCapture);
target.addEventListener(type, listener, options);
<body>
<button id="btn">按钮</button>
<script>
window.addEventListener(
"click",
() => {
console.log("捕获:window");
},
true
);
document.addEventListener(
"click",
() => {
console.log("捕获:document");
},
true
);
btn.addEventListener(
"click",
e => {
console.log("捕获:btn");
},
true
);
btn.addEventListener("click", e => {
console.log("冒泡:btn");
});
document.addEventListener("click", () => {
console.log("冒泡:document");
});
window.addEventListener("click", () => {
console.log("冒泡:window");
});
</script>
</body>
打印结果:
once
- 是否只响应一次
- 最典型的应用可能就是现代浏览器视频播放,需要用户点击一次后播放
<body>
<video id="video" controls width="250" autoplay>
<source
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm"
type="video/webm"
/>
<source
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
type="video/mp4"
/>
Sorry, your browser doesn't support embedded videos.
</video>
<script>
document.addEventListener(
"click",
() => {
console.log("播放视频");
video.play();
},
{
once: true
}
);
</script>
</body>
passive
- 设置为 true 时,事件处理不会调用
preventDefault()
,可提高性能
ele.addEventListener("touchmove", ()=>{}, {
passive: true,
})
signal
- AbortSignal,该 AbortSignal 的 abort() 方法被调用时,监听器会被移除
- AbortSignal 也被用于取消 fetch 请求
<body>
<button id="btn">按钮</button>
<script>
const controller = new AbortController();
const signal = controller.signal;
btn.addEventListener(
"click",
() => {
console.log("按钮被点击了");
controller.abort();
},
{ signal }
);
</script>
</body>
e.preventDefault
- 阻止默认的行为,比如有 href 属性的 a 标签不会跳转,checkbox 的选中不会生效等
- 事件依旧还会继续传递
<body>
<a id="aLink" target="_blank" href="https://www.imooc.com/">跳转到xxx</a>
<input id="cBox" type="checkbox" /><label for="ckBox">复选框</label>
<script>
aLink.addEventListener("click", e => {
e.preventDefault();
});
cBox.addEventListener("click", e => {
e.preventDefault();
});
</script>
</body>
e.stopPropagation
- 阻止捕获和冒泡阶段中当前事件的进一步传播
<body>
<button id="btn">按钮</button>
<script>
btn.addEventListener("click", () => {
console.log("冒泡:btn");
});
document.addEventListener("click", () => {
console.log("冒泡:document");
});
btn.addEventListener(
"click",
() => {
console.log("捕获:btn");
},
true
);
document.addEventListener(
"click",
e => {
console.log("捕获:document");
e.stopPropagation();
},
true
);
</script>
</body>
打印结果:
捕获:document
e.stoplmmediatePropagation
- 阻止监听同一事件的其他事件监听器被调用
<body>
<button id="btn">按钮</button>
<script>
btn.addEventListener("click", () => {
console.log("冒泡:btn");
});
btn.addEventListener(
"click",
() => {
console.log("捕获:btn 1");
},
true
);
btn.addEventListener(
"click",
e => {
// 阻止 click 事件冒泡,并且阻止 btn 元素上绑定的其他 click 事件的事件监听函数的执行
e.stopImmediatePropagation();
console.log("捕获:btn 2");
},
true
);
btn.addEventListener(
"click",
() => {
console.log("捕获:btn 3");
},
true
);
document.addEventListener("click", () => {
console.log("冒泡:document");
});
document.addEventListener(
"click",
() => {
console.log("捕获:document");
},
true
);
</script>
</body>
执行结果如下:
捕获:document
捕获:btn 1
捕获:btn 2
useCapture
- 如果这个参数相同并且事件回调函数相同,事件不会被添加
<body>
<button id="btn">按钮</button>
<script>
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
});
</script>
</body>
target 和 currentTarget
- target: 触发事件的元素,谁触发就是谁
- currentTarget: 事件绑定的元素,谁添加的事件函数就是谁
<body>
<button id="btn">按钮</button>
<script>
// 点击btn的时候
document.addEventListener("click", e => {
console.log(e.target); // button元素
console.log(e.currentTarget); // document元素
});
</script>
</body>
事件委托
- 基于事件冒泡的机制,利用外层节点处理事件
- 优点:不能为每个元素单独绑定事件函数、减少内存消耗
<body>
<ul id="ulList">
<li>
<a class="btn-buy" data-id="1">白菜</a>
</li>
<li>
<a class="btn-buy" data-id="2">萝卜</a>
</li>
</ul>
<script>
// 绑定
ulList.addEventListener("click", e => {
// 识别节点
if (e.target.classList.contains("btn-buy")) {
console.log("商品id:", e.target.dataset.id);
}
});
</script>
</body>
DOM3 级事件
- DOM3 Events 在 DOM2 Events 基础上重新定义了事件,并增加了新的事件类型
注意事项和建议
- DOMO 级事件一定程度上可以复制,DOM2级别事件不可以复制
- 合理利用选项 once 和 passive 和 事件代理 提升性能
- capture 选项相同和并且事件回调函数相同,事件不会被添加
- 因为都是继承于
EventTarget
,任何一个节点都是事件中心
转载自:https://juejin.cn/post/7205199441287938085