JavaScript高级程序设计读书笔记13

第13章 事件

  • 事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间

事件流

  • 事件流:从页面中接收事件的顺序
  • IE的事件流是冒泡流
  • Netscape的事件流是捕获流

事件冒泡

  • 事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

事件捕获

  • 事件捕获:不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。
  • 用意在于在事件到达预定目标之前捕获他。
  • 使用事件冒泡,在有特殊需要时再使用事件捕获。

DOM事件流

  • “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。

事件处理程序

  • 事件处理程序:响应某个事件的函数

HTML事件处理程序

  • 不能在其中使用未经转义的HTML语法字符
  • event变量,可以直接访问事件对象
  • this值等于事件的目标对象
  • 在函数内部,可以像访问局部变量一样访问document及该元素本身的成员
  • 存在的缺点:1.存在时差问题 2.扩展事件处理程序的作用域链在不同浏览器中会导致不同的结果 3.HTML和JS代码紧密耦合

DOM0级事件处理程序

  • 将一个函数赋值给一个事件处理程序属性
  • 优势:1.简单 2.跨浏览器
  • 要使用JS指定时间处理程序,首先必须取得一个操作的对象的引用
  • 使用DOM0级方法指定的时间处理程序被认为是元素的方法,因此是在元素的作用域中运行,程序中的this引用当前元素。
  • 会在事件流的冒泡阶段被处理。
    1
    2
    3
    4
    5
    var btn = document.getElementById("myBtn"); 
    btn.onclick = function() {
    alert(this.id); // "myBtn"
    };
    btn.onclick = null; // 删除事件处理程序

