DOM事件的工作原理

作者: 犯迷糊的小羊 | 来源:发表于2016-07-18 18:02 被阅读947次

    导读:
    本文是teren对DOM事件知识点所做的进一步整理,整理资料主要参考DOM事件简介饥人谷课件,如果对DOM事件有什么不了解的地方,可以直接参考原文出处。
    这篇笔记的目的有两个,一是作为自己整理的资料方便日后查阅,毕竟自己整理的资料用起来更加得心应手,思路更加契合自己;二是希望通过在撰写笔记过程中强化记忆;
    如果这篇文章有什么能够帮助到各位读者,我要万分感谢原文的作者以及原文的翻译!!!
    最后,这篇文章的整理结构如目录所示。

    目录
    1.预备知识
    监听事件
    移除事件
    2.事件阶段
    3.event对象
    4.事件的操作
    停止事件传播
    阻止浏览器的默认行为
    自定义事件
    代理事件监听
    5.FQA
    DOM 0事件和DOM 2事件
    事件流的三种模型
    IE兼容性


    1. 预备知识

    监听事件

    在为节点添加事件时,推荐使用addEventListener接口(IE的使用attachEvent接口)

    node.addEventListener(eventname,callback[,useCapture])
    
    • eventname:监听事件的名称,如
      click/mouseover/mouseout/mousedown/mouseup/load/unload等事件

    • callback:事件触发时被调用的函数,此时会自动产生一个event对象,作为第一个参数传入callback

    var ul = document.querySelector('ul')
    ul.addEventListener('click',function(event){console.log(event.target)})
    

    关于event对象后面详细讲解

    • useCapture:决定回调函数(callback)是否在“捕获(capture)”阶段被触发,,默认是false,即在冒泡阶段被触发
      关于事件阶段后面详解,现在给个范例演示,可以看完事件阶段章节后再“回调”查看(~ ̄▽ ̄)~
      demo: usecapture
    移除事件

    移除事件使用removeEventListener接口

    node.removeEventListener(eventname,callback)
    

    值的注意的是:
    通过addEventListener添加的事件处理程序只能通过removeEventListener移除,移除时参数与添加的时候相同;
    这也就意味着:在移除事件时回调函数不能为匿名函数,因为匿名函数虽然方法体一样,但是句柄(可以理解为函数名)却不相同
    也就是说当初定义回调函数必须以以下形成出现

    var ul  = document.querySelector('ul')
    //method 1
    function printHello(){
      alert('hello world')
    }
    //method 2
    handler = function (){
      alert('hello world 2')
    }
    ul.addEventListener('click',printHello);
    ul.addEventListener('click',handler);
    ul.addEventListener('click',function(){alert('hello world 3')})
    ul.removeEventListener('click',printHello);
    //此时你要移除第3个hello world,那么你无法码下去
    //ul.removeEventListener('click',)
    

    demo : removeEvent

    2.事件阶段

    以一个例子去描述事件阶段

      <ul>
        <li id="demo1">demo1</li>
        <li id="demo2">demo2</li>
        <li id="demo3">demo3</li>
      </ul>
    
    var demo2 = document.querySelector('#demo2')
    demo2.addEventListener('click',callback)
    function callback(){
      console.log('demo')
    }
    

    给li#demo2节点添加click事件,在点击li#demo2节点时,点击事件不是直接在该节点直接发生,而是分为三个事件阶段:

    • click事件从html文档的根节点window流向目标节点li#demo2(捕获阶段)
    • 然后在目标节点上click事件触发(目标阶段)
    • 最后再返回到文档的根节点(冒泡阶段)


      事件阶段

    demo:event phases
    小结:
    事件触发的整个过程可分为三个阶段:

    • 捕获阶段
      事件的第一个阶段是捕获阶段。事件从文档的根节点出发,随着DOM树的结构向事件的目标节点流去。途中经过各个层次的DOM节点,并在各节点上触发捕获事件,直到到达事件的目标节点。

    类似水流一样,从源头流向目的地

    • 目标阶段
      当事件到达目标节点的,事件就进入了目标阶段。事件在目标节点上被触发,然后会逆向回流,直到传播至最外层的文档节点
      【注】
      或许有人会疑问?事件在目标节点被触发,那么设置usecapture还有什么用处呢?
      我的理解是:
      你为节点设置事件是一回事,你触发事件时是另一回事;
      节点的事件触发时点可分为上述三个阶段,在乎你怎么设置
      下面代码中,li#demo2一定是在目标阶段被触发,而ul则在乎你的设置,下例设置为捕获阶段被触发
    var ul = document.querySelector('ul')
    var demo2  = document.querySelector('#demo2')
    function printUl(){alert('Ul')}
    function printList(){
      alert('List')
    }
    //当点击li#demo2时,到了目标阶段触发li#demo2的click事件
    //当你为ul的usecapture设置true时,意味着ul在捕获阶段触发click事件
    ul.addEventListener('click',printUl,true)
    demo2.addEventListener('click',printList)
    

    demo:how to recognize event phases

    • 冒泡阶段
      事件在目标元素上触发后,并不在这个元素上终止。它会随着DOM树一层层向上冒泡,直到到达最外层的根节点

    3.event对象

    event对象是在事件第一次触发时候被创建,并且一直伴随着事件在DOM结构中流转的整个生命周期。
    event对象会被作为第一个参数传递给事件监听的回调函数。
    event对象中包含大量当前事件相关的信息:

    属性/方法 备注
    type 事件名称
    target 事件的目标节点
    currentTarget 事件触发时的当前节点
    bubbles 判断节点的事件是否是在冒泡阶段捕获
    preventDefault(function) 阻止浏览器中用户代理对当前事件的相关默认行为被触发。比如阻止a元素的click事件加载一个新的页面
    cancelable(boolean) 指明这个事件的默认行为是否可以通过调用event.preventDefault来阻止,即只有cancelable为true的时候,调用event.preventDefault才能生效
    stopPropagation(function) 阻止当前事件流上后面的元素的回调函数被触发,当前节点上针对此事件的其他回调函数依然会被触发
    stopImmediatePropagation(function) 阻止当前事件流上后面所有的回调函数被触发,也包括当前节点上针对此事件已绑定的其他回调函数
    eventPhase(number) 表示当前这个事件所处的阶段(phase):none(0), capture(1),target(2),bubbling(3)
    timeStamp(number) 事件发生的时间

    下面将一些简单的属性放在下面的示例中,复杂的方法将在事件操作章节单独罗列
    demo : event simple property

    4.事件的操作

    停止事件传播

    通过调用事件对象的stopPropagation方法,在任何阶段(捕获阶段或者冒泡阶段)中断事件的传播;
    此后,事件不会在后面传播过程中的经过的节点上调用任何的监听函数;
    demo:stopPropagation
    但event.stopPropagation()不会阻止当前节点上此事件其他的监听函数被调用。如果你希望阻止当前节点上的其他回调函数被调用的话,你可以使用更激进的event.stopImmediatePropagation()
    方法;
    demo:stopImmediatePropagation

    阻止浏览器的默认行为

    当特定事件发生的时候,浏览器会有一些默认的行为作为反应。例如,使用a元素时会自动添加click事件,当a元素上click事件触发时,它会向上冒泡直到DOM结构的最外层document,浏览器会解释href属性,并且在窗口中加载新地址的内容。
    如果我们需要阻止浏览器针对点击事件的默认行为,可以调用event.preventDefault()
    demo:preventDefault

    自定义事件

    【注】
    知道有这么一回事,这篇不详讲。

    代理事件监听

    所谓代理事件监听,指的是不直接在监听的目标节点上添加事件监听函数,而是通过其他的节点代为监听目标节点的事件;

    举个例子:
    如果有一个列表ul包含了100个子元素li,它们都需要对click事件做出相似的响应,那么我们可能需要查询这100个子元素,并分别为他们添加上事件监听器。这样的话,我们就会产生100个独立的事件监听器

    代理事件监听可以让我们更简单的处理这种情况。我们不去监听所有的子元素的click事件,相反,我们监听他们的父元素ul。当一个li元素被点击的时候,这个事件会向上冒泡至ul,触发回调函数。我们可以通过检查事件的event.target属性来判断具体是哪一个li被点击了。
    这样一来,仅仅使用了一个上层的事件监听器,并且我们不需要在为添加元素而考虑它的事件监听问题
    demo:事件代理
    但是在实际代理事件监听中,我们往往使用jQuery提供的on()方法去实现事件代理
    demo:事件代理-on()方法

    5.FQA

    DOM 0级事件处理程序和DOM 2级事件处理程序

    首先,了解一下DOM的分级。
    DOM是HTML与XML的应用编程接口(API),DOM将整个页面映射为一个由层次节点组成的文件,有1级、2级、3级共3个级别。
    1级DOM
    1级DOM,由DOM核心与DOM HTML两个模块组成。
    DOM核心能映射以XML为基础的文档结构,允许获取和操作文档的任意部分。
    DOM HTML通过添加HTML专用的对象与函数对DOM核心进行了扩展。
    2级DOM
    鉴于1级DOM仅以映射文档结构为目标,DOM 2级面向更为宽广。通过对原有DOM的扩展,2级DOM通过对象接口增加了:
    DOM视图:描述跟踪一个文档的各种视图(使用CSS样式设计文档前后)的接口;
    DOM事件:描述事件接口;
    DOM样式:描述处理基于CSS样式的接口;
    DOM遍历与范围:描述遍历和操作文档树的接口;
    3级DOM
    3级DOM通过引入统一方式载入和保存文档和文档验证方法对DOM进行进一步扩展
    "0级"DOM
    需要注意的是并没有标准被称为0级DOM,它仅是DOM历史上一个参考点(0级DOM被认为是在Internet Explorer 4.0 与Netscape Navigator4.0支持的最早的DHTML)

    也就是说:
    DOM 0级事件处理程序是 通过javascript制定事件处理程序的传统方式,具体实现方式是:

    var btn = document.getElementById("btn");            
    btn.onclick = function(){                
    alert(this.id);//this指定当前元素btn  
    }
    删除DOM0事件处理程序,
    只要将对应事件属性置为null即可。btn.onclick = null;
    

    DOM 0级事件处理程序的优点是简单且具有跨浏览器的优势,缺点是一个事件处理程序只能对应一个处理函数

    DOM2级事件处理程序是在2级DOM中规定的API,通过addEventListener(IE为attachEvent)去监听事件,具体实现方式是:

    var btn = document.getElementById("btn");
    function handler(){
      alert(this.id)//this指定当前元素btn
    }
    btn.addEventListener('click',handler)
    

    demo:addEventListener
    同时制定了删除事件处理程序的方法
    removeEventListener(IE为detachEvent),关于removeEventListener的注意事项请详见上文移除事件章节;
    至于attachEvent与addEventListener的区别详见后文IE兼容性
    addEventListener的优点是一个事件处理程序能对应多个处理函数,缺点是存在兼容性问题。

    事件流的三种模型

    所谓事件流,指的是页面捕获事件的顺序,目前有三种模型:

    • IE的事件冒泡:当发生事件时,目标节点先捕获,然后逐级向上传播到父节点,即事件监听处于冒泡阶段

    • Netscape的事件捕获:当发生事件时,最先触发父节点的事件监听函数,然后逐渐向下传播到目标节点,即事件监听处于捕获阶段

    • 2级DOM规定事件流包括三个阶段,事件捕获阶段,处于目标阶段,事件冒泡阶段

    IE兼容性

    IE并不支持addEventListener和removeEventListener方法,而是实现了两个类似的方法:
    attachEvent(eventname,callback)
    detachEvent(eventname,callback)
    由于IE指支持事件冒泡,所以添加的程序会被添加到冒泡阶段。
    【注意】
    IE的事件监听的方法与addEventListener方法不同之处包括:
    eventname必须包含on以及没有usecapture;
    同时,使用attachEvent方法和addEventListener主要区别在于事件处理程序的作用域。采用addEventListener,事件处理程序会在其所属元素的作用域内运行。使用attachEvent,事件处理程序会在全局作用域内运行,因此this等于window。

    var btn = document.getElementById("btn");
    function handler(){
      alert(this.id)//this指定window
    }
    btn.attachEvent('onclick',handler)
    

    相关文章

      网友评论

      本文标题:DOM事件的工作原理

      本文链接:https://www.haomeiwen.com/subject/zpgujttx.html