深刻理解javascript事件机制

Created By JishuBao on 2019-04-17 12:38:22
Recently revised in 2019-04-17 17:38:22javascript

 

  欢迎你们来到技术宝的掘金世界,您的star是我写文章最大的动力!GitHub地址      html

不知道各位小伙伴在平时撸码中会不会常常看到诸如event.preventDefault()、event.stopPropagation()之类的方法,可能一开始看到只是随便的百度了下,大概了解这是干吗的,可是时间长了你只记得要点击事件的时候加上一下,可是这对于咱们对代码的掌控仍是远远不够,因此技术宝深入百度,带你们理一理这个javascript大头:事件机制java

1、了解事件

什么是事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。js与html的交互就是经过事件实现的。可使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码。git

事件最先出现于IE3Netscape Navigator2,IE九、Firefox、Opera、Safari、Chrome所有已经实现DOM2事件.IE8是最后一个仍然使用其专有事件系统的浏览器。github


2、了解事件流

事件流描述的是从页面中接收事件的顺序。可是,IE和Netscape提出了差很少彻底相反的概念。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕获流。浏览器

IE9/Opera/Firefox/Chrome/Safari都支持DOM事件流;IE8以及更早版本不支持DOM事件流。bash

1.事件冒泡

IE的事件流叫作冒泡事件,即事件开始时由最具体的元素接收,而后逐级向上传播到较为不具体的节点。click事件沿dom树向上传播,在每一级节点上都会发生,直至传播到document对象。dom

IE5.5以及更早版本中的事件冒泡会跳过<html>元素,从body直接跳到document,IE9/Firefox/Chrome/Safari则将事件一直冒泡到window对象函数

2.事件捕获

Netscape提出了另外一种事件流叫作事件捕获。事件捕获的用意在于在事件达到预约目标以前捕获它。post

IE9/Safari/Chrome/Opera和Firefox目前也都支持这种事件流模型。尽管"DOM2级事件"规范要求事件应该从document对象开始传播,可是这些浏览器都是从window对象开始捕获事件的。

3.dom事件流

"dom2级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。而后实际的目标接受到事件。最后一个阶段是冒泡阶段,能够在这个阶段对事件做出相应。

在dom事件流中,实际的目标在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document到html再到body后就中止了,下一个阶段是处于目标阶段,因而事件再div上发生,并在事件处理中被当作冒泡阶段的一部分。而后,冒泡阶段发生,事件又传播回文档。

虽然“dom2级事件”规范明确要求捕获阶段不会涉及到事件目标,可是IE九、Safari、Chrome、Safari及更高版本都会在捕获阶段触发事件对象上的事件。结果,就有2个机会在目标对象上操做事件。


3、事件处理程序

事件就是用户或浏览器自身执行的某种动做。诸如click、load、mouseover,都是事件的名字。而相应某个事件的函数就叫作事件处理程序(事件侦听器)。事件处理程序以“on”开头,所以click的事件处理程序就是onclick。为事件指定处理程序的方式有好几种:

1.HTML事件处理程序

某个元素支持的每种事件,均可以使用一个与相应事件处理程序同名的Html特性来指定。这个特性的值应该是可以执行的js代码。

<input type="button" value="click me" onclick="alert('clicked')"/>
复制代码

因为值是js,因此不能使用未转义的HTML语法字符,如:&、""、>、<,为了不使用HTML实体,这里使用了单引号。若是想使用双引号:

<input type="button" value="click me" onclick="alert(&quot;clicked me&quot;)" />
复制代码

除了包含具体的执行动做外,还能够调用其余地方定义的脚本(括号不可不加)

<input type="button" value="click me" onclick="showMessage()" />
<script type="text/javascript"> function showMessage(){ alert('Hello world') } </script>
复制代码

事件处理程序中的代码在执行时,有权访问全局做用域中的任何代码

这样指定事件处理程序会建立一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象

<input type="button" value="click me" onclick="alert(event.type)"/>//click
复制代码

