请简述 DOM 事件模型?
什么是DOM事件模型?
当一个HTML元素触发一个事件时,该事件会在元素结点与根结点之间的路径传播。传播按顺序分为三个阶段:捕获阶段、目标阶段、冒泡阶段,这个传播过程就是 DOM 事件流。
当某一事件触发时,先执行捕获再执行冒泡。
捕获阶段:当子元素的事件类型触发时,所有父元素上绑定的同类型事件均会从外到内层层触发。
**目标阶段:**在要触发事件的元素上触犯
冒泡阶段:当子元素的事件类型触发时,所有父元素上绑定同类型事件均会从内到外层层触发。
先经历从上到下的捕获阶段,再经历从下到上的冒泡阶段。
addEventListener(’click’,fn,true/false) //第三个参数可以选择阶段。
可以使用 event.stopPropagation() //来阻止捕获或冒泡。
什么是事件委托?
事件委托也称之为事件代理(Event Delegation)。是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
事件委托的原理:
给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素。
事件委托的优点:
- 可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒
- 动态新创建的子元素,也拥有事件。
简单的手写事件委托
ul.addEventListener('click', function(e){
if(e.target.tagName.toLowerCase() === 'li'){
fn()// 执行某个函数
}
})
bug 在于,如果用户点击的是 li 里面的 span,就没法触发 fn,这显然不对。 好处:
- 节省监听器
- 实现动态监听
**坏处:**调试比较复杂,不容易确定监听者。
优化版本事件委托手写代码
思路是点击 span 后,递归遍历 span 的祖先元素看其中有没有 ul 里面的 li。
function delegate(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
}
delete(ul, 'click', 'li', f1)
手写可拖曳 div
实例代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="xxx"></div>
<script>
var dragging = false
var position = null
xxx.addEventListener('mousedown',function(e){
dragging = true
position = [e.clientX, e.clientY]
})
document.addEventListener('mousemove', function(e){
if(dragging === false){return}
console.log('hi')
const x = e.clientX
const y = e.clientY
const deltaX = x - position[0]
const deltaY = y - position[1]
const left = parseInt(xxx.style.left || 0)
const top = parseInt(xxx.style.top || 0)
xxx.style.left = left + deltaX + 'px'
xxx.style.top = top + deltaY + 'px'
position = [x, y]
})
document.addEventListener('mouseup', function(e){
dragging = false
})
</script>
</body>
</html>
要点
-
注意监听范围,不能只监听 div
-
不要使用 drag 事件,很难用。
-
使用 transform 会比 top / left 性能更好,因为可以避免 reflow 和 repaint