JS-Web-API-事件(必考)
1. 题目
- 编写一个通用的事件监听函数
- 描述事件冒泡的流程
- 无线下拉的图片列表,如何监听每个图片的点击
2. 知识点
2.1. 事件绑定
- 代码示例html
<div id="div1" class="container"> <button id="btn1">事件绑定示例1</button> </div> <script> const btn = document.getElementById('btn1') btn.addEventListener('click', event=> { console.log('clicked'); }) </script>
<div id="div1" class="container"> <button id="btn1">事件绑定示例1</button> </div> <script> const btn = document.getElementById('btn1') btn.addEventListener('click', event=> { console.log('clicked'); }) </script>
- 写一个通用的绑定函数 - 简易版html
<div id="div1" class="container"> <button id="btn1">事件绑定示例1</button> </div> <a id="link1" href="https://www.baidu.com/">事件绑定示例2</a> <script> // 通用的绑定函数 - 简易版 let bindEvent = (elem, type, fn)=> { elem.addEventListener(type, fn) } // 按钮 const btn = document.getElementById('btn1') // 函数他会返回一个参数值,即event对象 bindEvent(btn, 'click', event=> { // event参数的用法 console.log(event.target); // 触发点击的元素 - 作代理的时候会有用 event.preventDefault(); // 阻止默认行为 alert('clicked'); }) // a标签 const a = document.getElementById('link1') bindEvent(a, 'click', event=> { event.preventDefault(); // 在这里会阻止a标签的跳转 console.log("link"); }) </script>
<div id="div1" class="container"> <button id="btn1">事件绑定示例1</button> </div> <a id="link1" href="https://www.baidu.com/">事件绑定示例2</a> <script> // 通用的绑定函数 - 简易版 let bindEvent = (elem, type, fn)=> { elem.addEventListener(type, fn) } // 按钮 const btn = document.getElementById('btn1') // 函数他会返回一个参数值,即event对象 bindEvent(btn, 'click', event=> { // event参数的用法 console.log(event.target); // 触发点击的元素 - 作代理的时候会有用 event.preventDefault(); // 阻止默认行为 alert('clicked'); }) // a标签 const a = document.getElementById('link1') bindEvent(a, 'click', event=> { event.preventDefault(); // 在这里会阻止a标签的跳转 console.log("link"); }) </script>
2.2. 事件冒泡
- 代码示例html
<div id="div1"> <p id="p1">激活</p> <p id="p2">取消</p> <p id="p3">取消</p> <p id="p4">取消</p> </div> <div id="div2"> <p id="p5">取消</p> <p id="p6">取消</p> </div> <script> // 通用的绑定函数 - 简易版 let bindEvent = (elem, type, fn)=> { elem.addEventListener(type, fn) } // 事件冒泡 // 示例一 - 理解冒泡 // 在body上监听事件 const body = document.body bindEvent(body, 'click', event => { console.log('取消') console.log('body clicked') // 每点击一下,事件都会一层一层往上冒泡,冒到body上去 console.log(event.target) // 需要知道是谁触发的 }) // 事件可以冒泡div2上吗 // 点击div2的时候,div2的事件先执行,再执行body里面的事件 // 因为他是一层一层地往上冒泡,div2在body里面 // 点击p5的时候,他是先冒泡到div2,再冒泡到body // 点击div1下的p标签,它只会冒泡到body // 因为div1下的p标签没有被监听 const div2 = document.getElementById('div2') bindEvent(div2, 'click', event=> { console.log("div2 clicked"); console.log(event.target); }) // 示例二 - p1 // 点击p1激活,点击取消,就都是取消状态 // 没必要给每个p标签都做绑定(即获取所有p标签id) const p1 = document.getElementById('p1') // p bindEvent(p1, 'click', event => { // 根据冒泡机制,点击激活p标签,也会冒泡到body,显示取消 // 这里使用阻止冒泡,即只触发点击id为p1的p标签的事件 event.stopPropagation() // 阻止冒泡 console.log('激活') }) </script>
<div id="div1"> <p id="p1">激活</p> <p id="p2">取消</p> <p id="p3">取消</p> <p id="p4">取消</p> </div> <div id="div2"> <p id="p5">取消</p> <p id="p6">取消</p> </div> <script> // 通用的绑定函数 - 简易版 let bindEvent = (elem, type, fn)=> { elem.addEventListener(type, fn) } // 事件冒泡 // 示例一 - 理解冒泡 // 在body上监听事件 const body = document.body bindEvent(body, 'click', event => { console.log('取消') console.log('body clicked') // 每点击一下,事件都会一层一层往上冒泡,冒到body上去 console.log(event.target) // 需要知道是谁触发的 }) // 事件可以冒泡div2上吗 // 点击div2的时候,div2的事件先执行,再执行body里面的事件 // 因为他是一层一层地往上冒泡,div2在body里面 // 点击p5的时候,他是先冒泡到div2,再冒泡到body // 点击div1下的p标签,它只会冒泡到body // 因为div1下的p标签没有被监听 const div2 = document.getElementById('div2') bindEvent(div2, 'click', event=> { console.log("div2 clicked"); console.log(event.target); }) // 示例二 - p1 // 点击p1激活,点击取消,就都是取消状态 // 没必要给每个p标签都做绑定(即获取所有p标签id) const p1 = document.getElementById('p1') // p bindEvent(p1, 'click', event => { // 根据冒泡机制,点击激活p标签,也会冒泡到body,显示取消 // 这里使用阻止冒泡,即只触发点击id为p1的p标签的事件 event.stopPropagation() // 阻止冒泡 console.log('激活') }) </script>
2.3. 事件代理
- 事件代理是在事件冒泡的机制上做的
- 只有实现了事件冒泡,才可以做事件代理
- 概念
- 当dom数量较多或结果比较复杂,不能给每个dom绑定事件的时候,我们把事件绑定到它的父元素上
- 在这个父元素上根据event.target获取触发的元素,然后判断是否为我们需要的元素,之后再去做其他的动作
- 这就是事件代理
- 代码示例html
<!-- 如果要给每个a标签监听事件 不可能给每个a标签都设定一个id去监听 这时候可以把监听放在div1上 通过冒泡机制去监听所有a标签去响应事件 这就是事件代理 --> <div id="div1"> <a href="#">a1</a><br> <a href="#">a2</a><br> <a href="#">a3</a><br> <a href="#">a4</a><br> <button>加载更多...</button> </div> <script> // 通用的绑定函数 - 简易版 let bindEvent = (elem, type, fn)=> { elem.addEventListener(type, fn) } // 事件代理示例 const div1 = document.getElementById('div1'); bindEvent(div1, 'click', event=> { // 每次点击div1下的标签,都会冒泡到div1上来 // 点击button的时候也会冒泡到div1上来 // a标签还有一个默认行为就是点击可能会跳转链接 event.preventDefault() // 阻止默认行为 let target = event.target // 判断当前触发元素 console.log("当前触发元素", target, target.nodeName, target.textContent); if(target.nodeName === 'A') { alert(target.innerHTML) } }) </script>
<!-- 如果要给每个a标签监听事件 不可能给每个a标签都设定一个id去监听 这时候可以把监听放在div1上 通过冒泡机制去监听所有a标签去响应事件 这就是事件代理 --> <div id="div1"> <a href="#">a1</a><br> <a href="#">a2</a><br> <a href="#">a3</a><br> <a href="#">a4</a><br> <button>加载更多...</button> </div> <script> // 通用的绑定函数 - 简易版 let bindEvent = (elem, type, fn)=> { elem.addEventListener(type, fn) } // 事件代理示例 const div1 = document.getElementById('div1'); bindEvent(div1, 'click', event=> { // 每次点击div1下的标签,都会冒泡到div1上来 // 点击button的时候也会冒泡到div1上来 // a标签还有一个默认行为就是点击可能会跳转链接 event.preventDefault() // 阻止默认行为 let target = event.target // 判断当前触发元素 console.log("当前触发元素", target, target.nodeName, target.textContent); if(target.nodeName === 'A') { alert(target.innerHTML) } }) </script>
- 事件代理的意义
- 代码简洁
- 不需要给每个节点设置id,还减少了dom查询
- 减少浏览器内存占用
- 如果子节点数量过多,每个节点都去监听,会十分损耗内存
- 但是不能滥用
- 对于瀑布流这些子节点很多的可以使用,其他不建议
- 类似一个按钮的绑定使用代理显得复杂
- 代码简洁
2.4. 通用的事件绑定函数
- 代码示例 html
<div id="div1" class="container"> <button id="btn1">事件绑定示例1</button> </div> <div id="div2"> <a href="#">a1</a><br> <a href="#">a2</a><br> <a href="#">a3</a><br> <a href="#">a4</a><br> <button>加载更多...</button> </div> <script> // 通用的绑定函数 // 既能支持普通的监听 // 又能支持代理的监听 // 在代理中不需要在自己的业务逻辑中判断触发的元素 // selector表示筛选出需要触发的标签 // 并且需要this永远指向触发的元素 let bindEvent = (elem, type, selector, fn)=> { // 需要做判断,如果没有传selector,即不是代理 // x == null => x === null || x === undefind // 如果fn为null表示传了三个参数,需要把对应关系转换一下 if(fn == null) { fn = selector selector = null } // 绑定 elem.addEventListener(type, event=> { // 不管有没有代理,都需要target const target = event.target // 我们当前触发的元素 const targetText = event.target.textContent // 获取文本 console.log("当前触发的元素", target) if(selector) { console.log("代理绑定"); // 存在selector表示他是代理绑定 // 使用matches表示,这个dom元素(我们当前触发的元素),是否符合于这个css选择器 if(target.matches(selector)) { // 将fn的this指向改成触发的元素 // call第一个参数是this指向,后面的参数表示fn的参数 // call会直接执行 fn.call(target, event) } } else { console.log("普通绑定"); // 不存在selector表示他是普通绑定 // 需要this指向当前触发的元素,即target // event表示执行的函数的参数 // 我们需要使用event对象做一些操作 // call后面第二个参数开始,就可以当作函数的形参 fn.call(target, event) } }) } // 普通绑定 const btn1 = document.getElementById('btn1') // 这里不能使用箭头函数,因为箭头函数没有this指向 // 也可以说箭头函数里面的this指向当前方法块所在的环境(上级作用域) // 即箭头函数里面的this永远指向它的父级(即上级作用域) bindEvent(btn1, 'click', function (event) { // console.log(event.target) // 获取触发的元素 event.preventDefault() // 阻止默认行为 // 如果使用箭头函数,这里的this指向window alert(this.innerHTML) // this指向触发元素 // 如果使用箭头函数 // alert(btn1.innerHTML) }) // 代理绑定 const div2 = document.getElementById('div2') // 表示只允许a标签触发 bindEvent(div2, 'click', 'a', function (event) { event.preventDefault() // 阻止默认行为 alert(this.innerHTML) // 如果使用箭头函数 // alert(div2.innerHTML) }) </script>
<div id="div1" class="container"> <button id="btn1">事件绑定示例1</button> </div> <div id="div2"> <a href="#">a1</a><br> <a href="#">a2</a><br> <a href="#">a3</a><br> <a href="#">a4</a><br> <button>加载更多...</button> </div> <script> // 通用的绑定函数 // 既能支持普通的监听 // 又能支持代理的监听 // 在代理中不需要在自己的业务逻辑中判断触发的元素 // selector表示筛选出需要触发的标签 // 并且需要this永远指向触发的元素 let bindEvent = (elem, type, selector, fn)=> { // 需要做判断,如果没有传selector,即不是代理 // x == null => x === null || x === undefind // 如果fn为null表示传了三个参数,需要把对应关系转换一下 if(fn == null) { fn = selector selector = null } // 绑定 elem.addEventListener(type, event=> { // 不管有没有代理,都需要target const target = event.target // 我们当前触发的元素 const targetText = event.target.textContent // 获取文本 console.log("当前触发的元素", target) if(selector) { console.log("代理绑定"); // 存在selector表示他是代理绑定 // 使用matches表示,这个dom元素(我们当前触发的元素),是否符合于这个css选择器 if(target.matches(selector)) { // 将fn的this指向改成触发的元素 // call第一个参数是this指向,后面的参数表示fn的参数 // call会直接执行 fn.call(target, event) } } else { console.log("普通绑定"); // 不存在selector表示他是普通绑定 // 需要this指向当前触发的元素,即target // event表示执行的函数的参数 // 我们需要使用event对象做一些操作 // call后面第二个参数开始,就可以当作函数的形参 fn.call(target, event) } }) } // 普通绑定 const btn1 = document.getElementById('btn1') // 这里不能使用箭头函数,因为箭头函数没有this指向 // 也可以说箭头函数里面的this指向当前方法块所在的环境(上级作用域) // 即箭头函数里面的this永远指向它的父级(即上级作用域) bindEvent(btn1, 'click', function (event) { // console.log(event.target) // 获取触发的元素 event.preventDefault() // 阻止默认行为 // 如果使用箭头函数,这里的this指向window alert(this.innerHTML) // this指向触发元素 // 如果使用箭头函数 // alert(btn1.innerHTML) }) // 代理绑定 const div2 = document.getElementById('div2') // 表示只允许a标签触发 bindEvent(div2, 'click', 'a', function (event) { event.preventDefault() // 阻止默认行为 alert(this.innerHTML) // 如果使用箭头函数 // alert(div2.innerHTML) }) </script>
3. 面试题解答(总结)
- 编写一个通用的事件监听函数
- 需要考虑代理和this的问题
- 见上
- 描述事件冒泡的流程
- 基于DOM树形结构
- 事件会顺着触发元素往上冒泡
- 监听父节点的dom,点击子节点会触发父节点的事件,如果监听body的点击事件,那么在dom树中body下任意元素触发点击事件,都会冒泡到body的事件中
- 应用场景:代理
- 无限下拉的图片列表,如何监听每个图片的点击
- 使用事件代理,监听父元素,通过父元素的监听获取子元素内容
- 使用event.target获取触发元素
- 使用matches来判断是否触发元素