JS 事件
最近跟同事一起面试, 发现同时每次必问 JS 事件相关的内容, 自己就做了下梳理, 对其事件相关的内容做了下总结.
事件模型:
顾名思义就是事件的一个流转规则, 说到事件模型就跟各个浏览器之间的差异就有关系了, 目前浏览器没有统一事件模型, 大致分为三种原始事件模型(DOM0 级),DOM2 事件模型, IE 事件模型.
原始事件模型: 被所有浏览器支持, 原始事件没有事件流, 立马触发立马响应不会传递, 我自己理解就是点击只触发当前, 类似阻止传递.
// 主要就是 onclick 事件
(1)在 html 代码中直接指定属性值:<button id="demo" type="button" onclick="doSomeTing()" />
(2)在 JS 代码中为 document.getElementsById("demo").onclick = doSomeTing()
优点: 所有浏览器都兼容
缺点:
1. 逻辑与显示没有分离;
2. 相同事件的监听函数只能绑定一个, 后绑定的会覆盖掉前面的, 如: a.onclick = func1; a.onclick = func2; 将只会执行 func2 中的内容.
3. 无法通过事件的冒泡, 委托等机制 (后面会讲到) 完成更多事情.
因为这些缺点, 虽然原始事件类型兼容所有浏览器, 但仍不推荐使用, 当然一般情况我们也很少使用.
IE 事件模型: IE 事件模型共有两个过程:
事件处理阶段: 事件到达目标元素, 触发目标元素的监听函数.
事件冒泡阶段: 事件从目标元素冒泡到 document, 依次检查经过的节点是否绑定了事件监听函数, 如果有则执行.
先执行元素的监听函数, 然后事件沿着父节点一直冒泡到 document. 冒泡机制后面系列会讲, 此处暂记. IE 模型下的事件监听方式也挺独特, 绑定监听函数的方法是: attachEvent( "eventType","handler"), 其中 evetType 为事件的类型, 如 onclick, 注意要加'on'. 解除事件监听器的方法是 detachEvent("eventType","handler" )
DOM2 事件模型: 这种形式是 W3C 制定的标准模型, 现代浏览器 (IE6~8 除外) 都已经遵循这个规范. W3C 制定的事件模型中, 一次事件的发生包含三个过程: 1. 事件捕获阶段, 2. 事件目标阶段, 3. 事件冒泡阶段. 下边两个图对该事件模型讲解的很清楚.
图 1
图 2
其实我们之后要讲的事件冒泡, 事件捕获, 事件委托都是以此模型来讲解的.
事件冒泡
事件从最具体的元素到不具体的元素, 从当前触发的事件目标一级一级往上传递, 依次触发, 直到 document 为止.
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>xxx</title>
- </head>
- <body onclick="bodyClick()">
- <div onclick="divClick()">
- <button onclick="btn()">
- <p onclick="p()">点击冒泡</p>
- </button>
- </div>
- <script>
- function p(){
- console.log('p 被点击')
- }
- function btn(){
- console.log("button 被点击")
- }
- function divClick(event){
- console.log('div 被点击');
- }
- function bodyClick(){
- console.log('body 被点击')
- }
- // 也可以使用
addEventListener('click',function(){ },false); 来替换 onclick.
- </script>
- </body>
- </HTML>
p 被点击》button 被点击》div 被点击》body 被点击, 即具体元素到不具体元素, 从 p 冒泡到根节点.
阻止事件冒泡:
标准的 W3C 方式: e.stopPropagation(); 这里的 stopPropagation 是标准的事件对象的一个方法, 调用即可
非标准的 IE 方式: ev.cancelBubble=true; 这里的 cancelBubble 是 IE 事件对象的属性, 设为 true 就可以了
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- Title
- </title>
- </head>
- <body>
- <div>
- <button>
- <p>
- 点击捕获
- </p>
- </button>
- </div>
- <script>
- var _p = document.querySelector('p');
- var _b = document.querySelector('button');
- var _d = document.querySelector('div');
- var _body = document.querySelector('body');
- _p.addEventListener('click',
- function() {
- console.log('p 被点击')
- },
- true);
- _b.addEventListener('click',
- function() {
- console.log("button 被点击")
- },
- true);
- _d.addEventListener('click',
- function() {
- console.log('div 被点击')
- },
- true);
- _body.addEventListener('click',
- function() {
- console.log('body 被点击')
- },
- true);
- </script>
- </body>
- </HTML>
事件捕获
从不具体的元素到具体的元素, 从 document 开始触发, 一级一级往下传递, 依次触发, 直到真正事件目标为止.
body 被点击》div 被点击》buton 被点击》p 被点击, 即跟冒泡是相反的, 从不具体元素到具体元素, 从根节点到 p 标签.
事件委托
原理: 事件冒泡机制
利用事件冒泡, 只指定一个事件处理程序, 就可以管理某一类型的所有事件. 借网上别人的例子简单明了的解释:
有三个同事预计会在周一收到快递. 为签收快递, 有两种办法: 一是三个人在公司门口等快递; 二是委托给前台 MM 代为签收. 现实当中, 我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递). 前台 MM 收到快递后, 她会判断收件人是谁, 然后按照收件人的要求签收, 甚至代为付款. 这种方案还有一个优势, 那就是即使公司里来了新员工(不管多少), 前台 MM 也会在收到寄给新员工的快递后核实并代为签收.
优点:
1. 大量减少内存占用, 减少事件注册.
2. 新增元素实现动态绑定事件
- <ul id="ul">
- <li>1</li>
- <li>2</li>
- <li>3</li>
- <li>4</li>
- </ul>
实现方式:
1. 可用 addEventListener(); // 所有主流浏览器, 除了 IE8 及更早 IE 版本.
- ul.addEventListener("click",function(e) {
- if(e.target && e.target.nodeName.toLowerCase() == "li") { // 检查事件源 e.target 是否为 li
- console.log("List item",e.target.id.replace("post-","")," was clicked!"); // 打印当前点击是第几个 item
- }
- });
- 2.attachEvent() //IE8 及 IE 更早版本, 现在已经不怎么使用.
- <1>. 用法: element.attachEvent(event,function);
(1)event 事件加'on',onClick
(2)没有第三个参数, 因为 IE 只有冒泡, 没有反向冒泡.
(3)执行顺序按照绑定的反序(先执行后绑定的方法).
<2>. 移除事件监听: element.detachEvent(event,function)
以上就是 JS 事件冒泡, 捕获, 委托和事件模型的相关个人理解, 有错误之处还请指正.
来源: http://www.jianshu.com/p/a9b8a433eb71