JS-15:DOM事件模型

147 阅读3分钟

1 DOM事件模型

1.1 监听函数

浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器听到了这个事件,就会执行对应的监听函数,这是事件驱动编程模式(event-driven)的主要编程方式。

1.2 JS绑定监听函数

  • HTML 的 on 属性
<div onclick = 'console.log('')> 
<div onclick = 'fn()'  //函数名要加圆括号
  • 元素节点的事件属性
widow.onload = fn;  //等号右边是函数名,不加圆括号
div.onclick = function(event){
  console.log('xx')
}
  • DOM节点的 .addEventListener 方法
div.addEventListener('click',fn,true/false)
  • 比较 第一种违反了 HTML 与 JavaScript 代码相分离的原则;

第二种,同一个事件只能定义一个监听函数,也就是说,如果定义两次 onclick属性,后一次定义会覆盖前一次;

第三种,推荐使用,优点有:同一个事件可以添加多个监听函数;能够指定捕获阶段还是冒泡阶段触发监听函数;除了DOM节点,其他对象(比如 window、XMLHttpRequest等)也有这个接口,它等于是整个 JavaScript 统一的监听函数接口;

1.3 this 的指向

监听函数内部的 this 指向触发事件的那个元素节点(e.currentTarget),以下三种方式的 this 都输出 btn ,不推荐使用 this 。

<button id = 'btn' onclick = 'console.log(this.id)'>点击</button>

<button id='btn'>点击</button>

btn.onclick=function(){
  console.log(this.id)
}

btn.addEventListener('click',function(e){
  console.log(this.id)
},false)

2 事件的传播

  • 第一阶段:从 window 对象传到目标节点(从外到内),称为“捕获阶段(capture phase)”;

  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase);

  • 第三阶段:从目标节点传导回 window 对象(从内到外),称为“冒泡阶段”(bubbling phase)

image.png

  • 先捕获,再冒泡,中间有函数就执行,没函数就跳过;

  • e 对象被传给所有监听函数;

  • 事件结束后,e 对象会被浏览器悄悄修改,不推荐在事件结束后访问 e ,要用的话提前将属性复制到自己的变量里: conse a = e.currentTarget

3 target 和 currentTarget

  • 区别:
  1. e.target :用户操作的元素

  2. e.currentTarget :程序员监听的元素

  3. this 是 e.currentTarget ,不推荐使用

<div>
  <span>文字</span>  //用户点击文字,e.target 就是
</div>    //文字,e.currentTarget 就是 div 

4 取消冒泡和默认动作

  • event.stopPropagation() :阻止捕获和冒泡阶段中当前事件的进一步传播;

  • event.stopPropagation() 只能阻止这个事件的传播,不能取消这个事件;

// 事件传播到 p 元素后,就不再向下传播了`
p.addEventListener('click', function (event) {
event.stopPropagation();
console.log(1)  //冒泡被阻止,函数会触发,输出1;
}, true);

// 事件冒泡到 p 元素后,就不再向上冒泡了`
p.addEventListener('click', function (event) {
event.stopPropagation();
console.log(2)  //冒泡被阻止,函数会触发,输出2;
}, false);
  • 如果要彻底取消函数,不在触发后面所有 click 的监听函数,可以使用 stopImmediatePropagation 方法:
p.addEventListener('click', function (event) {
event.stopImmediatePropagation();
console.log(1);  //只输出1;
});

p.addEventListener('click', function(event) {
// 不会被触发
console.log(2);
});
  • Cancelable :是否可以阻止默认事件,与冒泡无关;

5 事件代理

由于事件在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法就叫事件代理(delegation):

let ul = document.querySelector('ul')
ul.addEventListener('click',function(e){
  if(e.target.tagName.toLowerCase() === 'li'){
    console.log('xxx')
  }   
}
// click 事件的监听函数定义在 ul 节点,但是处理的是子节点 li 的 click 事件。好处是:只要定义一个监听函数,就能处理多个子节点的事件,而不用在每个 li 节点上定义监听函数。而且以后再添加子节点,监听函数依然有效。
  • 如何监听目前不存在的元素(动态元素) 监听祖先,等点击的时候看看是不是自己想要监听的元素即可;
<div id="div1">
</div>

setTimeout(()=>{
  const button = document.createElement('button')
  button.textContent = 'click1'
  div1.appendChild(button)
},3000)

div1.addEventListener('click',(e)=>{
  const t = e.target
  if(t.tagName.toLowerCase() === 'button'){
    console.log('button 被 click 了')
  }
})

6 阻止滚动

  • scroll事件不能阻止默认动作;

  • 要阻止滚动,可阻止 wheel 和 touchstart 的默认动作;

  • 要找准滚动条所在的元素;

  • 可以用 CSS 让滚动条 width:0;

  • 可以用 overflow:hidden 直接取消滚动条,但此时 JS 依然可以修改 scrollTop ;

x.addEventListener('wheel', (e)=>{
  console.log(2)
  e.preventDefault()  //阻止滚动事件
})

x.addEventListener('touchstart', (e)=>{
  console.log(2)
  e.preventDefault()  //阻止触摸事件
})

::-webkit-scrollbar { width: 0 !important } //移动端隐藏滚动条
  • 封装事件委托
<div id="div1">
</div>

setTimeout(()=>{
  const button = document.createElement('button')
  button.textContent = 'click1'
  div1.appendChild(button)
},3000)

on('click','#div1','button',()=>{
  console.log('xxxx')
})

function on(eventType,element,selector,fn){
  if(!(element instanceof Element)){
    element = document.querySelector(element)
  }
  element.addEventListener(eventType,(e)=>{
    const t = e.target  // t 为button
    if(t.matches(selector)){
      fn(e)
    }
  })
}
//将上述代码改为:内容被 span 包围,span 放在 button 中:
setTimeout(()=>{
  const button = document.createElement('button')
  const span = document.createElement('span')
  span.textContent = 'click1'
  div1.appendChild(button)
  button.appendChild(span)
},3000)

on('click','#div1','button',()=>{
  console.log('xxxx')
})

function on(eventType,element,selector,fn){
  if(!(element instanceof Element)){
    element = document.querySelector(element)
  }
  element.addEventListener(eventType,(e)=>{
    let el = e.target   //el初始值为 span ,与selector的值 button 不匹配;
    while(!el.matches(selector)){
      if(element === el){
        el = null  //认为元素不存在
        break
      }
      el = el.parentNode
    }
    el && fn.call(el,e,el)
  })
  return element
}

7 浏览器事件

  • 事件参考 | MDN (mozilla.org)

  • DOM 事件不属于 JS 的功能,是浏览器提供的 DOM 的功能,JS 只是调用了 DOM 提供的 addEventListener 而已。