事件流分析

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

早些时的IE和网景却提出了差不多是两种完全相反的事件流概念。
IE提出的是冒泡流,而网景提出的是捕获流,后来在W3C组织的统一之下,JS支持了冒泡流和捕获流,但是目前低版本的IE浏览器还是只能支持冒泡流(IE6,IE7,IE8均只支持冒泡流),所以为了能够兼容更多的浏览器,建议大家使用冒泡流

JS事件流原理图如下:

DOM规范在推进过程中,对事件流的支持如下:

DOM Level 捕获流 冒泡流
DOM Level 0 不支持 支持
DOM Level 2 支持 支持
DOM Level 3 支持 支持
IE 不支持 支持

事件处理程序

HTML 事件处理程序

1
<input type="button" value="点击我" onclick="showMessage()">
1
2
3
function showMessage() {
alert('hello word!');
}

这种方式是我们刚学习js时用的方式,因为和html代码紧密耦合等缺点,目前已经极少使用了。

DOM Level 0 级事件处理程序

1
<input type="button" id="btn" value="点击我">
1
2
3
4
5
6
7
var btn = document.getElementById('btn');
btn.onclick = function() {
alert(this.value);
}

// 这样可以删除事件处理程序
btn.onclick = null;
  • 这种方式不支持捕获流,在冒泡阶段被处理;
  • 同一个事件类型不能绑定多个事件处理程序;

DOM Level 2 级事件处理程序

1
<input type="button" id="btn" value="点击我">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var btn = document.getElementById('btn');

/**
* 参数说明
* 1、 要处理的事件名
* 2、作为事件处理程序的函数
* 3、一个布尔值--true: 表示在捕获阶段调用事件处理程序; false: 表示在冒泡阶段调用事件处理程序; 默认是false
*/
btn.addEventListener('click', function() {
alert(this.id);
}, false);

btn.addEventListener('click', function() {
alert(this.type);
}, false);

// 删除事件处理程序
var handler = function() {
alert(this.id);
};
btn.addEventListener('click', handler);
// 注意这里传入的参数必须和绑定时的一模一样哦
btn.removeEventListener('click', handler);
  • 这种方式支持捕获流、冒泡流;
  • 同一个事件类型可以绑定多个事件处理程序,会按照添加的顺序执行;

DOM Level 3 级事件处理程序

暂无

IE事件处理程序

1
2
3
4
5
6
7
8
9
10
11
var btn = document.getElementById('btn');

// 注意下this哦
btn.attachEvent('onclick', function() {
alert(this === window);
})

// 删除的时候和removeEventListener注意点是一样的
btn.detachEvent('onclick', function() {
alert(this === window);
})
  • 同一个事件类型可以绑定多个事件处理程序,会按照添加的顺序倒序执行;

跨浏览器事件处理程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var EventUtil = {
addHandler(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(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;
}
}
};

事件对象



在触发DOM上的某个事件时,会产生一个事件对象 event ,这个对象中包含着所有与事件相关的信息。

  • 只在事件处理程序执行期间,event才存在

DOM中的事件对象

常用属性 常用方法
type (click…) stopPropagation
eventPhase (1:代表在捕获阶段调用的事件处理程序; 2:代表在目标对象上调用; 3:代表在冒泡阶段调用) preventDefault()
target
currentTarget (=== this)

IE中的事件对象

常用属性 常用方法
tsrcElement (等价于 target) cancelBubble (等价于 stopPropagation)
type returnValue (等价于 preventDefault)

访问IE中event对象的方式,取决于指定事件处理程序的方式

1
2
3
4
5
6
7
8
9
10
11
var btn = document.getElementById('btn');

// DOM0级添加事件处理程序-event是作为window对象的属性存在的
btn.onclick = function() {
var event = window.event;
}

// IE事件处理程序-event作为参数传入的
btn.attachEvent('onclick', function(event) {

})