DOM2级事件处理程序

  • 指定事件处理程序:addEventListener()
  • 删除事件处理程序:removeEventListener()
    • 三个参数:要处理的事件名、作为事件处理程序的函数、布尔值(true 表示捕获阶段调用事件处理程序,false 表示冒泡阶段调用事件处理程序
  • 添加多个事件处理程序时,会按照添加他们的顺序触发
  • 移除时传入的参数与添加处理程序时使用的参数相同,使用匿名函数则无法移除。
  • 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器
  • 最好只需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段
    1
    2
    3
    4
    5
    6
    var btn = document.getElementById("myBtn");
    var handler = function() {
    alert(this.id);
    };
    btn.addEventListener("click", handler, false);
    btn.removeEventListener("click", handler, false); // 删除事件处理程序

IE事件处理程序

  • attachEvent()和detachEvent()
  • 两个参数:事件处理程序名称、事件处理程序函数
  • attachEvent()添加的事件处理程序都会被添加到冒泡阶段
  • addEvenet()的第一个参数是onclick,addEventListener()方法中的第一个参数是click
  • 在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域:DOM0级中是在其所属元素的作用域中运行,在attachEvent方法中则是在全局作用域中运行。
  • 添加多个事件处理程序时,会按照添加他们的顺序的相反顺序触发
  • 移除时传入的参数与添加处理程序时使用的参数相同,使用匿名函数则无法移除。

跨浏览器的事件处理程序

  • EventUtil对象:用于处理浏览器间的差异
  • addHandler():视情况分别使用 DOM0 级方法、DOM2 级方法或 IE 方法来添加事件
  • removeHandler():移除添加的事件处理程序
  • 要操作的元素、事件名称、事件处理程序函数
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 定义EventUtil对象
    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对象
    var btn = document.getElementById("myBtn");
    var handler = function() {
    alert("Clicked");
    }

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

    EventUtil对象方法定义及用法

事件对象

  • 事件对象event,包含着所有与事件有关的信息。

DOM中的事件对象

  • currentTarget:事件处理程序当前正在处理事件的那个元素。target:事件的目标
  • 在事件处理程序内部,对象this始终等于currentTarget的值,而target则值包含事件的实际目标。
  • 如果直接将事件处理程序制定给了目标元素,则this、currentTarget、target包含相同的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var btn=document.getElementById("myBtn");
    btn.onclick=function(event){
    alert(event.currentTarget); //[object HTMLInputElement]
    alert(this); //[object HTMLInputElement]
    alert(event.target); //[object HTMLInputElement]
    alert(event.currentTarget===this); //true
    alert(event.target===this); //true
    }


    document.body.onclick=function(event){
    alert(event.currentTarget); //[object HTMLBodyElement]
    alert(this); //[object HTMLBodyElement]
    alert(event.target); //[object HTMLInputElement]
    }
  • type属性:需要通过一个函数处理多个事件时

  • preventDefault方法:阻止特定事件的默认行为
  • stopPropagation():用于立即停止事件在DOM层次中的传播

    1
    2
    3
    4
    5
    6
    7
    8
    var btn=document.getElementById("myBtn");
    btn.onclick=function(event){
    alert("ckicked");
    event.stopPropagation();
    }
    document.body.onclick=function(event){
    alert("Body clicked"); //不会执行
    }
  • eventPhase等于2时,this、target、currentTarget始终都是相等的

  • 只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完成,event对象就会被销毁

IE中的时间对象

  • 在使用DOM0级添加事件处理程序时,event对象作为window对象的一个属性存在window.event
  • 在用attachEvent()添加时,会有一个event对象作为参数被传入事件处理程序函数中,也可以作为window对象的一个属性访问window.event
  • 在通过HTML特性指定的事件处理程序中,通过一个名叫event的变量来访问event对象

跨浏览器的事件对象

  • 定义EventUtil对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // 定义EventUtil对象
    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;
    }
    },
    //返回对 event 对象的引用
    getEvent: function(event) {
    return event ? event : window.event;
    },
    //返回事件的目标
    getTarget: function(event) {
    return event.targete || event.srcElement;
    },
    //用于取消事件的默认行为
    preventDefault: function(event) {
    if (event.preventDefault) {
    event.preventDefault();
    } else {
    event.retrunValue = false;
    }
    },
    //删除事件处理程序
    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;
    }
    },
    //阻止事件流
    stopPropagation: function(event) {
    if (event.stopPropagation) {
    event.stopPropagation();
    } else {
    event.cancleBubble = true;
    }
    }
    }
  • 使用EventUtil对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 使用EventUtil对象
    var btn = document.getElementById("myBtn");
    var handler = function() {
    alert("Clicked");
    }

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

    btn.onclick = function(event) {
    event.EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    EventUtil.preventDefault(event);
    EventUtil.stopPropagation(event);
    }

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

事件类型

UI事件

  • UI事件:不一定与用户操作有关的事件

load事件

  • 当页面完全加载后(包括所有图像,JS文件,CSS文件等外部资源),就会触发window上面的load事件。
  • 有两种定义onload事件处理程序的方式:1.使用JS代码 2.为body元素添加一个onload特性。尽量使用JS代码
  • 图像、<script><link>元素也支持load事件

unload事件

  • 在文档被完全卸载后触发,只要用户从一个页面切换到另一个页面就会发生unload事件
  • 利用unload事件多用于清除引用,避免内存泄漏
  • 有两种定义onunload事件处理程序的方式:1.使用JS代码 2.为body元素添加一个onunload特性。尽量使用JS代码

resize事件

  • 当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件
  • 有两种定义resize事件处理程序的方式:1.使用JS代码 2.为body元素添加一个onresize特性。尽量使用JS代码
  • 不同的浏览器有不同的触发resize事件机制

scroll事件

  • 虽然是在window对象上发生的,但是实际表示的则是页面中相应元素的变化。
  • 在混杂模式下,通过<body>元素的scrollLeft和scrollTop监控这一变化,在标准模式下,除Safari之外的所有浏览器都会通过<html>元素访问

焦点事件

  • 在页面元素获得或失去焦点时触发
  • focus和blur是主要方法,但是它们都不冒泡

