JS高程:读书摘要(十一)事件

JavaScriptHTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码(属于观察者模式),在DOM元素上注册事件处理程序就是订阅,当事件被触发的时候,DOM元素便会向订阅者发布这个消息。当然我们还可以随意增加或者删除订阅者,增加任何订阅者都不会影响发布者代码的编写。

一、事件流

事件流描述的是从页面中接收事件的顺序,分为事件冒泡和事件捕获,事件冒泡就是targetElement -> body -> document,事件捕获就是document -> body -> targetElement

由于老版本的浏览器不支持,因此很少有人使用事件捕获。我们也建议读者放心地使用事件冒泡,在有特殊需要时再使用事件捕获。

DOM事件流

DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。document -> body ->targetElement -> body -> document

二、事件处理程序

HTML事件处理程序

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML属性来指定。属性值可以是一个js语句字符串,例如:alert('hello wrold');也可以是一个定义在全局作用域下的函数调用,在事件被触发时,会执行相关代码。由此可知属性值是指定的函数代码,如果将这个属性值设置为null,也可以移除该事件。

<input type="button" name="clickBtn" value="Echo Username" onclick="show()">
<input type="button" name="clickBtn1" value="test" onclick="alert(this.name)"> 
<!-- 第二个按钮被点击会弹出"clickBtn1"  在html定义的事件函数的this指向当前元素 -->
<script>
    function show() {
        console.log(this); // window
        // 第一个按钮的oncclick函数定义在js中
        // show函数中this指向window(谁调用指向谁)
        // show函数是一个全局函数,当第一个input被点击的只是执行了这个show函数
    } 
</script>

forminputname属性,会在form中生成一个同名指针指向对应的input。在form表单元素中,事件函数的作用域相当于有documentform 、 本身元素,可以像访问局部变量一样访问这些作用域中的属性 ,所以可以直接访问到username.name相当于form.username.value

<form method="post" id="form1">
    <input type="text" name="username" value="">
    <input type="button" name="clickBtn" value="Echo Username" 
            onclick="alert(username.value)">
</form>
<script>
    var form1 = document.getElementById('form1')
    console.log(form1);
    console.log(form1.username); // 打印出name为username的input元素
    console.log(form1.clickBtn.value); // Echo Username
</script>
DOM0 级事件处理程序

使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素。

<input type="button" id="btn1" name="clickBtn" value="Echo Username">
<script>
    var oBtn = document.getElementById('btn1')
    // 每个元素都有自己的事件处理程序属性,这些属性通常全部小写
    // 将这种属性的值设置为一个函数,就可以指定事件处理程序
    oBtn.onclick = function(){
        console.log(this.name) // 'clickBtn'
        // 使用JavaScript 指定事件处理程序 this指向oBtn元素
        // 因为事件处理程序(或者说onclick属性)是注册到这个元素上的 
    }
    btn.onclick = null; //删除事件处理程序
</script>
DOM2 级事件处理程序
  • addEventListener()removeEventListener()

所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

var btn = document.getElementById("myBtn");

btn.addEventListener("click", function(){
    alert(this.id);
}, false);

btn.addEventListener("click", function(){
    alert("Hello world!");
}, false);

使用DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。不会被覆盖,会根据添加的顺序相继执行。

removeEventListener()来移除事件处理程序,移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除。

IE的事件处理程序
  • attachEvent()detachEvent()

这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数,因为IE8及更早的版本只支持事件冒泡。第一个参数事件处理程序名称字符串是需要加"on"的,例如"onclick"。也可以用来为一个元素添加多个事件处理程序,但是不是以添加它们的顺序执行,而是以相反的顺序被触发。attachEvent()添加的匿名函数也不能使用detachEvent()移除。

跨浏览器的事件处理程序
var EventUtil = {
    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;
        }
    },
    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;
        }
    }
};

EventUtil.addHandler(btn, "click", handler);
EventUtil.removeHandler(btn, "click", handler);

三、事件对象

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。