跨浏览器的事件对象

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
var EventUtil = {
addHandler(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(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;
}
},
getEvent(event) {
return event ? event : window.event;
},
getTarget(event) {
return event.target || event.srcElement;
},
preventDefault(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
stopPropagation(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};

事件类型

UI事件

  • load:
    • 页面加载完成在window上面触发(包括所有图片、js css文件加载完成);
    • 图片加载完成时在img上触发;需要注意:图片加载是异步。为了防止意外,最好把绑定事件处理程序写在src上面
    • ie9+等浏览器在script 或者link加载完成也会触发load;
  • unload:

    • 文档被卸载或者切到另一页面时触发;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 曾经这样子测试过,结果貌似没有触发unload
      window.onunload=function(){
      alert('onunload');
      }

      // 注意哦,上面不是window.onunload事件不执行。而是某些浏览器规定当触发window.onunload事件之后,alert()/confim()/prompt()等模态对话框不再允许弹出
      // 换种测试方式
      window.onunload=function(){
      localStorage.setItem('onunload_text', 'onunload');
      }
  • error:

    • 当发生js错误时在window上触发;
    • 当无法加载图像时在img上触发(在图片加载失败的时候做一些操作类似的需求会用到这个事件);
  • select:
  • resize:
    • 当浏览器窗口被调整到一个新的高度或者宽度时在 window上触发;
    • 窗口最大化最小化时也会触发哦;
    • 触发这个事件,每个浏览器处理机制不一样,有的是窗口变化就触发有的是直到窗口不再变化了才触发;
  • scroll:
    • 在window上触发;
    • 混杂模式:
      • document.body.scrollLeft
      • document.body.scrollTop
    • 标准模式:
      • document.documentElement.scrollLeft
      • document.documentElement.scrollTop

焦点事件

注意这两个事件都不冒泡,所有浏览器都支持

  • blur
  • focus

鼠标滚轮事件

  • click:

    • 触发click之前,事件的触发顺序如下(如果某个事件处理程序去除了,就跳过继续该执行的):
      • mousedown
      • mouseup
      • click
  • dbclick:

    • 触发dbclick之前,事件的触发顺序如下:
      • mousedown
      • mouseup
      • click
      • mousedown
      • mouseup
      • click
  • mouseup

  • mousemove

  • mouseover

  • mouseout

  • mouseenter

    • 不冒泡哦
  • mouseleave

    • 不冒泡哦
  • mousewheel

    • 此处理程序中的event对象,有个属性值得看下:event.wheelDelta – 用来判断页面滚动方向(正数:用户向前滚动鼠标滚轮;负数:用户向后滚动鼠标滚轮);
  • 该注意的event属性:

属性 备注
clientX, clientY 鼠标指针在视口中的坐标位置
pageX, pageY 鼠标在页面中的坐标位置,ie8及一下不支持该属性,可以通过scrollTop之类的来计算出来
screenX, screenY 鼠标在屏幕坐标位置
修改键 shiftKey、altKey、ctrlKey、metaKey - 触发事件的时候按下,则返回true; 否则返回false

键盘与文本事件

  • keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
  • keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。
  • keyup:当用户释放键盘上的键时触发。
  • textinput:textInput是对keypress的补充,用意是在将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发textInput事件。
  • input:该事件在 input 或 textarea 元素的值发生改变时触发

当用户按下一个键盘上的字符键时,首先会触发keydown事件,然后紧跟着是keypress事件,最后会触发keyup事件。
keydown和kepress都是在文本框发生变化之前触发的,keyup事件则是在文本框已经发生变化之后触发的。
如果用户按下一个字符键不放,就会重复触发keydown和keypress事件,直到用户松开该键为止。
如果用户按下的是一个非字符键,那么首先会触发keydown事件,然后就是keyup事件。如果按住这个非字符键不放,那么就会一直重复触发keydown事件,直到用户松开这个键,此时会触发keyup事件。

属性 备注
keyCode 表示按下按键的数字代码。
charCode 按下按键的Unicode字符。

HTML5事件

  • contextMenu:鼠标右击或者移动端上长按,会触发这个事件并出现一个功能菜单;
  • DOMContentLoaded:
    • 形成完整DOM树之后触发
    • IE9+ 支持
    • 可以用setTimout模拟

内存与性能

事件委托

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

为什么要用事件委托

一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?

在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;

每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了(内存不够用,是硬伤,哈哈),比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,那只能说呵呵了,如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好。

例子

1
2
3
4
5
6
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
1
2
3
4
5
6
7
8
9
window.onload = function(){
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
for(var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(123);
}
}
}