|
| 1 | +# Event 对象 |
| 2 | + |
| 3 | +## 概述 |
| 4 | + |
| 5 | +事件发生以后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供一个`Event`对象,所有的事件都是这个对象的实例,或者说继承了`Event.prototype`对象。 |
| 6 | + |
| 7 | +`Event`对象本身就是一个构造函数,可以用来生成新的实例。 |
| 8 | + |
| 9 | +```javascript |
| 10 | +event = new Event(type, options); |
| 11 | +``` |
| 12 | + |
| 13 | +`Event`构造函数接受两个参数。第一个参数`type`是字符串,表示事件的名称;第二个参数`options`是一个对象,表示事件对象的配置。该对象主要有下面两个属性。 |
| 14 | + |
| 15 | +- `bubbles`:布尔值,可选,默认为`false`,表示事件对象是否冒泡。 |
| 16 | +- `cancelable`:布尔值,可选,默认为`false`,表示事件是否可以被取消,即能否用`Event.preventDefault()`取消这个事件。一旦事件被取消,就好像从来没有发生过,不会触发浏览器对该事件的默认行为。 |
| 17 | + |
| 18 | +```javascript |
| 19 | +var ev = new Event( |
| 20 | + 'look', |
| 21 | + { |
| 22 | + 'bubbles': true, |
| 23 | + 'cancelable': false |
| 24 | + } |
| 25 | +); |
| 26 | +document.dispatchEvent(ev); |
| 27 | +``` |
| 28 | + |
| 29 | +上面代码新建一个`look`事件实例,然后使用`dispatchEvent`方法触发该事件。 |
| 30 | + |
| 31 | +注意,如果不是显式指定`bubbles`属性为`true`,生成的事件就只能在“捕获阶段”触发监听函数。 |
| 32 | + |
| 33 | +```javascript |
| 34 | +// HTML 代码为 |
| 35 | +// <div><p>Hello</p></div> |
| 36 | +var div = document.querySelector('div'); |
| 37 | +var p = document.querySelector('p'); |
| 38 | + |
| 39 | +function callback(event) { |
| 40 | + var tag = event.currentTarget.tagName; |
| 41 | + console.log('Tag: ' + tag); // 没有任何输出 |
| 42 | +} |
| 43 | + |
| 44 | +div.addEventListener('click', callback, false); |
| 45 | + |
| 46 | +var click = new Event('click'); |
| 47 | +p.dispatchEvent(click); |
| 48 | +``` |
| 49 | + |
| 50 | +上面代码中,`p`元素发出一个`click`事件,该事件默认不会冒泡。`div.addEventListener`方法指定在冒泡阶段监听,因此监听函数不会触发。如果写成`div.addEventListener('click', callback, true)`,那么在“捕获阶段”可以监听到这个事件。 |
| 51 | + |
| 52 | +另一方面,如果这个事件在`div`元素上触发。 |
| 53 | + |
| 54 | +```javascript |
| 55 | +div.dispatchEvent(click); |
| 56 | +``` |
| 57 | + |
| 58 | +那么,不管`div`元素是在冒泡阶段监听,还是在捕获阶段监听,都会触发监听函数。因为这时`div`元素是事件的目标,不存在是否冒泡的问题,`div`元素总是会接收到事件,因此导致监听函数生效。 |
| 59 | + |
| 60 | +## 实例属性 |
| 61 | + |
| 62 | +### Event.bubbles,Event.eventPhase |
| 63 | + |
| 64 | +`Event.bubbles`属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,一般用来了解 Event 实例是否可以冒泡。前面说过,除非显式声明,`Event`构造函数生成的事件,默认是不冒泡的。 |
| 65 | + |
| 66 | +`Event.eventPhase`属性返回一个整数常量,表示事件目前所处的阶段。该属性只读。 |
| 67 | + |
| 68 | +```javascript |
| 69 | +var phase = event.eventPhase; |
| 70 | +``` |
| 71 | + |
| 72 | +`Event.eventPhase`的返回值有四种可能。 |
| 73 | + |
| 74 | +- 0,事件目前没有发生。 |
| 75 | +- 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。 |
| 76 | +- 2,事件到达目标节点,即`Event.target`属性指向的那个节点。 |
| 77 | +- 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。 |
| 78 | + |
| 79 | +### Event.cancelable,Event.cancelBubble,event.defaultPrevented |
| 80 | + |
| 81 | +`Event.cancelable`属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,一般用来了解 Event 实例的特性。 |
| 82 | + |
| 83 | +大多数浏览器的原生事件是可以取消的。比如,取消`click`事件,点击链接将无效。但是除非显式声明,`Event`构造函数生成的事件,默认是不可以取消的。 |
| 84 | + |
| 85 | +```javascript |
| 86 | +var evt = new Event('foo'); |
| 87 | +evt.cancelable // false |
| 88 | +``` |
| 89 | + |
| 90 | +当`Event.cancelable`属性为`true`时,调用`Event.preventDefault()`就可以取消这个事件,阻止浏览器对该事件的默认行为。 |
| 91 | + |
| 92 | +如果事件不能取消,调用`Event.preventDefault()`会没有任何效果。所以使用这个方法之前,最好用`Event.cancelable`属性判断一下是否可以取消。 |
| 93 | + |
| 94 | +```javascript |
| 95 | +function preventEvent(event) { |
| 96 | + if (event.cancelable) { |
| 97 | + event.preventDefault(); |
| 98 | + } else { |
| 99 | + console.warn('This event couldn\'t be canceled.'); |
| 100 | + console.dir(event); |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +`Event.cancelBubble`属性是一个布尔值,如果设为`true`,相当于执行`Event.stopPropagation()`,可以阻止事件的传播。 |
| 106 | + |
| 107 | +`Event.defaultPrevented`属性返回一个布尔值,表示该事件是否调用过`Event.preventDefault`方法。该属性只读。 |
| 108 | + |
| 109 | +```javascript |
| 110 | +if (event.defaultPrevented) { |
| 111 | + console.log('该事件已经取消了'); |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +### Event.currentTarget,Event.target |
| 116 | + |
| 117 | +`Event.currentTarget`属性返回事件当前所在的节点,即正在执行的监听函数所绑定的那个节点。 |
| 118 | + |
| 119 | +`Event.target`属性返回原始触发事件的那个节点,即事件最初发生的节点。事件传播过程中,不同节点的监听函数内部的`Event.target`与`Event.currentTarget`属性的值是不一样的,前者总是不变的,后者则是指向监听函数所在的那个节点对象。 |
| 120 | + |
| 121 | +```javascript |
| 122 | +// HTML代码为 |
| 123 | +// <p id="para">Hello <em>World</em></p> |
| 124 | +function hide(e) { |
| 125 | + console.log(this === e.currentTarget); // 总是 true |
| 126 | + console.log(this === e.target); // 有可能不是 true |
| 127 | + e.target.style.visibility = 'hidden'; |
| 128 | +} |
| 129 | + |
| 130 | +para.addEventListener('click', hide, false); |
| 131 | +``` |
| 132 | + |
| 133 | +上面代码中,如果在`para`节点的`<em>`子节点上面点击,则`e.target`指向`<em>`子节点,导致`<em>`子节点(即 World 部分)会不可见。如果点击 Hello 部分,则整个`para`都将不可见。 |
| 134 | + |
| 135 | +### Event.type |
| 136 | + |
| 137 | +`Event.type`属性返回一个字符串,表示事件类型。事件的类型是在生成事件的时候。该属性只读。 |
| 138 | + |
| 139 | +```javascript |
| 140 | +var evt = new Event('foo'); |
| 141 | +evt.type // "foo" |
| 142 | +``` |
| 143 | + |
| 144 | +### Event.timeStamp |
| 145 | + |
| 146 | +`Event.timeStamp`属性返回一个毫秒时间戳,表示事件发生的时间。它是相对于网页加载成功开始计算的。 |
| 147 | + |
| 148 | +```javascript |
| 149 | +var evt = new Event('foo'); |
| 150 | +evt.timeStamp // 3683.6999999995896 |
| 151 | +``` |
| 152 | + |
| 153 | +它的返回值有可能是整数,也有可能是小数(高精度时间戳),取决于浏览器的设置。 |
| 154 | + |
| 155 | +下面是一个计算鼠标移动速度的例子,显示每秒移动的像素数量。 |
| 156 | + |
| 157 | +```javascript |
| 158 | +var previousX; |
| 159 | +var previousY; |
| 160 | +var previousT; |
| 161 | + |
| 162 | +window.addEventListener('mousemove', function(event) { |
| 163 | + if ( |
| 164 | + previousX !== undefined && |
| 165 | + previousY !== undefined && |
| 166 | + previousT !== undefined |
| 167 | + ) { |
| 168 | + var deltaX = event.screenX - previousX; |
| 169 | + var deltaY = event.screenY - previousY; |
| 170 | + var deltaD = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); |
| 171 | + |
| 172 | + var deltaT = event.timeStamp - previousT; |
| 173 | + console.log(deltaD / deltaT * 1000); |
| 174 | + } |
| 175 | + |
| 176 | + previousX = event.screenX; |
| 177 | + previousY = event.screenY; |
| 178 | + previousT = event.timeStamp; |
| 179 | +}); |
| 180 | +``` |
| 181 | + |
| 182 | +### Event.isTrusted |
| 183 | + |
| 184 | +`Event.isTrusted`属性返回一个布尔值,表示该事件是否由真实的用户行为产生。比如,用户点击链接会产生一个`click`事件,该事件是用户产生的;`Event`构造函数生成的事件,则是脚本产生的。 |
| 185 | + |
| 186 | +```javascript |
| 187 | +var evt = new Event('foo'); |
| 188 | +evt.isTrusted // false |
| 189 | +``` |
| 190 | + |
| 191 | +上面代码中,`evt`对象是脚本产生的,所以`isTrusted`属性返回`false`。 |
| 192 | + |
| 193 | +## 实例方法 |
| 194 | + |
| 195 | +### Event.preventDefault() |
| 196 | + |
| 197 | +`Event.preventDefault`方法取消浏览器对当前事件的默认行为。比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了;再比如,按一下空格键,页面向下滚动一段距离,使用这个方法以后也不会滚动了。该方法生效的前提是,事件对象的`cancelable`属性为`true`,如果为`false`,调用该方法没有任何效果。 |
| 198 | + |
| 199 | +注意,该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。如果要阻止传播,可以使用`stopPropagation()`或`stopImmediatePropagation()`方法。 |
| 200 | + |
| 201 | +```javascript |
| 202 | +// HTML 代码为 |
| 203 | +// <input type="checkbox" id="my-checkbox" /> |
| 204 | +var cb = document.getElementById('my-checkbox'); |
| 205 | + |
| 206 | +cb.addEventListener( |
| 207 | + 'click', |
| 208 | + function (e){ e.preventDefault(); }, |
| 209 | + false |
| 210 | +); |
| 211 | +``` |
| 212 | + |
| 213 | +上面代码中,浏览器的默认行为是单击会选中单选框,取消这个行为,就导致无法选中单选框。 |
| 214 | + |
| 215 | +利用这个方法,可以为文本输入框设置校验条件。如果用户的输入不符合条件,就无法将字符输入文本框。 |
| 216 | + |
| 217 | +```javascript |
| 218 | +// HTML 代码为 |
| 219 | +// <input type="text" id="my-input" /> |
| 220 | +var input = document.getElementById('my-input'); |
| 221 | +input.addEventListener('keypress', checkName, false); |
| 222 | + |
| 223 | +function checkName(e) { |
| 224 | + if (e.charCode < 97 || e.charCode > 122) { |
| 225 | + e.preventDefault(); |
| 226 | + } |
| 227 | +} |
| 228 | +``` |
| 229 | + |
| 230 | +上面代码为文本框的`keypress`事件设定监听函数后,将只能输入小写字母,否则输入事件的默认行为(写入文本框)将被取消,导致不能向文本框输入内容。 |
| 231 | + |
| 232 | +### Event.stopPropagation() |
| 233 | + |
| 234 | +`stopPropagation`方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。 |
| 235 | + |
| 236 | +```javascript |
| 237 | +function stopEvent(e) { |
| 238 | + e.stopPropagation(); |
| 239 | +} |
| 240 | + |
| 241 | +el.addEventListener('click', stopEvent, false); |
| 242 | +``` |
| 243 | + |
| 244 | +上面代码中,`click`事件将不会进一步冒泡到`el`节点的父节点。 |
| 245 | + |
| 246 | +### Event.stopImmediatePropagation() |
| 247 | + |
| 248 | +`Event.stopImmediatePropagation`方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比`Event.stopPropagation()`更彻底。 |
| 249 | + |
| 250 | +如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了`Event.stopImmediatePropagation`方法,其他的监听函数就不会再执行了。 |
| 251 | + |
| 252 | +```javascript |
| 253 | +function l1(e){ |
| 254 | + e.stopImmediatePropagation(); |
| 255 | +} |
| 256 | + |
| 257 | +function l2(e){ |
| 258 | + console.log('hello world'); |
| 259 | +} |
| 260 | + |
| 261 | +el.addEventListener('click', l1, false); |
| 262 | +el.addEventListener('click', l2, false); |
| 263 | +``` |
| 264 | + |
| 265 | +上面代码在`el`节点上,为`click`事件添加了两个监听函数`l1`和`l2`。由于`l1`调用了`event.stopImmediatePropagation`方法,所以`l2`不会被调用。 |
| 266 | + |
| 267 | +### Event.composedPath() |
| 268 | + |
| 269 | +`Event.composedPath()`返回一个数组,成员是事件的最底层节点和依次冒泡经过的所有上层节点。 |
| 270 | + |
| 271 | +```javascript |
| 272 | +// HTML 代码如下 |
| 273 | +// <div> |
| 274 | +// <p>Hello</p> |
| 275 | +// </div> |
| 276 | +var div = document.querySelector('div'); |
| 277 | +var p = document.querySelector('p'); |
| 278 | + |
| 279 | +div.addEventListener('click', function (e) { |
| 280 | + console.log(e.composedPath()); |
| 281 | +}, false); |
| 282 | +// [p, div, body, html, document, Window] |
| 283 | +``` |
| 284 | + |
| 285 | +上面代码中,`click`事件的最底层节点是`p`,向上依次是`div`、`body`、`html`、`document`、`Window`。 |
| 286 | + |
0 commit comments