鼠标和滚轮事件

  • click:用户单机主鼠标按钮或者按下回车键触发
  • dblclick:用户双击主鼠标按钮时触发
  • mousedown:在用户按下了任意鼠标按钮时触发。
  • mouseenter:在鼠标光标从元素外部首次移动到了元素范围内触发。
  • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。
  • mousemove:当鼠标指针在元素内部移动时重复地触发。
  • mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。
  • mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内触发
  • mouseup:在用户释放鼠标按钮时触发
  • 除了mouseenter和mouseleave,所有鼠标事件都会冒泡

客户区坐标位置

  • 事件发生时鼠标指针在视口中的水平和垂直坐标
    1
    2
    3
    4
    5
    var div = document.getElementById("myBtn");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Client coordinates: " + event.clientX + "," + event.clientY);
    });

页面坐标位置

  • 事件发生时鼠标指针在页面中的位置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    var pageX = event.pageX,
    pageY = event.pageY;
    if (pageX === undefined){
    pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
    }
    if (pageY === undefined){
    pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
    }
    alert("Page coordinates: " + event.pageX + "|" + pageX + "," + event.pageY + "|"+pageY);
    });

屏幕坐标位置

  • 事件发生时鼠标指针在电脑屏幕中的位置
    1
    2
    3
    4
    5
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Screen coordinates: " + event.screenX + "," + event.screenY);
    });

修改键

  • 按下鼠标时键盘上的某些键的状态也可以影响到所有采取的操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    var keys = new Array();
    if (event.shiftKey){
    keys.push("shift");
    }
    if (event.ctrlKey){
    keys.push("ctrl");
    }
    if (event.altKey){
    keys.push("alt");
    }
    if (event.metaKey){
    keys.push("meta");
    }
    //将检测到的键的信息显示给用户
    alert("Keys: " + keys.join(","));
    });

相关元素

  • 对于mouseover事件,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。
  • 对于mouseout事件,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。
  • 跨浏览器取得相关元素,方法添加到EventUtil中
    1
    <div id="myDiv" style="background-color:red;height:100px;width:100px">Move the mouse from here to the white</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getRelatedTarget: function(event){
if (event.relatedTarget){
return event.relatedTarget;
} else if (event.toElement){
return event.toElement;
} else if (event.fromElement){
return event.fromElement;
} else {
return null;
}
}

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mouseout", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
alert("Moused out of " + target.tagName + " to " + relatedTarget.tagName);
});

鼠标按钮

  • 只有在主鼠标按钮被单击(或键盘回车键被按下)时才会触发click事件,因此检测按钮的信息并不是必要的
  • DOM的button属性可能有如下三个值:0表示主鼠标按钮,1表示中间的按钮,2表示次鼠标按钮。
  • IE8及之前版本也提供了button属性,但属性的值与DOM的button属性有很大差异。
  • 跨浏览器地确定Button值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    getButton: function(event){
    if (document.implementation.hasFeature("MouseEvents", "2.0")){
    return event.button;
    } else {
    switch(event.button){
    case 0://没有按下按钮
    case 1://主按钮
    case 3://同时按下主、次按钮
    case 5://同时按下主鼠标和中间的鼠标按钮
    case 7://同时按下三个按钮
    return 0;
    case 2://按下次鼠标按钮
    case 6://同时按下次鼠标和中间鼠标按钮
    return 2;
    case 4: return 1;//按下了中间鼠标按钮
    }//以上代码表示把主按钮优先级调到最高,次按钮其次,中间按钮最后
    }
    },

更多的事件信息

  • event对象中还提供了detail属性,用于给出有关事件的更多信息
  • 在同一个元素上相继发生一次mousedown和mouseup事件算作一次单击。
  • 如果鼠标在mousedown和mouseup之间移动了位置,则detail会被重置为0。