在这个函数内部,this的值等于事件的目标元素。

<input type="button" value="click me" onclick="alert(this.value)"/>//click me
复制代码

在这个函数内部,能够像访问局部变量同样访问document以及该元素自己的成员。这个函数使用with像下面这样扩展做用域:

function(){
    with(document){
        with(this){
            //元素属性值
        }
    }
}
复制代码

这样以来,访问本身的属性简单的多

<input type="button" value="click me 5" onclick="alert(type)"/>//button
复制代码

若是当前元素是一个表单输入元素,则:

function(){
    with(document){
        with(this.form){
            with(this){
                //元素属性值
            }
        }
    }
}
复制代码

则不须要引用表单元素就能访问其余表单字段

<form method="post">
	<input type="text" name="username" value="jsbao"> <input type="button" value="Echo" onclick="alert(username.value)"> //jsbao </form> 复制代码

这样存在3个缺点:

时差问题,用户可能会在html元素一出现就触发相应的事件,但当时的事件处理程序有可能尚不具有执行条件。为避免没必要要的错误,咱们通常会用try-catch块包住:

<input type="button" value="click me" onclick="try{showMessage();}catch(ex){}"/>
复制代码

扩展事件处理程序的做用域链在不一样浏览器中会致使不一样结果,由于不一样Js引擎遵循的标识符解析规则略有差别!

html与js代码耦合度过高!

2.DOM0级事件处理程序

经过js指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。每一个元素都是本身的事件处理程序属性,这些属性一般所有小写,例如onclick。将这种属性的值设置为一个函数,就能够指定事件处理程序。

<input type="button" value="click me 5" id="button1"/>
var button1=document.getElementById('button1')
button1.onclick=function(){
	alert('hello world')
}
复制代码

要注意,在这些代码运行以前不会指定事件处理程序,所以若是这些代码在页面中处于按钮后面,就有可能在一段时间内怎么单击都没反应。

使用dom0级方法指定的事件处理程序被认为是元素的方法。所以,这时候的事件处理程序是在元素的做用域中进行,程序中的this引用当前元素。

<input type="button" value="click me 5" id="button2"/>
var button2=document.getElementById('button2');
	button2.onclick=function(){
	alert(this.id)//button2
}
复制代码

这种方式添加的事件处理程序会在事件流的冒泡阶段被处理

也能够经过dom0级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为null便可:

btn.onclick=null;//删除事件处理程序
复制代码

设置为null之后,再单击按钮将不会有任何动做发生

3.DOM2级事件处理程序

dom2级事件定义了俩个方法:addEventListener()removeEventListener(),分别用于处理指定和删除事件处理程序。全部dom节点都包含这2个方法,而且他们都接受3个参数:要处理的事件名、做为事件处理程序的函数、一个布尔值,最后这个参数若是是true,表示在捕获阶段调用事件处理程序;若是是false,表示在冒泡阶段调用事件处理程序。

<input type="button" value="click me 5" id="button3"/>
var button3=document.getElementById('button3');
button3.addEventListener("click",function(){
		alert(this.id)//button3
},false)
复制代码

与dom0级事件同样,这里添加的事件处理程序也是在其依附的元素的做用域中运行,使用dom2级的好处是能够添加多个事件处理程序。

<input type="button" value="click me 5" id="button3"/>
var button3=document.getElementById('button3');
button3.addEventListener("click",function(){
	alert(this.id)//button3
},false)
button3.addEventListener("click",function(){
	alert(this.value)//click me 5
},false)
//会按次序执行
复制代码

经过addEventListener添加的事件处理程序只能经过removeEventListener来移除,而传入的参数与添加处理程序使用的参数相同,这意味着经过addEventListener添加的匿名函数将没法移除;

button3.addEventListener("click",function(){
	alert(this.value)
},false)
button3.removeEventListener("click",function(){
	alert(this.value)
},false)
//无效,由于传入的匿名函数不同
复制代码
var handler=function(){
	alert(this.value);
}
button3.addEventListener("click",handler,false);
button3.removeEventListener("click",handler,false);//有效!
复制代码

