事件是什么?
事件是 用户操作网页时发生的交互动作,比如 click/move, 事件除了用户触发的动作外,还可以是文档加载,窗口滚动和大小调整。
事件被 封装成一个 event 对象,包含了该事件发生时的所有相关信息( event 的属性)以及可以对事件进行的操作( event 的方法)。
事件模型?
JavaScript事件模型指的是一种基于事件驱动的编程模式,用于处理浏览器和用户发生的事件。当用户或浏览器执行某个动作时,例如点击某个元素或者提交表单,JavaScript就会将这些动作封装为一个事件,并将该事件传递给注册了该事件的回调函数。
JavaScript中的事件分为两种类型:
- 原生事件
- 自定义事件。
原生事件是由浏览器定义的事件,如点击、滚动、鼠标移动等,而自定义事件是由开发人员自己定义的事件。
在JavaScript事件模型中,当一个事件发生时,浏览器会将该事件封装成一个事件对象,并将其传递给注册了该事件的回调函数。
开发人员可以通过addEventListener()方法来向一个元素注册一个事件监听器,该方法接受三个参数:事件类型、回调函数和一个布尔值,指示回调函数是在事件捕获阶段还是事件冒泡阶段被调用。
事件捕获是从顶层元素开始,向下级元素逐层传递事件的过程,直到抵达目标元素。事件冒泡则是从目标元素开始,向上级元素逐层传递事件的过程,直到抵达顶层元素。开发人员可以根据需要选择事件捕获还是事件冒泡阶段来注册事件监听器。
在事件处理函数中,开发人员可以使用event对象来访问事件相关的信息,如事件类型、目标元素、鼠标位置等。同时,还可以通过event.preventDefault()方法来阻止默认行为,或通过event.stopPropagation()方法来阻止事件传播。
在JavaScript中,事件模型是一种非常重要的编程模式,可以帮助开发人员更好地处理用户交互、浏览器行为等方面的问题。
事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型:
原始事件模型(DOM0级)
在原始事件模型中,事件发生后没有传播的概念,所以没有事件流的概念。
但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。
事件发生,马上处理。监听函数只是元素的一个属性值,通过指定元素的属性值来绑定监听器。
直接在 dom 对象上注册事件名称,就是 DOM0 写法
书写方式有两种:
HTML代码中指定属性值:
<input type="button" onclick="func1()" />
在js代码中指定属性值:
document.getElementsByTagName(‘input’)[0].onclick = func1
优点:
所有浏览器都兼容
缺点:
- 逻辑与显示没有分离;
- 相同事件的监听函数只能绑定一个,后绑定的会覆盖掉前面的,如:a.onclick = func1; a.onclick = func2;将只会执行func2中的内容;
- 无法通过事件的冒泡、委托等机制完成更多事情;
IE 事件模型
“IE不把该对象传入事件处理函数,由于在任意时刻只会存在一个事件,所以IE把它作为全局对象window的一个属性”,用IE8执行了代码alert(window.event),结果弹出是null,说明该属性已经定义,只是值为null(与undefined不同),
代码如下:
window.onload = function (){alert(window.event);}
setTimeout(function(){alert(window.event);},2000);
第一次弹出【object event】,两秒后弹出依然是null。
由此可见IE是将event对象在处理函数中设为window的属性,一旦函数执行结束,便被置为null了;
在该事件模型中,一次事件共有两个过程:
- 事件处理阶段(先执行元素的监听函数,)
- 事件冒泡阶段(然后事件沿着父节点一直冒泡到document。)
事件处理阶段会首先执行目标元素绑定的监听事件。
然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到document,依次检查经过的节点是否绑定了事件监听函数,如果有则
执行。
这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
IE模型下的事件监听方式比较独特,绑定监听函数的方法是:
attachEvent( "eventType","handler");//其中evetType为事件的类型,
如onclick,注意要加’on’。解除事件监听器的方法是:
detachEvent("eventType","handler" )
IE的事件模型已经可以解决原始模型的三个缺点,但其自己的缺点就是兼容性,只有IE系列浏览器才可以这样写。
更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
DOM2事件模型
此模型是W3C制定的标准模型,既然是标准,现代浏览器(指IE6~8除外的浏览器)都已经遵循这个规范。
W3C制定的事件模型中,一次事件的发生包含三个过程:
- capturing phase:事件捕获阶段。事件被从document一直向下传播到目标元素,在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行;
- target phase:事件处理阶段。事件到达目标元素,执行目标元素的事件处理函数;
- bubbling phase:事件冒泡阶段。事件从目标元素上升一直到达document,同样依次检查经过的节点是否注册了该事件的监听函数,有则执行;
这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
所有的事件类型都会经历captruing phase但是只有部分事件会经历bubbling phase阶段,例如submit事件就不会被冒泡。
标准的事件监听器绑定:
addEventListener("eventType","handler","true|false");
其中eventType指事件类型。第二个参数是处理函数,第三个即用来指定是否在捕获阶段进行处理,一般设为false来与IE保持一致。
监听器的解除也类似:
removeEventListner("eventType","handler","true!false");
以上便是事件的三种模型,我们在开发的时候需要兼顾IE与非IE浏览器,所以注册一个监听器应该这样写:
var a = document.getElementById('a');
if(a.attachEvent){
a.attachEvent('onclick',func);
}
else{
a.addEventListener('click',func,false);
}
对事件循环的理解
因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。
当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。
任务队列可以分为:
- 宏任务队列
- 微任务队列
宏任务(Macrotasks)
宏任务(Macrotasks)包括以下几种常见的操作:
-
setTimeout和setInterval: 通过定时器设置的回调函数会被添加到宏任务队列中,等待执行。
-
I/O 操作:涉及到网络请求、文件读写等的异步操作会被添加到宏任务队列中。
-
UI 渲染:渲染引擎会将UI渲染任务添加到宏任务队列中。
-
事件处理程序:例如点击事件、键盘事件等会触发相应的回调函数,这些回调函数也会被添加到宏任务队列中。
微任务(Microtasks)
微任务(Microtasks)则是具有更高优先级的异步操作,它们会在当前宏任务执行完毕后立即执行。
常见的微任务包括:
-
Promise回调函数: 当Promise对象状态变为resolved或rejected时,相关的回调函数会被添加到微任务队列中。
-
MutationObserver: 监听DOM树变化的回调函数会被添加到微任务队列中。
-
process.nextTick(Node.js环境): 在Node.js环境中,process.nextTick函数添加的回调函数会被添加到微任务队列中。
对于执行顺序,当一个宏任务执行完毕后,会先执行所有的微任务队列中的任务,直到微任务队列为空,然后再执行下一个宏任务。这样的循环保证了微任务具有更高的优先级,能够及时处理一些重要的回调函数。
总结,
- 宏任务包括定时器、I/O操作、UI渲染和事件处理程序等,
- 而微任务包括Promise回调函数和MutationObserver等。
微任务比宏任务具有更高的优先级,在当前宏任务结束后立即执行。
当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行
,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务
。
Event Loop 执行顺序如下所示:
JavaScript中的Event Loop是用来管理异步代码执行的机制。
它确保了代码运行的顺序和正确性。下面是Event Loop的大致执行顺序:
-
执行同步代码:从上到下依次执行JavaScript代码中的同步部分。
-
处理微任务(Microtasks): 当遇到微任务时,会将微任务添加到一个微任务队列中。常见的微任务包括Promise回调函数、MutationObserver和process.nextTick。在当前宏任务执行完毕后,会依次执行微任务队列中的所有微任务,直到队列为空。
-
执行宏任务(Macrotasks): 当所有微任务执行完毕后,会检查是否有宏任务需要执行。常见的宏任务包括setTimeout、setInterval、requestAnimationFrame、I/O操作和事件回调。从宏任务队列中取出一个任务执行,执行完毕后返回步骤2。
-
重复步骤2和步骤3: 不断循环执行步骤2和步骤3,直到没有待处理的微任务和宏任务。
需要注意的是,微任务具有更高的优先级,会在下一个宏任务执行之前被处理完毕。而宏任务则是按照添加的顺序执行。
同时,值得提及的是,不同的宿主环境(例如浏览器和Node.js)可能有一些差异,但大致的执行逻辑是相似的。
Event Loop的执行顺序可以简化为:同步代码 -> 微任务 -> 宏任务 -> 重复执行微任务和宏任务
。
这样的循环保证了JavaScript的并发性和响应性。