鼠标滚轮事件

  • mousewheel事件:用户通过鼠标滚轮与页面交互、在垂直方向上滚动页面时会触发
  • 这个事件可以在任何元素上面触发,最终会冒泡到document或window对象

触摸设备

  • 不支持dblclick事件
  • 轻击可单击元素会触发mousemove事件。
  • mousemove事件也会触发mouseover和mouseout事件。
  • 两个手指放在频幕上且页面随手指滚动而滚动时会触发mousewheel和scroll事件

键盘与文本事件

  • 对键盘事件的支持主要遵循的是DOM0级
  • 用户按了一下键盘上的字符键时,首先会触发keydown事件,然后紧跟着是keypress事件,最后会触发keyup事件。如果用户按着字符键不放,会重复触发keydown和keypress事件,直到用户松开该键为止。
  • 如果按下的是非字符键,首先会触发keydown事件,然后是keyup事件。如果按住这个非字符键不放,就会一直触发keydown事件直到用户松开这个键。

键码

  • 在发生keydown或keyup事件时,event对象的keyCode属性会包含一个代码,与键盘上一个特定的键对应。
  • keyCode对应键码,其中数字字母字符键的keycode属性的值与ASCII码中对应小写字母或数字的编码相同

字符编码

  • 发生keypress事件意味着按下的键会影响到屏幕中文本的显示
  • charCode属性只有在发生keypress事件时才包含值,是按下的那个键所代表字符的ASCII编码。

DOM3级变化

  • key和char属性
  • keyIdentifier 返回U+0000这类字符串
  • location属性,0表示默认键盘,1表示左侧键盘,2表示右侧位置,3表示数字小键盘,4表示移动设备键盘,5表示手柄。在IE9中支持。Safari和Chrome中支持名为keyLocation的等价属性。
  • getModifierState()方法:检测修改值

textInput事件

  • textInput事件:当用户在可编辑区域中输入字符时,就会触发这个事件。
  • 这个用于替代keypress的textInput事件的行为稍有不同。
  • 区别之一就是任何可以获得焦点的元素都可以出发keypress事件,但只有可编辑区域才能出发textInput事件。
  • 区别之二是textInput事件只会在用户按下能够输入实际字符的键时才会被触发,而keypress事件则是那些能影响文本显示的键时也会触发。
  • textInput事件主要考虑的是字符,它的event对象中还包含一个data属性,这个属性的值就是用户输入的字符(而非ASCII值)。
  • event对象上还有一个属性,叫inputMethod,表示把文本输入到文本框中的方式。

复合事件

  • 是DOM3级事件中新添加的一类事件,用于处理IME的输入序列。
  • IME:输入法编辑器,可以让用户输入在物理键盘上找不到的字符

变动事件

  • DOM2级变动事件能在DOM中的某一部分发生变化时给出提示

删除节点

  • 在使用removeChild()或replaceChild()从DOM中删除节点时触发的事件顺序如下:
  • 1.DOMNodeRemoved事件,event.target是被删除的节点,relateNode是目标节点的父节点。冒泡
  • 2.目标节点的子节点触发DOMNodeRemovedDocument事件,不冒泡
  • 3.在父节点上触发DOMSubtreeModified事件

插入节点

  • 在使用appendChild()、replaceChild()、insertBefore()从DOM中插入节点时触发的事件顺序如下:
  • 1.DOMNodeInserted事件,event.target是被插入的节点,relateNode是目标节点的父节点。冒泡
  • 2.目标节点的子节点触发DOMNodeInsertedDocument事件,不冒泡
  • 3.在父节点上触发DOMSubtreeModified事件