大多数状况下,都是将事件处理程序添加到事件流的冒泡阶段,这样能够最大限度的兼容各类浏览器

IE、Firefox、Safari、Chrome、Opera支持dom2级事件处理程序

4.IE事件处理程序

IE实现了俩个方法:attachEvent()/detachEvent()。第一个参数为事件程序处理名称,第二个参数为事件处理函数名称。因为IE8以及更早版本只支持冒泡,因此经过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。

以下为给按钮添加事件处理程序:

var btn=document.getElementById('myBtn');
btn.attachEvent("onclick",function(){
    alert('clicked')
})
复制代码

与dom0事件的主要区别在于attachEvent的做用域会在全局做用域内执行,即this===window

var btn=document.getElementById('myBtn');
btn.attachEvent("onclick",function(){
    alert(this===window);//true
})
复制代码

与addEventListener相似,也能够为一个元素添加多个事件处理程序,可是不一样的是,他是按照相反的顺序被触发,detachEvent用法和removeEventListener用法同样,再也不赘述。

var btn=document.getElementById('myBtn');
btn.attachEvent("onclick",function(){
    alert("clicked")
})
btn.attachEvent("onclick",function(){
    alert("hello world!")
})
复制代码

支持IE事件处理程序的浏览器有IE和Opera

5.跨浏览器的事件处理程序

要保证处理事件的代码能带大多数浏览器下保持一致的运行,只需关注冒泡阶段

1.建立addHandler函数

这个方法的做用是视状况分别使用DOM0级方法、DOM2级方法、或者IE方法来添加事件。三个参数依次是:要操做的对象、事件名称、事件处理程序函数

addHandler:function(element,type,handler){
    if(element.addEventListener){
        element.addEventListener(type,handler,false);
    }else if(element.attachEvent){
        element.attachEvent("on"+type,handler);
    }else{
        element["on"+type]=handler;
    }
}
复制代码

2.建立removeHandler函数

这个方法的做用是移除以前添加的事件处理程序,参数和addHandler的参数一一对应

removeHandler:function(element,type,handler){
    if(element.removeEventListener){
        element.removeEventListener(type,handler,false)
    }else if(element.detachEvent){
        element.detachEvent("on"+type,handler);
    }else{
        element["on"+type]=null
    }
}
复制代码

4、事件对象

在触发dom上的某个事件时,会产生一个事件对象event。这个对象包含着全部与事件有关的信息。全部浏览器都支持event对象,但支持方式不一样。

1.dom中的事件对象

兼容dom的浏览器会将一个event对象传入事件处理程序中。event对象包含与建立它的特定事件有关的属性和方法,不过,全部event对象包含下表所列的成员。

属性/方法 类型 读/写 说明
bubbles Boolean 只读 代表事件是否冒泡
cancelable Boolean 只读 表面是否能够取消事件的默认行为
currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素
defaultPrevented Boolean 只读 为true表示以及调用了preventDefault()(DOM3级事件新增)
detail Integer 只读 与事件相关的细节信息
eventPhase Integer 只读 调用事件处理程序的阶段 1.捕获2.处于目标3.冒泡
preventDefault Function 只读 取消事件的默认行为。若是cancelable是true,则可使用这个方法
stopImmediatePropagation Function 只读 取消事件的进一步冒泡或捕获,同时阻止任何事件处理程序被调用(dom3级事件中新增)
stopPropagation Function 只读 取消事件的进一步冒泡或捕获.若是bubbles是true,则可使用这个方法
target Element 只读 事件的目标
trusted Boolean 只读 为true表示事件是浏览器生成的,为false表示事件是由开发人员经过js建立的(dom3级事件中新增)
type String 只读 被触发的事件的类型
view AbstractView 只读 与事件关联的抽象视图。等同与发生事件的window对象
相关文章
相关标签/搜索