event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下列通用属性。

  • bubblesBoolean类型,表明事件是否冒泡。
  • cancelableBoolean类型,表明是否可以取消事件的默认行为。
  • currentTargetElement类型,其事件处理程序当前正在处理事件的那个元素。
  • preventDefault():取消事件的默认行为。如果cancelabletrue,则可以使用这个方法
  • defaultPreventedBoolean类型,为true 表示已经调用了preventDefault()DOM3新增
  • stopPropagation():取消事件的进一步捕获或冒泡。如果bubblestrue,则可以使用这个方法。
  • stopImmediatePropagation():取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用;DOM3新增
  • targetElement类型,事件的目标。
  • typeString类型,被触发的事件的类型
  • eventPhase:调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段

事件处理程序中的thiscurrentTarget,均指向注册事件的那个元素。而target则指向的是事件目标,即事件在哪个元素上被触发。如果注册事件的元素和触发事件的元素则三个值相等。

// 点击了myBtn 以下函数会弹出3个true
document.body.onclick = function(event){
    alert(event.currentTarget === document.body); //true
    alert(this === document.body); //true
    alert(event.target === document.getElementById("myBtn")); //true
};

在需要通过一个函数处理多个事件时,可以使用type属性和switch语句结合,不同的类型执行不同的代码。

如果你想阻止链接导航这一默认行为,那么通过链接的"onclick"事件处理程序可以取消它

var link = document.getElementById("myLink");
link.onclick = function(e){
    e.preventDefault();
};

如果你想停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
    alert("Clicked");
    event.stopPropagation();
};
document.body.onclick = function(event){
    alert("Body clicked"); // 将不会执行。
};
IE中的事件对象

在使用DOM0 级方法添加事件处理程序(元素注册on事件)时,event对象作为window对象的一个属性存在。如果事件处理程序是使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    var event = window.event;
    alert(event.type); //"click"
}

btn.attachEvent("onclick", function(event){
    alert(event.type); //"click"
});

IE 的event 对象的通用属性:

  • cancelBubbleBoolean类型,可读写,默认值为false,但将其设置为true就可以取消事件冒泡。
  • returnValueBoolean类型,可读写,默认值为true,但将其设置为false就可以取消事件的默认行为。
  • srcElementElement类型,只读,事件的目标(与target属性相同)
  • typeString类型,被触发的事件的类型

四、事件类型

4.1 UI事件

User Interface(用户界面)事件,指的是那些不一定与用户操作有关的事件。以下<object>元素可以理解为<script><link>标签。

onload

onload:当页面完全加载后(包括所有图像、JavaScript 文件、CSS文件等外部资源)在window上面触发,当图像加载完毕时在<img>元素上面触发,或者当嵌入的内容加载完毕时在<object>元素上面触发。

我们如果想向DOM中添加一个新元素,必须确定页面已经加载完毕——如果在页面加载前操作DOM会导致错误。新图像元素不一定要被添加到文档后才开始下载,只要设置了src属性就会开始下载。与图像不同,只有在设置了<script>元素的src属性并将该元素添加到文档后,才会开始下载JavaScript 文件。

除IE8及更早版本外,都支持<script>onload事件;IE 和Opera 支持<link>元素上的onload事件。

onunload

onunload 事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload 事件。用的最多的情况是清除引用,以避免内存泄漏。可以注册在window对象上,也可以在为<body>元素添加onunload属性来指定事件处理程序。

需要小心编写onunload事件处理程序中的代码。unload事件是在一切都被卸载之后才触发,这样在页面加载后存在的那些对象,此时就不一定存在了。此时,操作DOM节点或者元素的样式就会导致错误。

onabort

在用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上面触发。

onerror

当发生JavaScript错误时在window上面触发,当无法加载图像时在<img>元素上面触发,当无法加载嵌入内容时在<object>元素上面触发,或者当有一或多个框架无法加载时在框架集上面触发。

onselect

当用户选择文本框(<input><texterea>)中的一或多个字符时触发。

onresize

onresize:当窗口或框架的大小变化时在window或框架上面触发。

onscroll

onscroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。<body>元素中包含所加载页面的滚动条。可以通过document.documentElement / document.bodyscrollLeftscrollTop属性 来监控到这一变化。

resizescroll事件也会在窗口变化 / 文档被滚动期间重复被触发,所以有必要尽量保持这两个事件处理程序的代码简单。

4.2 焦点事件

焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。

  • onblur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
  • onfocus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
  • onfocusin:在元素获得焦点时触发。,但它冒泡。支持这个事件的浏览器有IE5.5+、Safari 5.1+、Opera 11.5+和Chrome。
  • onfocusout:在元素失去焦点时触发。这个事件是HTML 事件blur 的通用版本。对这个事件支持的浏览器与onfocusin一样。
4.3 鼠标事件
  • onclick:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保易访问性很重要,意味onclick 事件处理程序既可以通过键盘也可以通过鼠标执行。

  • ondblclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。

  • onmousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。

  • onmouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。

  • onmouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。

  • onmousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。

  • onmouseout:在鼠标指针位于目标元素上方,然后用户将其移入另一个元素时在目标元素触发。移入目标元素外部或者是移入目标元素的子元素也会触发。不能通过键盘触发这个事件。

  • onmouseover:在鼠标指针位于目标元素外部或者目标元素的子元素上,然后用户将其移入目标元素时触发。不能通过键盘触发这个事件。

  • onmouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。

  • onmousewheel:在用户滚动鼠标滚轮时触发。

页面上的所有元素都支持鼠标事件。除了mouseentermouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。

只有在同一个元素上相继触发mousedownmouseup事件,才会触发click事件;如果mousedownmouseup中的一个被取消,就不会触发click 事件。顺序是:mousedown->mouseup->click->mousedown->mouseup->click->dblclick

鼠标事件都是在浏览器视口中的特定位置上发生的,会有相应的位置信息保存在事件对象中:

位置信息

  • e.clientX:鼠标位置在视口中的水平坐标。
  • e.clientX:鼠标位置在视口中的水平坐标。
  • e.pageX:鼠标位置在文档中的水平坐标。如果页面向右滚动了,这个值大于e.clientX,否则相等。
  • e.pageY:鼠标位置在文档中的垂直坐标。如果页面向下滚动了,这个值大于e.clientY,否则相等。
  • e.screenX:鼠标位置在整个电脑屏幕中的水平坐标。
  • e.screenY:鼠标位置在整个电脑屏幕中的垂直坐标。

修改键

  • e.shiftKeyBoolean类型,表示鼠标被按下时,是否shift键也被按下了。
  • e.ctrlKeyBoolean类型,表示鼠标被按下时,是否ctrl键也被按下了。
  • e.altKeyBoolean类型,表示鼠标被按下时,是否alt键也被按下了。
  • e.metaKeyBoolean类型,表示鼠标被按下时,是否meta键也被按下了。(在Windows 键盘中是Windows 键,在苹果机中是Cmd 键)

相关元素

  • e.relatedTarget

在发生mouseovermouserout 事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指针从一个元素的边界之内移动到另一个元素的边界之内。对mouseover事件而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。类似地,对mouseout事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseovermouseout事件才包含值。在其他鼠标事件触发时,这个值是null

IE8及之前版本不支持relatedTarget属性,在mouseover 事件触发时,IE 的fromElement 属性中保存了相关元素;在mouseout 事件触发时,IE 的toElement 属性中保存着相关元素。

鼠标按钮

  • e.button

该属性可能有如下3 个值:0 表示主鼠标按钮(一般是左键),1 表示中间的鼠标按钮(鼠标滚轮按钮),2 表示次鼠标按钮(一般是右键)。在使用onmouseup事件处理程序时,button的值表示释放的是哪个按钮。

鼠标滚轮

onmousewheel事件这可以在任何元素上面触发,最终会冒泡到document,对应的event 对象除包含鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta 属性。

  • e.wheelDelta

当用户向前滚动鼠标滚轮时,wheelDelta120 的倍数;当用户向后滚动鼠标滚轮时,wheelDelta-120的倍数。通过检测wheelDelta的正负号就可以确定鼠标滚轮滚动的方向。