HTML5事件

  • DOM规范没有涵盖所有浏览器支持的所有事件,HTML详尽列出了浏览器应该支持的所有事件
  • contextmenu:用以表示何时应该显示上下文菜单,以便开发人员取消默认的上下文菜单,而提供自定义的菜单
  • beforeunload:在浏览器卸载页面之前触发,通过它来取消写在并继续使用原有的网页
  • DOMContentLoaded:在形成完整的DOM树之后就会触发,不理会图像、JS文件、CSS文件或其他资源是否已经加载完毕。始终在load事件之前触发
  • readystatechange:提供与文档或元素的加载状态有关的信息,但这个事件的行为有时候也很难预料。
  • pageshow:在页面显示时触发。新加载的页面,在load事件触发后触发,来自bfcache(往返缓存)的页面,页面状态完全恢复后触发。必须将事件处理程序添加到window
  • pagehide:在浏览器卸载页面时触发。新加载的页面,在unload事件触发后触发,页面状态完必须将事件处理程序添加到window
  • hashchange:在URL参数列表发生变化时通知开发人员

设备事件

  • oritentationonchange:用户查看设备的模式从横向切换为纵向或纵向切换为横向
  • MozOritentation:当设备的加速计检测到设备的方向改变时,就会触发这个事件,告诉开发人员设备在如何移动
  • deviceorientation:加速计检测到设备方向变化时再window对象上触发,告诉开发人员设备在空间中朝哪儿
  • devicemotion:告诉开发人员设备什么时候移动

触摸和手势事件

触摸事件

  • touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。
  • touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault()可以阻止滚动。
  • touchend:当手指从屏幕上移开时触发。
  • touchcancel:当系统停止跟踪触摸时触发。关于此事件的确切触发时间,文档中没有明确说明。
  • 除了常见的DOM属性外,触摸事件还包含下列三个用于跟踪触摸的属性。
  • touches:表示当前跟踪的触摸操作的Touch对象的数组。
  • targetTouches:特定于事件目标的Touch对象的数组。
  • changeTouches:表示紫上次触摸以来发生了什么改变的Touch对象的数组。

手势事件

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

内存和性能

  • 存在的问题:在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须实现指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交换就绪时间。

事件委托

  • 对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
  • 与传统做法相比具有的优势:

    1.document对象很快就可以访问到,而且可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待DOMContentLoaded或load事件)。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能。
    2.在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少。
    3.整个页面占用的内存空间更少,能够提升整体性能。

  • 最适合采用事件委托技术的事件包括:click、mousedown、mouseup、keydown、keyup和keypress。

移除事件处理程序

  • 内存中留有那些过时不用的“空事件处理程序”,也是造成Web应用程序内存与性能问题的主要原因。
  • 两种情况造成“空事件处理程序”:

    1.从文档中移除带有事件处理程序的元素时,例如使用removeChild()和replaceChild()方法,但更多地是发生在使用innerHTML替换页面中某一部分的时候。
    解决方法:(1)手工移除事件处理程序 (2)事件委托,把事件处理程序指定给最高层次的元素,同样能够处理该区域中的事件。
    2.卸载页面中的时候,如果在页面被卸载之前没有清理干净事件处理程序。那它们就会滞留在内存中。
    解决方法:通过onunload事件处理程序移除所有事件处理程序

模拟事件

DOM中的事件模拟

  • 可以在document对象上使用createEvent()方法创建event对象。
  • 在创建了event对象之后,还需要使用与事件有关的信息对其进行初始化。每种类型的event对象都有一个特殊的方法,为它传入适当的数据就可以初始化该event对象。
  • 模拟事件的最后一步就是要触发事件。这一步需要使用dispatchEvent()方法,所有支持事件的DOM节点都支持这个方法。

IE中的事件模拟

  • document.createEventObject()方法可以在IE中创建event对象。但与DOM方式不同的是,这个方法不接受参数,结果会返回一个通用的event对象。
  • 然后,你必须手工为这个对象添加所有必要的信息(没有方法来辅助完成这一步骤)。
  • 最后一步就是在目标上调用fireEvent()方法,这个方法接受两个参数:事件处理程序的名称和event对象。在调用fireEvent()方法时,会自动为event对象添加srcElement和type属性;其他属性则都是必须通过手工添加的。