diff --git "a/27 \350\265\265\346\265\251\346\225\217/\344\275\234\344\270\232/11.24\345\215\225\345\217\214\345\207\273\344\272\213\344\273\266.html" "b/27 \350\265\265\346\265\251\346\225\217/\344\275\234\344\270\232/11.24\345\215\225\345\217\214\345\207\273\344\272\213\344\273\266.html" new file mode 100644 index 0000000000000000000000000000000000000000..fa97506e3bfa27c6e5340718cbf4d83f5bb8d238 --- /dev/null +++ "b/27 \350\265\265\346\265\251\346\225\217/\344\275\234\344\270\232/11.24\345\215\225\345\217\214\345\207\273\344\272\213\344\273\266.html" @@ -0,0 +1,38 @@ + + + + + Title + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/27 \350\265\265\346\265\251\346\225\217/\347\254\224\350\256\260/11.24 js\347\232\204\344\272\213\344\273\266\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/27 \350\265\265\346\265\251\346\225\217/\347\254\224\350\256\260/11.24 js\347\232\204\344\272\213\344\273\266\345\255\246\344\271\240\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..3c6f44eab706621cd104107c4ee44c9cba11de27 --- /dev/null +++ "b/27 \350\265\265\346\265\251\346\225\217/\347\254\224\350\256\260/11.24 js\347\232\204\344\272\213\344\273\266\345\255\246\344\271\240\347\254\224\350\256\260.md" @@ -0,0 +1,565 @@ +# [js的事件学习笔记](https://www.cnblogs.com/zzzlw/p/9693527.html) + + + +目录 + +- [0、参考](https://www.cnblogs.com/zzzlw/p/9693527.html#0参考) +- 1、事件流 + - [冒泡传播](https://www.cnblogs.com/zzzlw/p/9693527.html#冒泡传播) + - [事件捕获](https://www.cnblogs.com/zzzlw/p/9693527.html#事件捕获) +- 2、事件绑定--onclick接口 + - [onclick类的接口,只能注册一个同类事件](https://www.cnblogs.com/zzzlw/p/9693527.html#onclick类的接口只能注册一个同类事件) + - [onclick类的接口,使用button.onclick = null的方式注销事件](https://www.cnblogs.com/zzzlw/p/9693527.html#onclick类的接口使用buttononclick--null的方式注销事件) +- 3、事件绑定--addEventListener接口 + - [addEventListener接口,可以注册多个同类事件,发生时,依次执行回调函数](https://www.cnblogs.com/zzzlw/p/9693527.html#addeventlistener接口可以注册多个同类事件发生时依次执行回调函数) + - [addEventListener接口无法注销以匿名函数注册的事件](https://www.cnblogs.com/zzzlw/p/9693527.html#addeventlistener接口无法注销以匿名函数注册的事件) +- [4、事件对象](https://www.cnblogs.com/zzzlw/p/9693527.html#4事件对象) +- [5、阻止冒泡传播和阻止默认行为](https://www.cnblogs.com/zzzlw/p/9693527.html#5阻止冒泡传播和阻止默认行为) +- [6、事件带来的问题](https://www.cnblogs.com/zzzlw/p/9693527.html#6事件带来的问题) +- [7、事件委托(代理)](https://www.cnblogs.com/zzzlw/p/9693527.html#7事件委托代理) +- [8、模拟事件触发](https://www.cnblogs.com/zzzlw/p/9693527.html#8模拟事件触发) +- [9、总结](https://www.cnblogs.com/zzzlw/p/9693527.html#9总结) + + + +## 0、参考 + +JavaScript高级程序设计(第三版),第13章 + +## 1、事件流 + +`js`的事件流分为捕获和冒泡两类,目前主流的方式是使用冒泡,在特殊情况下才会启用捕获(比如这种需求:一个`div`中有多个子元素,希望可以用鼠标在页面上拖动整个`div`而不触发子元素的事件,就可以用事件捕获) + +### 冒泡传播 + +考虑如下的`DOM`结构,外面有3层`div`盒子,最内部有一个`button`按钮 + +```xml +
+
+
+ +
+
+
+``` + +为`button`和`div`分别注册事件,查看当点击按钮时会发生的执行结果 + +```javascript +var box1 = document.getElementsByClassName('box1')[0]; +var box2 = document.getElementsByClassName('box2')[0]; +var box3 = document.getElementsByClassName('box3')[0]; +var btn = document.getElementsByClassName('btn')[0]; + +btn.onclick = function () { + console.log('btn被触发') +}; + +box3.onclick = function () { + console.log('box3被触发') +}; + +box2.onclick = function () { + console.log('box2被触发') +}; + +box1.onclick = function () { + console.log('box1被触发') +}; +``` + +执行结果: + +```mipsasm +btn被触发 m5-js.js:27:5 +box3被触发 m5-js.js:32:5 +box2被触发 m5-js.js:37:5 +box1被触发 m5-js.js:41:5 +``` + +### 事件捕获 + +依然考虑如下的`DOM`结构 + +```xml +
+
+
+ +
+
+
+``` + +使用`addEventListener`接口可以指定捕获或者冒泡阶段 + +```javascript +var box1 = document.getElementsByClassName('box1')[0]; +var box2 = document.getElementsByClassName('box2')[0]; +var box3 = document.getElementsByClassName('box3')[0]; +var btn = document.getElementsByClassName('btn')[0]; + +box1.addEventListener('click', function (evt) { + console.log('捕获阶段--box1被触发'); +}, true); + +box2.addEventListener('click', function (evt) { + console.log('捕获阶段--box2被触发'); +}, true); + +box3.addEventListener('click', function (evt) { + console.log('捕获阶段--box3被触发'); +}, true); + +btn.addEventListener('click', function (evt) { + console.log('捕获阶段--btn被触发'); +}, true); + +btn.addEventListener('click', function (evt) { + console.log('冒泡阶段--btn被触发'); +}, false); + +box1.addEventListener('click', function (evt) { + console.log('冒泡阶段--box1被触发'); +}, false); + +box2.addEventListener('click', function (evt) { + console.log('冒泡阶段--box2被触发'); +}, false); + +box3.addEventListener('click', function (evt) { + console.log('冒泡阶段--box3被触发'); +}, false); +``` + +结果: + +```mipsasm +捕获阶段--box1被触发 m5-js.js:27:5 +捕获阶段--box2被触发 m5-js.js:31:5 +捕获阶段--box3被触发 m5-js.js:35:5 +捕获阶段--btn被触发 m5-js.js:39:5 +冒泡阶段--btn被触发 m5-js.js:43:5 +冒泡阶段--box3被触发 m5-js.js:55:5 +冒泡阶段--box2被触发 m5-js.js:51:5 +冒泡阶段--box1被触发 m5-js.js:47:5 +``` + +------ + +## 2、事件绑定--onclick接口 + +事件绑定有两个主要的接口,第一种接口: +`button.onclick = fn` +`DOM 0`级,此类注册事件只会出现在冒泡阶段。 +这种方式将事件触发接口`(onclick)`作为元素的一个属性,值即为事件触发后执行的回调函数`(fn)`。因为一个元素的一个属性只能指向一个值,故此类绑定方式中,元素只能注册一个同类的事件,旧的注册事件会被新的注册事件所覆盖,事件注销时,使用`button.onclick = null`。 + +### onclick类的接口,只能注册一个同类事件 + +考虑下面的`DOM`结构 + +```xml + +``` + +在按钮`button`上先后绑定两个同类事件,后绑定的事件会覆盖前绑定的事件 + +```javascript +var btn = document.getElementsByClassName('btn')[0]; + + +btn.onclick = function (event) { + console.log('hello word') +}; + +btn.onclick = function () { + console.log('你好世界') +}; +``` + +执行结果 + +```x86asm +你好世界 m5-js.js:32:5 +``` + +### onclick类的接口,使用button.onclick = null的方式注销事件 + +依然考虑如下的`DOM`结构 + +```xml + +``` + +在`button`上绑定一个点击事件,然后启动一个定时器,在`4s`之后注销此事件 + +```javascript +var btn = document.getElementsByClassName('btn')[0]; + +setTimeout(function () { + btn.onclick = null; +}, 2000); + +btn.onclick = function (event) { + console.log('hello word'); +}; +``` + +执行结果 +在网页加载完毕后的`2s`内,每次点击`button`按钮都会打印`hello world`,但是`2s`之后点击就不再有反应。 + +```x86asm +hello word m5-js.js:31:5 +``` + +------ + +## 3、事件绑定--addEventListener接口 + +事件绑定的第二种接口: +`button.addEventListener('click', fn, false)` +`DOM 2`级,此类注册事件可以自选出现在捕获或者冒泡阶段。 +这种事件触发接口`(addEventListener)`作为元素的一个事件注册函数,参数是事件类型、回调函数、布尔值(`true`为选择捕获/`false`为选择冒泡,默认为`false`)。此注册函数实现了类似'回调函数队列'的效果,一个元素可以注册多个同类的事件,当事件发生时,多个回调函数会依次执行。 +事件注销时,使用`button.removeEventListner('click', fn, false)`,参数的值必须和注册时指向的对象一致。 + +*注意:使用`removeEventListener`接口方式,无法处理匿名回调函数的事件注销。(因为匿名函数一旦创建,后续就无法获取对它的引用)* + +### addEventListener接口,可以注册多个同类事件,发生时,依次执行回调函数 + +考虑如下`DOM`结构 + +```xml + +``` + +为button注册多个同类click事件,这些回调函数不会覆盖,而是会依次执行 + +```javascript +var btn = document.getElementsByClassName('btn')[0]; + +btn.addEventListener('click', function (evt) { + console.log('hello func - 1'); +}, false); + +btn.addEventListener('click', function (evt) { + console.log('hello func - 2'); +}, false); + +btn.addEventListener('click', function (evt) { + console.log('hello func - 3'); +}, false); + +btn.addEventListener('click', function (evt) { + console.log('hello func - 4'); +}, false); +``` + +执行结果 + +```x86asm +hello func - 1 m5-js.js:27:5 +hello func - 2 m5-js.js:32:5 +hello func - 3 m5-js.js:36:5 +hello func - 4 m5-js.js:40:5 +``` + +### addEventListener接口无法注销以匿名函数注册的事件 + +考虑如下`DOM`结构 + +```xml + +``` + +button按钮注册了两个`click`事件,其中一个指向有名回调函数`sayHello`,一个指向匿名函数。`2s`后尝试注销这两个事件,最终发现,有名函数的事件可以被注销,匿名函数的事件无法注销。 + +```javascript +var btn = document.getElementsByClassName('btn')[0]; + +function sayHello() { + console.log('hello world') +} + +// button注册了两个click事件,其中一个使用有名回调函数,一个使用匿名回调函数 +btn.addEventListener('click', sayHello, false); +btn.addEventListener('click', function (evt) { + console.log('你好世界'); +}); + +// 2s后尝试注销button的click事件 +setTimeout(function () { + btn.removeEventListener('click', sayHello, false); + btn.removeEventListener('click', function (evt) { + console.log('你好世界'); + }); +}, 2000); +``` + +------ + +## 4、事件对象 + +每一次预定事件发生时,在回调函数中都可以直接引用`event`对象,此对象代表了事件对象,包含事件发生的一些必要信息。事件对象中有两个关于目标的属性,`event.currentTarget`代表着当前回调函数所属的对象,`event.target`代表着触发事件的源对象。 + +考虑如下`DOM`结构 + +```xml + +``` + +为`button`按钮绑定一个事件,在回调函数中可以直接调用`event`对象 + +```javascript +var btn = document.getElementsByClassName('btn')[0]; + +btn.onclick = function (event) { + console.log(event.currentTarget === this); + console.log(event.target === btn); +}; +``` + +执行结果 + +```x86asm +true m5-js.js:27:5 +true m5-js.js:28:5 +``` + +`this`和`event.currentTarget`总是同样的引用,代表的是回调函数所属的对象。 +`event.target`可以获取发生事件的源头对象。 + +------ + +## 5、阻止冒泡传播和阻止默认行为 + +**冒泡类事件流,默认情况下会将事件传播至所有父级元素,但在某些时候我们需要主动停止冒泡事件流的传播。** +考虑如下`DOM`结构 + +```xml +
+ +
+``` + +为`button`和`box1`均注册点击事件,默认情况下,`button`的点击事件也会触发`box1`的点击行为。但如果在`button`的回调函数中执行`event.stopPropagation()`就可以阻止冒泡事件的向上传播。 + +```javascript +var box1 = document.getElementsByClassName('box1')[0]; +var btn = document.getElementsByClassName('btn')[0]; + +box1.onclick = function () { + console.log('box1发生了click事件'); +}; + +btn.onclick = function (event) { + console.log('btn发生了click事件'); + event.stopPropagation(); +}; +``` + +执行结果 + +```x86asm +btn发生了click事件 m5-js.js:30:5 +``` + +**某些`html`元素含有默认的事件行为,某些时候我们也需要主动停止默认行为的发生。** +考虑如下`DOM`结构 + +```xml +
+ 点击去往百度 +
+``` + +当点击`a`元素的时候,会执行默认行为,即前往指定的`herf`地址。但是如果使用`event.preventDefault()`即可阻止默认行为的发生。如下执行结果并不会跳转到指定`href`页面。 + +```javascript +var a = document.getElementsByClassName('link')[0]; + +a.onclick = function (event) { + console.log('连接a发生了点击事件,取消默认行为'); + event.preventDefault(); +}; +``` + +执行结果 + +```x86asm +连接a发生了点击事件,取消默认行为 m5-js.js:36:5 +``` + +*有时候,我们希望既可以阻止冒泡事件的传播,同时也可以阻止默认行为,那么就可以使用`return false`。* + +------ + +## 6、事件带来的问题 + +第一个问题在于页面上会出现过多数量的事件,这些事件的回调函数都是需要占用内存,事件越多占用的内存也越大。某些事件有重复的情况,比如多个同类的`li`标签可能绑定着同样的事件回调函数,这种事件触发的方式效比较低,还可以进一步优化,以上问题可以使用事件委托(代理)。 + +第二个问题在于如何妥善的处理事件的注销。比如元素在发生`innerHTML`修改或者被`remove`节点时,对应的回调函数也会失去引用,这些失去引用的事件可能无法被垃圾回收机制正确回收。所以在处理一个含有事件的元素时,应特别注意事件的注销操作,使用`onclick = null`或者使用`removeEventListener`。 + +------ + +## 7、事件委托(代理) + +事件委托技术依赖于事件流冒泡传播。同类的子元素的事件发生,不应该在子元素上注册事件,而应该在共同的父元素上注册事件,由父元素来处理子元素的事件发生,此父元素即为子元素的事件委托者或者代理者。 + +考虑如下`DOM`结构 + +```xml + +``` + +使用事件代理,只需要在父元素上绑定事件,所有子类元素的事件均会通过冒泡的形式传播到父元素的回调函数中处理。 + +```javascript +var itemList = document.getElementsByClassName('item-list')[0]; + +itemList.onclick = function (event) { + // 利用冒泡原理来实现事件代理 + var origin = event.target; + var originText = origin.innerText; + + console.log('发生点击事件的元素是:', originText); +}; +``` + +事件委托技术减少了多个同类子元素的重复事件注册,减少了内存的开销,事件处理的效率也更高。 +此外,事件委托还有一个好处是不用关心新增子元素的事件注册操作,新增子元素只需要加入父元素即可,不必再次执行事件注册。 + +*父元素也可以通过`switch`判断子元素的方式来对不同的子元素执行相应的处理函数。* +考虑如下的`DOM`结构 + +```xml +
+ +
  • 选项2
  • + 选项3 +
    +``` + +父元素执行事件代理,同时通过`switch`判断子元素的`tagName`,针对不同的标签执行不同的事件处理。 + +```javascript +var itemList = document.getElementsByClassName('item-list')[0]; + +itemList.onclick = function (event) { + // 利用冒泡原理来实现事件代理 + var origin = event.target; + + switch (origin.tagName) { + case 'BUTTON': + console.log('点击的是一个按钮'); + break; + case 'LI': + console.log('点击的是一个列表项目'); + break; + case 'A': + console.log('点击的是一个链接'); + break; + } +}; +``` + +执行结果 + +```x86asm +点击的是一个按钮 m5-js.js:26:13 +点击的是一个列表项目 m5-js.js:29:13 +点击的是一个链接 m5-js.js:32:13 +``` + +------ + +## 8、模拟事件触发 + +在`jQuery`中,提供了`trigger`接口作为模拟事件的触发,此接口让我们非常方便的触发指定元素的指定事件。 + +考虑如下`DOM`结构 + +```bash + + +``` + +为两个`button`都注册对应的事件,在`button1`的回调函数中,执行`button2`的`trigger`函数,触发`button2`的点击事件。点击`button1`后,即使在没有点击`button2`的情况下,也会执行`button2`的回调函数。 + +```php +var $btn1 = $('#btn1'); +var $btn2 = $('#btn2'); + +$btn1.bind('click', function () { + console.log('btn1被点击'); + $btn2.trigger('click'); +}); + +$btn2.bind('click', function () { + console.log('btn2被点击'); +}); +``` + +执行结果(只点击了`button1`) + +```x86asm +btn1被点击 m5-js.js:21:5 +btn2被点击 m5-js.js:26:5 +``` + +**在原生JS中,需要使用`createEvent`接口和`dispatchEvent`接口实现同样的效果。** +考虑如下`DOM`结构 + +```bash + + +``` + +点击`button1`的时候,触发`button2`的`click`事件 + +```javascript +var btn1 = document.getElementById('btn1'); +var btn2 = document.getElementById('btn2'); + +var event = document.createEvent('MouseEvents'); +event.initMouseEvent('click', true, true, document.defaultView, 0, 0, 0, 0, 0, + false, false, false, false, 0, null); + +btn1.onclick = function () { + console.log('btn1被点击'); + btn2.dispatchEvent(event); +}; + +btn2.onclick = function () { + console.log('btn2被点击'); +}; +``` + +执行结果(只点击了`button1`) + +```x86asm +btn1被点击 m5-js.js:24:5 +btn2被点击 m5-js.js:29:5 +``` + +------ + +## 9、总结 + +事件是`js`中非常重要的模块,它完成了`js`代码和`html`代码之间的互动,通过事件可以提供丰富的高体验性用户交互,可以说通过`js`实现的用户交互就是以事件作为驱动的。`js`的事件模型为:目标元素+事件对象+回调函数。 \ No newline at end of file