1,事件冒泡,捕获,委托
早期的事件,是作为分担服务器运算负载的一种手段,实文档或者浏览器窗口中发生的一些特定的交互瞬间,如点击按钮,拖放文件等。我们可以使用侦听器来预定事件,当事件发布时候就可作出相应的响应,这种模式称为观察者模型。
事件流
事件流是从页面接收事件的顺序。在一个html页面中,dom元素组成一颗dom树,由于子元素一般所处的位置都会在父元素之中。那么,当这个子元素被点击时候,可以认为子元素受到了点击,也可以认为父元素受到点击。那么确定谁先接收这个点击事件就成了浏览器需要解决的问题。
事件冒泡
IE在处理上述事件时候,是由事件开始最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到根节点。这就是事件冒泡。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>test</title>
- </head>
- <body>
- <div id="container"></div>
- </body>
- </html>
在如上代码里面,如果单击了div元素,那么接着<body>、<html>、document会按着顺序收到点击事件。
事件捕获
Netscape Communicator团队提出了一种与IE完全相反的一种解决办法,接收事件的顺序为根节点到具体的节点,这种方法就是事件捕获。也就是在上面的这个例子中,接收点击事件的顺序为document、<html>、<body>、<div>。
DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,然后处于目标阶段,最后才事件冒泡。依然以上面的代码为例子,单击div时,首先document获得点击事件,依次到<html>、<body>,之后事件捕获结束。事件捕获的意义在于,能够在目标获得点击事件之前截获事件,并对其作出相应的处理。处于目标阶段时,div能够执行绑定的事件处理程序,之后再到事件冒泡阶段。即使“DOM2级事件”规范明确规定捕获阶段不会涉及事件目标,但是浏览器基本都会 实现在捕获阶段触发事件对象上的事件,也就是说,可以在事件捕获阶段触发事件对象的事件处理程序,这种情况通常都需要在绑定事件处理程序的情况下指明可在捕获阶段触发。如果非必须,最好不要使用事件捕获。
事件处理程序
HTML事件处理程序
dom元素一般都有支持事件的特性,例如onclick等,这个特性的值应该是能够执行的JavaScript代码,这些JavaScript代码就是其事件处理程序,例如:
- <input type="button" value="click me" onclick="alert('click')" />
也可以调用在页面其他地方定义的脚本:
- <input type="button" value="click me" onclick="handler()">
- <script>
- function handler() {
- alert('click');
- }
- </script>
在HTML页面中指定事件处理程序有三个缺点:存在时差,当脚本较绑定了事件dom元素慢,那么用户在脚本没有加载出来的过程就触发事件会导致报错;另一个缺点是这样扩展事件处理程序的作用域链在不同的浏览器会导致不同的结果,即this的指向不一致;最后,这种紧密耦合的代码风格导致维护的成本升高,在以后修改代码的时候需要修改HTML代码和js代码。
DOM0级事件处理程序
通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种指定事件处理程序具有简单,跨浏览器的优点,其事件处理程序会在事件流的冒泡阶段被触发:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>test</title>
- </head>
- <body>
- <input id="myButton" type="button" value="click me">
- <script>
- var button = document.getElementById('myButton');
- function handler() {
- alert('click');
- }
- button.onclick = handler;
- // 移除事件
- // button.onclick = null;
- </script>
- </body>
- </html>
注意,此种方式无法为同一个元素对同一事件指定多个事件处理程序,如果绑定多次,就会以最后一次为准。
DOM2级事件处理程序
DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。它们接受三个参数:处理的事件名称,事件处理程序,一个指定是在事件冒泡还是事件捕获阶段处理的布尔值,true则为在事件捕获阶段处理,false为在事件冒泡阶段处理。需要注意的是,通过addEventListener()指定的事件处理程序智能通过removeEventListener()移除;同时,如果在addEventListener()中的第二个参数是一个匿名函数,则无法通过removeEventListener()移除,也没有办法移除。而IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent(),这两个方法直接收两个参数,第一个为事件名称(注意事件名称都有on前缀,例如click事件则为onclick),第二个参数为事件处理程序函数。以下示例是兼容了浏览器的事件绑定函数:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>test</title>
- </head>
- <body>
- <input id="myButton" type="button" value="click me">
- <script>
- function 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;
- }
- }
- function 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;
- }
- }
- var button = document.getElementById('myButton');
- function handler() {
- alert('click');
- }
- addHandler(button, 'click', handler);
- // removeEventListener(button, 'click', handler);
- </script>
- </body>
- </html>
事件委托
事实上,操作系统给浏览器分配的资源会比其他桌面应用少,主要是怕黑客利用网页应用进行大量的计算,导致计算机的资源被过多占用而down机等其他原因。JavaScript影响网页应用性能有很多方面,如:每个函数都是对象,都会占用内存;内存中的对象越多,性能越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数越多,就会造成越长的页面交互就绪时间。所以多个事件处理程序过多就会降低事件的性能,解决这种问题的方案就是事件委托。事件委托利用事件冒泡,只指定一个事件处理程序,就可以管理某一类的所有事件。例如,可以在DOM树中尽可能高的层次添加一个事件处理程序,之后就可以操作事件目标元素:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>test</title>
- </head>
- <body>
- <ul id="myLinks">
- <li id="goSomewhere">Do something</li>
- <li id="doSomething">Do something</li>
- <li id="sayHi">Say Hi</li>
- </ul>
- <script>
- function 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;
- }
- }
- function 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;
- }
- }
- var list = document.getElementById('myLinks');
- function handler(event) {
- event = event ? event : window.event;
- var target = event.target || event.srcElement;
- switch(target.id) {
- case 'doSomething':
- alert('doSomething');
- break;
- case 'goSomewhere':
- alert('goSomewhere');
- break;
- case 'sayHi':
- alert('hi');
- break;
- }
- }
- addHandler(list, 'click', handler);
- // removeEventListener(list, 'click', handler);
- </script>
- </body>
- </html>
由上面的代码可看出,只获取了一个DOM元素,只添加了一个事件处理程序,所以能够减少内存的使用。但是,不建议对mouseover和mouseout使用事件代理,因为当鼠标从一个元素移到其子节点时,或者当鼠标移除该元素时,都会触发mouseout事件。
js实现继承
JS作为面向对象的语言,继承是其主要特性之一。那么如何在JS中实现继承呢?让我们拭目以待。
1.原型链法:拿父类实例来充当子类原型对象,非常简单的继承,易于实现,实例是子类的实例,也是父类的实例,父类增加新特性子类也能访问。
缺点:原型对象的引用属性是所有实例共享的,其中一个改变,其它都跟着改变。
2.构造函数继承:借父类的构造函数来增强子类实例,等于是把父类的实例属性复制了一份给子类实例化,解决了引用属性共享的问题。
缺点:无个法实现函数复用,每个子类实例都持有一新的fun函数,太多了就会影响性能,内存爆炸。
3.组合继承:把实例函数都放在原型对象上,以实现函数复用。同时还要保留借用构造函数方式的优点,通过call继承父类的基本属性和引用属性并保留能传参的优点;通过prototype继承父类函数,实现函数复用
缺点:子类原型上有一份多余的父类实例属性,因为父类构造函数被调用了两次,生成了两份,而子类实例上的那一份屏蔽了子类原型上的。
4.圣杯继承(最佳方式):可以避开3的缺点。
总结:只需要记住最后一种继承的写法即可。