DOM事件(一)

2018-09-12  本文已影响8人  zhaochengqi

JavaScript使用侦听器来订阅事件,实现与HTML之间的交互。

DOM2级规范开始尝试标准化DOM事件。主流浏览器已全部实现了DOM2级事件模块的核心部分。IE8是最后一个仍使用其专有事件系统的主要浏览器。

一、 DOM事件流

1.1 事件流

事件流描述的是从页面接收事件的顺序。

尽管“DOM2级事件”规范要求事件应该从document对象开始传播,但IE9、Safari、Chrome、Opera和Firefox都是从window对象开始捕获事件的。在冒泡模式中也将事件一直冒泡到window对象


“DOM2级事件”规定的事件流包括三个阶段:

  1. 事件捕获阶段

    事件从 documenthtml 再到 body。这个阶段目标节点不会接收到事件

  2. 处于目标阶段

    目标节点接收到事件,在事件处理中被看做冒泡阶段的一部分

  3. 事件冒泡阶段

    事件又从目标节点传播回文档

即使“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但主流浏览器高版本都会在捕获阶段触发事件对象上的事件。结果就有两个机会在目标对象上面操作事件。

IE8及更早版本不支持DOM事件流(不支持捕获)

二、 事件处理程序

click、load、mouseover等由用户或浏览器自身执行的某种动作就是事件。
而响应某个事件的函数就是事件处理程序(或事件侦听器)

事件处理程序的名字以“on”开头:onclick、onload;为事件制定处理程序有好几种方式。

【事件类型传送门】

2.1 HTML事件处理程序

使用一个与相应事件处理程序同名的HTML特性来指定,指定的值应该是 能够执行的JavaScript代码,

所以调用页面其他地方定义的函数时不能只填一个函数名哦 。

<a onclick="alert('clicked!')">click me</a>
<a onclick="foo()">click me</a>
<a onclick="console.log(event)">click me</a>

> MouseEvent {isTrusted: true, screenX: 39, screenY: 120, ...}

事件处理程序作用域使用with像下面这样扩展

function(){
    with(document){
        with(this){
            
        }
    }
}

SO,事件处理程序访问自己的属性就肥肠简单了

<a onclick="console.log(href)">click me</a>

如果当前元素是一个表单输入元素,则作用域中还会包含表单元素的入口

function(){
    with(document){
        with(this.form){
            with(this){
                
            }
        }
    }
}

这样不需要引用表单元素就能访问其他表单字段

<form action="#">
    <input type="text" name="username">
    <input type="button" value="submit" onclick="console.log(username.value)">
</form>

BUT,如果不是在HTML特性中直接写的执行代码,而是调用的其他地方定义的函数,那么在指定函数中是无法直接访问上述扩展的作用域的。而且this等于window(除非你用箭头函数)

<button id="btn1" onclick="console.log(id)">HTML</button> // btn1
<button id="btn2" onclick="handler()">HTML2</button>
<script>
    function handler() {
      console.log(id)  //Uncaught ReferenceError: id is not defined
    }
</script>

通过HTML指定事件处理程序最大的缺点就是HTML与JavaScript代码紧密耦合


2.2 DOM0级事件处理程序

通过JavaScript指定事件处理程序的传统方式: 将一个函数赋值给一个事件处理程序属性

每个元素(包括window和document)都有自己的事件处理程序属性,这些属性通常全部小写。
这个方法会替换这个元素上所有已存在的相应事件处理程序

//首先取得一个要操作的对象的引用
var btn = document.getElementById('btn')
//将属性值设为一个函数
btn.onclick = function(){console.log('clicked:'+this.id)}