Opera 9.5 之前的版本中,wheelDelta 值的正负号是颠倒的(数值也是120)。Firefox 支持一个名为DOMMouseScroll的类似事件,信息则保存在detail属性中,正负号也与标准是颠倒的,当向前滚动鼠标滚轮时,这个属性的值是-3 的倍数,当向后滚动鼠标滚轮时,这个属性的值是3 的倍数。

触摸设备iOS 和Android

  • 不支持ondblclick事件。双击浏览器窗口会放大画面,而且没有办法改变该行为。
  • 轻击可单击元素会触发mousemove事件。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此变化,那么会依次发生mousedownmouseupclick 事件。轻击不可单击的元素不会触发任何鼠标事件。可单击的元素是指那些单击可产生默认操作的元素(如链接),或者那些已经被指定了onclick 事件处理程序的元素。
  • mousemove 事件也会触发mouseovermouseout 事件
  • 两个手指放在屏幕上且页面随手指移动而滚动时会触发mousewheelscroll 事件。
4.4 键盘与文本事件
  • onkeydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
  • onkeypress:除 Shift, Fn, CapsLock 外任意键被按住时(连续触发)。(MDN解释)
  • onkeyup:当用户释放键盘上的键时触发。

键码

  • e.keyCode

在IE中是e.whichkeyCode属性的值与ASCII码中对应小写字母或数字的编码相同。非字符串的常用键码如下:

键码 键码
退格(Backspace 8 制表(Tab 9
回车(Enter 13 上档(Shift 16
控制(Ctrl 17 Alt 18
暂停/中断(Pause/Break 19 大写锁定(Caps Lock 20
退出(Esc 27 上翻页(Page Up 33
下翻页(Page Down 34 左箭头(Left Arrow 37
上箭头(Up Arrow 38 右箭头(Right Arrow 39
下箭头(Down Arrow 40 插入(Ins 45
删除(Del 46 数字锁(Num Lock 144
滚动锁(Scroll Lock 145

DOM3 级事件还是做出了一些改变,新属性:keykey 属性是为了取代keyCode 而新增的,它的值是一个字符串。在按下某个字符键时,key的值就是相应的文本字符('a''A''1')。在按下非字符键时, key的值是相应键的名(如“Shift”“Down”)

textInput 事件

“DOM3 级事件”规范中引入了一个新事件textInput。根据规范,当用户在可编辑区域中输入字符时,就会触发这个事件。由于textInput事件主要考虑的是字符,因此它的event 对象中还包含一个data属性,这个属性的值就是用户输入的字符(而非字符编码)。event对象上还有一个属性,叫inputMethod,表示把文本输入到文本框中的方式。

  • 0,表示浏览器不确定是怎么输入的。
  • 1,表示是使用键盘输入的。
  • 2,表示文本是粘贴进来的。
  • 3,表示文本是拖放进来的。
  • 4,表示文本是使用IME 输入的。
  • 5,表示文本是通过在表单中选择某一项输入的。
  • 6,表示文本是通过手写输入的(比如使用手写笔)。
  • 7,表示文本是通过语音输入的。
  • 8,表示文本是通过几种方法组合输入的。
  • 9,表示文本是通过脚本输入的。
4.5 HTML5 事件
  • beforeunload事件

当浏览器窗口关闭或者刷新时,会触发beforeunload事件。当前页面不会直接关闭,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新。

会弹出这个对话框询问用户是否离开,为了显示弹出对话框,必须将event.returnValue的值设置为要显示给用户的字符串(对IE 及Fiefox 而言),同时作为函数的值返回(对Safari 和Chrome 而言)。

window.addEventListener('beforeunload',function(e){
    var e  = e || window.event;
    var message = "I'm really going to miss you if you go.";
    event.returnValue = message;
    return message
})
  • DOMContentLoaded事件

DOMContentLoaded 事件则在形成完整的DOM树之后就会触发,不理会图像、JavaScript 文件、CSS 文件或其他资源是否已经下载完毕。

  • readystatechange事件

支持readystatechange事件的每个对象都有一个readyState属性,可能包含下列5 个值中的一个。

  • uninitialized(未初始化):对象存在但尚未初始化。
  • loading(正在加载):对象正在加载数据。
  • loaded(加载完毕):对象加载数据完成。
  • interactive(交互):可以操作对象了,但还没有完全加载。
  • complete(完成):对象已经加载完毕。

如果某个阶段不适用某个对象,则该对象完全可能跳过该阶段;并没有规定哪个阶段适用于哪个对象。显然,这意味着readystatechange事件经常会少于4 次,而readyState属性的值也不总是连续的。

对于document而言,值为"interactive"readyState 会在与DOMContentLoaded大致相同的时刻触发readystatechange 事件。

<script>(在IEOpera 中)和<link>(仅IE 中)元素也会触发readystatechange事件,可以用来确定外部的JavaScriptCSS 文件是否已经加载完成。与在其他浏览器中一样,除非把动态创建的元素添加到页面中, 否则浏览器不会开始下载外部资源。基于元素触发的readystatechange 事件也存在同样的问题, 即readyState 属性无论等于"loaded" 还是"complete"都可以表示资源已经可用。

  • pageshowpagehide 事件

FirefoxOpera 有一个特性,名叫“往返缓存”(back-forward cache,或bfcache),可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了DOMJavaScript的状态;实际上是将整个页面都保存在了内存里。 如果页面位于bfcache 中,那么再次打开该页面时就不会触发load事件。尽管由于内存中保存了整个页面的状态,不触发load事件也不应该会导致什么问题,但为了更形象地说明bfcache的行为,Firefox 还是提供了一些新事件。

pageshow,这个事件在页面显示时触发,无论该页面是否来自bfcache。在重新加载的页面中,pageshow 会在load 事件触发后触发;而对于bfcache 中的页面,pageshow会在页面状态完全恢复的那一刻触发。pageshow 事件的event 对象还包含一个名为persisted 的布尔值属性。如果页面被保存在了bfcache 中,则这个属性的值为true;否则,这个属性的值为false

pagehide事件,该事件会在浏览器卸载页面的时候触发,而且是在unload 事件之前触发。与pageshow 事件一样,pagehidedocument上面触发,但其事件处理程序必须要添加到window对象。这个事件的event对象也包含persisted属性,不过其用途稍有不同。对于pagehide 事件,如果页面在卸载之后会被保存在bfcache 中,那么persisted的值会被设置为true

  • hashchange事件

URL的参数列表(及URL“#”号后面的所有字符串)发生变化时触发。必须要把hashchange事件处理程序添加给window 对象,然后URL 参数列表只要变化就会调用它。此时的event 对象应该额外包含两个属性:oldURLnewURL

4.6 设备事件
  • orientationchange事件

移动Safari 中添加了orientationchange事件,以便开发人员能够确定用户何时将设备由横向查看模式切换为纵向查看模式。

移动Safariwindow.orientation 属性中可能包含3 个值:0 表示肖像模式,90 表示向左旋转的横向模式(“主屏幕”按钮在右侧),-90 表示向右旋转的横向模式(“主屏幕”按钮在左侧)。

只要用户改变了设备的查看模式,就会触发orientationchange 事件。此时的event 对象不包含任何有价值的信息,因为唯一相关的信息可以通过window.orientation 访问到。

4.7 触摸与手势事件

移动Safari提供了一些与触摸(touch)操作相关的新事件。后来Android 上的浏览器也实现了相同的事件。

  • touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。
  • touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault()可以阻止滚动。
  • touchend:当手指从屏幕上移开时触发。

上面这几个事件都会冒泡,也都可以取消。每个触摸事件的event 对象都提供了在鼠标事件中常见的属性外还包含三个用于跟踪触摸的属性。

  • touches:表示当前跟踪的触摸操作的Touch 对象的数组。
  • targetTouchs:特定于事件目标的Touch 对象的数组。
  • changeTouches:表示自上次触摸以来发生了什么改变的Touch对象的数组。

每个Touch 对象包含下列属性。

  • clientX:触摸目标在视口中的x 坐标。
  • clientY:触摸目标在视口中的y 坐标。
  • identifier:标识触摸的唯一ID。
  • pageX:触摸目标在页面中的x 坐标。
  • pageY:触摸目标在页面中的y 坐标。
  • screenX:触摸目标在屏幕中的x 坐标。
  • screenY:触摸目标在屏幕中的y 坐标。
  • target:触摸的DOM 节点目标。
function handleTouchEvent(event){
    //只跟踪一次触摸
    if (event.touches.length == 1){
        var output = document.getElementById("output");
        switch(event.type){
            case "touchstart":
                output.innerHTML = "Touch started";
            break;
            case "touchend":
                output.innerHTML += "<br>Touch ended (" +
                event.changedTouches[0].clientX + "," +
                event.changedTouches[0].clientY + ")";
            break;
            case "touchmove":
                event.preventDefault(); //阻止滚动
                output.innerHTML += "<br>Touch moved (" +
                event.changedTouches[0].clientX + "," +
                event.changedTouches[0].clientY + ")";
            break;
        }
    }
}

以上代码会跟踪屏幕上发生的一次触摸操作。当touchmove 事件发生时,会取消其默认行为,阻止滚动(触摸移动的默认行为是滚动页面),在touchend 事件发生时,touches集合中就没有任何Touch 对象了,因为不存在活动的触摸操作;此时,就必须转而使用changeTouchs集合。

iOS 2.0 中的Safari 还引入了一组手势事件。当两个手指触摸屏幕时就会产生手势,手势通常会改变显示项的大小,或者旋转显示项。

  • gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。
  • gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。
  • gestureend:当任何一个手指从屏幕上面移开时触发。

只有两个手指都触摸到事件的接收容器时才会触发这些事件。在一个元素上设置事件处理程序,意味着两个手指必须同时位于该元素的范围之内,才能触发手势事件件冒泡,所以将事件处理程序放在文档上也可以处理所有手势事件。此时,事件的目标就是两个手指都位于其范围内的那个元素。

触摸事件和手势事件之间存在某种关系。当一个手指放在屏幕上时,会触发touchstart 事件。如果另一个手指又放在了屏幕上,则会先触发gesturestart 事件,随后触发基于该手指的touchstart事件。如果一个或两个手指在屏幕上滑动,将会触发gesturechange 事件。但只要有一个手指移开,就会触发gestureend 事件,紧接着又会触发基于该手指的touchend 事件。

每个手势事件的event对象都包含着标准的鼠标事件属性,也包含两个额外的属性。

  • rotation 属性表示手指变化引起的旋转角度,负值表示逆时针旋转,正值表示顺时针旋转(该值从0 开始)

  • scale属性表示两个手指间距离的变化情况(例如向内收缩会缩短距离);这个值从1 开始,并随距离拉大而增长,随距离缩短而减小。

触摸事件也会返回rotationscale 属性,但这两个属性只会在两个手指与屏幕保持接触时才会发生变化。

五、内存和性能

JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。事实上,从如何利用好事件处理程序的角度出发,还是有一些方法能够提升性能的。

事件委托

使用事件委托,只需在DOM 树中尽量最高的层次上添加一个事件处理程序。在通过e.target属性得到目标元素,然后做出相应的操作。

function(event){
    var target = event.target || event.srcElement;
    switch(target.id){
        case "doSomething":
            document.title = "I changed the document's title";
            break;
        case "goSomewhere":
            location.href = "http://www.wrox.com";
            break;
        case "sayHi":
            alert("hi");
            break;
    }
}

在这段代码里,我们使用事件委托只为<ul>元素添加了一个onclick 事件处理程序。也只取得了一个DOM 元素,对用户来说最终的结果相同,但这种技术需要占用的内存更少。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术。

移除事件处理程序

当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript 代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案

在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。这可能是通过纯粹的DOM操作,例如使用removeChild()replaceChild()方法,但更多地是发生在使用innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被innerHTML 删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。

如果你知道某个元素即将被移除,那么最好手工移除事件处理程序。例如:btn.onclick = null;removeEventListener()detachEvent()或者使用事件委托将事件注册在尽量高的层次的元素上。

注意,在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。

另一种情况,就是卸载页面的时候,如果在页面被卸载之前没有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序占用的内存并没有被释放。一般来说,最好的做法是在页面卸载之前,先通过onunload 事件处理程序移除所有事件处理程序。

使用onunload 事件处理程序意味着页面不会被缓存在bfcache 中。如果你在意这个问题,那么可以使用别的方式来移除事件。

六、模拟事件

事件经常由用户操作或通过其他浏览器功能来触发。但很少有人知道,也可以使用JavaScript 在任意时刻来触发特定的事件,而此时的事件就如同浏览器创建的事件一样。也就是说,这些事件该冒泡还会冒泡,而且照样能够导致浏览器执行已经指定的处理它们的事件处理程序。

DOM中的事件模拟
  • createEvent()dispatchEvent()

可以在document 对象上使用createEvent()方法创建event 对象。这个方法接收一个参数,即表示要创建的事件类型的字符串。在DOM2 级中,所有这些字符串都使用英文复数形式,而在DOM3级中都变成了单数。

  • UIEvents:一般化的UI 事件。鼠标事件和键盘事件都继承自UI 事件。
  • MouseEvents:一般化的鼠标事件。
  • MutationEvents:一般化的DOM 变动事件。
  • HTMLEvents:一般化的HTML 事件。没有对应的DOM3 级事件

模拟事件的最后一步就是触发事件。这一步需要使用dispatchEvent()方法,所有支持事件的DOM 节点都支持这个方法。调用dispatchEvent()方法时,需要传入一个参数,即表示要触发事件的event 对象。

模拟鼠标事件

创建鼠标事件对象的方法是为createEvent()传入字符串"MouseEvents"。返回的事件对象有一个名为initMouseEvent()方法,用于指定与该鼠标事件有关的信息。这个方法接收15个参数。

event.initMouseEvent(type, canBubble, cancelable, view,
    detail, screenX, screenY, clientX, clientY,
    ctrlKey, altKey, shiftKey, metaKey,
    button, relatedTarget);
var btn = document.getElementById("myBtn");
//创建事件对象
var event = document.createEvent("MouseEvents");
//初始化事件对象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0,false, false, false, false, 0, null);
//触发事件
btn.dispatchEvent(event);

模拟键盘事件

event.initKeyEvent (type, bubbles, cancelable, viewArg, 
    ctrlKeyArg, altKeyArg, shiftKeyArg, 
    metaKeyArg,keyCodeArg, charCodeArg) 
//只适用于Firefox
var textbox = document.getElementById("myTextbox")
//创建事件对象
var event = document.createEvent("KeyEvents");
//初始化事件对象
event.initKeyEvent("keypress", true, true, document.defaultView, false, false,false, false, 65, 65);
//触发事件
textbox.dispatchEvent(event);

自定义事件

event.initCustomEvent (type, bubbles, cancelable, detail) 
var div = document.getElementById("myDiv");
var event;
EventUtil.addHandler(div, "myevent", function(event){
    alert("DIV: " + event.detail);
});
EventUtil.addHandler(document, "myevent", function(event){
    alert("DOCUMENT: " + event.detail);
});
if (typeof document.createEvent == 'function'){
    event = document.createEvent("CustomEvent");
    event.initCustomEvent("myevent", true, false, "Hello world!");
    div.dispatchEvent(event);
}
IE中的事件模拟
  • document.createEventObject()fireEvent()
var btn = document.getElementById("myBtn");
//创建事件对象
var event = document.createEventObject();
//初始化事件对象
// 你必须手工为这个对象添加所有必要的信息(没有方法来辅助完成这一步骤)
event.screenX = 100;
event.screenY = 0;
event.clientX = 0;
event.clientY = 0;
event.ctrlKey = false;
event.altKey = false;
event.shiftKey = false;
event.button = 0;
//触发事件
btn.fireEvent("onclick", event);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容