tips:

  1. 使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此事件处理程序是在元素的作用域内执行(函数中this引用当前元素,但没有像HTML那样为我们扩展作用域)
  2. 以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理
  3. 删除通过DOM0级方法指定的事件处理程序,只需将相应属性值设为null: btn.onclick=null(同样可以清除通过HTML特性设置的相应事件处理程序)
  4. 通过HTML指定事件处理程序,则节点对象相应的事件处理属性的值就是一个包含着在HTML特性中指定代码的函数。eg.
  <button id="btn1" onclick="console.log(id)">HTML</button>
  <button id="btn2" onclick="handler()">HTML2</button>
  <button id="btn3">DOM0</button>
  <script>
    function handler() {
      console.log(this.id)
    }
    //首先取得一个要操作的对象的引用
    var btn1 = document.getElementById('btn1')
    var btn2 = document.getElementById('btn2')
    var btn3 = document.getElementById('btn3')
    //将属性值设为一个函数
    btn3.onclick = function(){console.log(this.id)}

    console.log(btn1.onclick) //ƒ onclick(event) {console.log('clicked:'+this.id, event, this, id)}
    console.log(btn2.onclick) //ƒ onclick(event) {handler()}
    console.log(btn3.onclick) //ƒ (){console.log(this.id)}
  </script>

由此也可以看出为什么在HTML特性中指定外部处理函数时,在函数内无法访问扩展的作用域(直接访问节点对象属性)的原因(函数作用域)。

为我们扩展作用域的应该是 onclick(event)函数:)


2.3 DOM2级事件处理程序

“DOM2级事件”定义了两个方法来指定和删除事件处理程序:

```js
//那些不支持参数options的浏览器,会把第三个参数默认为useCapture,即设置useCapture为true
target.addEventListener('click', listener ,{
    capture: Boolean, 
    //表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
   
    once: Boolean, 
    //表示 listener 在添加之后最多只调用一次。如果是 true, 
    //listener 会在其被调用之后自动移除。
    
    passive: Boolean, 
    //表示 listener 永远不会调用 preventDefault()。
    //如果listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
    //添加passive参数后,touchmove事件不会阻塞页面的滚动(同样适用于鼠标的滚轮事件)
});

/**
true 是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件
目标,接收到该事件。沿着DOM树向上冒泡的事件不会触发被指定为use capture
(也就是设为true)的listener。当一个元素嵌套了另一个元素,两个元素都对
同一个事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的
事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可
以查看 事件流 及 JavaScript Event order 文档。 如果没有指定, useCapture
默认为 false 。 
**/
target.addEventListener('click', listener, true)
```

tips:

  1. DOM2级方法添加的事件处理程序也是在其依附的元素的作用域中执行的(没有扩展作用域)
  2. 可以添加多个事件处理程序,按顺序触发
  3. 通过addEventListener添加的事件处理程序只能通过使用removeEventListener来移除,移除时传入的参数与添加时使用的参数要相同。这也意味着通过addEventListener添加的匿名函数将无法移除
  4. IE8不支持

2.4 IE事件处理程序

IE实现了与DOM2级中类似的两个方法:attachEventdetachEvent

tips:

  1. IE8及更早版本只支持事件冒泡,所以通过attachEvent添加的事件处理程序都会被添加到冒泡阶段
  2. attachEvent第一个参数是onclick而不是DOM中addEventListenerclick
  3. 事件处理程序在全局作用域中运行,因此this等于window
  4. 可以为一个元素添加多个事件处理程序,但不同于DOM方法,他们不是按添加他们的顺序执行,而是以相反的顺序被触发。
  5. 只有IE和Opera支持IE事件处理程序

FINAL

作用域 HTML/函数 DOM0 DOM2 IE
event Y Y Y Y
this Y/N Y Y N
this.xxx Y/N N N N

var div1 = document.getElementById('div1')
var div2 = document.getElementById('div2')
function handler(){
  var ep = ''
  switch(event.eventPhase){
    case 1: ep = '捕获阶段'; break;
    case 2: ep = '处于目标'; break;
    case 3: ep = '冒泡阶段'; break;
    default: ep = 'error';
  }
  console.log(this.id, ep)
}
div1.addEventListener('click', handler, true)
div1.addEventListener('click', handler, false)
div2.addEventListener('click', handler, true)
div2.addEventListener('click', handler, false)

<= to be continued

上一篇 下一篇

猜你喜欢

热点阅读