阅读 952

我对 javascript 事件驱动机制的理解

之前在学习 javascript 在浏览器的应用时,也知道 javascript 是一个事件驱动语言,但对javascript事件驱动机制只有概念上的认识,因此,对异步机制也存在困惑。最近在接触 node.js ,对javascript事件驱动机制有了更深的理解。

概述

本文分别讲解了javascript在浏览器端和服务器端(node.js)的事件驱动机制,期间加入了一些异步编程的例子加深理解。

正文

javascript 在浏览器端的事件驱动机制

首先,javascript 在浏览器端运行是单线程的,这是由浏览器决定的,这是为了避免多线程执行不同任务会发生冲突的情况。也就是说我们写的javascript 代码只在一个线程上运行,称之为主线程(HTML5提供了web worker API可以让浏览器开一个线程运行比较复杂耗时的 javascript任务,但是这个线程仍受主线程的控制)。单线程的话,如果我们做一些“sleep”的操作比如说:

var now = + new Date()
while (+new Date() <= now + 1000){
//这是一个耗时的操所
}复制代码

那么在这将近一秒内,线程就会被阻塞,无法继续执行下面的任务。

还有些操作比如说获取远程数据、I/O操作等,他们都很耗时,如果采用同步的方式,那么进程在执行这些操作时就会因为耗时而等待,就像上面那样,下面的任务也只能等待,这样效率并不高。 为了解决单线程带来的阻塞问题很多操作系统实现了异步编程机制,浏览器中也是这么做的,主要表现如下:

  • 只在主线程中运行 javascript 代码

  • 主线程一启动就进入事件循环,整个过程就是不断的循环,不断地执行回调函数

  • 遇到网络请求、I/O操作等时,浏览器会单开工作线程来处理,并设置相应的观察者,然后立即返回主线程,主线程继续执行下面的任务

  • 浏览器开的线程处理好任务或者有监听的事件后会用得到的数据(或输入)形成一个事件,放在相应观察者的事件队列中,事件队列是在主线程中

  • 主线程不断的循环,不断检查事件队列,通过遍历事件依次执行事件对应的回调函数

图片来源segmentfault.com/img/bVxLvF)

注意:下图中的消息队列是存储在主线程中

上图中,假设你发起了一个AJAX请求,无论你把这个请求写在什么地方,它始终都在回调函数里。因为事件驱动机制就是把一切抽象为事件,代码开始执行也是一个事件,也会隐式调用回调函数,调用回调函数就是开始执行代码。然后主线程发起异步任务后就会随即返回,继续执行"代码开始事件"对应回调函数里下面的代码,等到这个回调函数执行完毕,就会执行下一个事件。在这之间,Ajax线程会完成请求,然后把请求完成的事件(包含返回的数据)发送到事件队尾中等待处理,等到主线程执行到这个事件时,指定的回调函数即被执行。

大概是这样。如果有几处疑问的话请往下看。下面结合代码讲一下具体的过程和机制。

console.log("开始");
setTimeout(function(){
  console.log('延迟执行的')
}, 1000);
setTimeout(function(){
  console.log('立即执行的')
}, 0);
console.log('结束') //开始 结束 立即执行的 延迟执行的复制代码

watcher机制

watcher,观察者,是事件驱动系统重要的机制。

setTimeout称为定时器,这是浏览器给的API。每当你使用定时器,这个函数将会设置一个watcher,观察者。主线程会不断的循环,不断的"经过"这里检查时间,当主线程检查时间间隔符合要求时,就会产生一个定时器事件,加入到这个watcher事件队列中并执行回调函数。因此执行setTimeout只是在时间到的时候产生了要调用回调函数的消息加入到了事件队列中,因此,回调函数并不一定在指定的时间时调用,它取决于前面有多少等待处理的事件。

刚才讲的是定时器观察者,还有I/O观察者、网络请求观察者、鼠标事件观察者、键盘事件观察者等等等等,我们经常遇到事件监听函数会让你绑定一个回调函数,这种监听函数一般就会设置watcher,其他线程产生的事件也会放到相应watcher的事件队列中,因此每个watcher产生自己的事件队列主线程在循环的时候,实际上是在依次调用这些watcher,检查每个watcher的事件队列,有事件就执行相应的回调。

(原创不容易,转载请注明出处 : ),有错误请评论):

它的过程就是 :

  • 进程一启动就进入事件循环

  • 有监听就添加watcher

  • 遍历watcher下的事件队列

  • 执行下一个watcher

事件驱动机制,它会有各种各样的事件,大量的事件,它所做的一切都跟处理事件有关。但并不是所有的事件都有watcher,如果都有,主进程任务会变得非常繁重,况且有些事件我们并不关心,例如你只写了一个定时器,代表你关心这个事件,那么点击事件、网络请求事件就不用关心,因为你根本就没写啊,也就没有watcher

javascript 在 node.js上的事件驱动机制

javascript 在 node.js上的事件驱动机制与浏览器端大致相同,都是单线程,都有event loop,上面讲的javascript在浏览器端的事件循环机制在node上也是大致一样的,不同的是执行者何执行者的行为不一样,因为他们关注的任务不一样:

  • node端异步机制和事件循环更加纯粹一些。node为了支持高并发,所有的API几乎都是异步的,这样会充分利用操作系统的其他线程来帮忙完成任务,主线程只负责事件消费。例如当web server接收到请求,node就把它关闭,交给其他线程进行处理,然后去服务下一个web请求。当这个请求完成,它被放到处理队列,当到达队列开头,这个结果被返回给用户。这样的话webserver一直接受请求而不等待任何读写操作,这种非阻塞型I/O性能很强。

  • 浏览器端是浏览器负责执行BOM API,管理线程,处理用户输入信息等,在node上是node的一个核心库libuv负责执行node API,管理主线程(运行javascript)和工作线程等。

  • 因为前端和后端关注的内容不同,因此两个运行环境的API也专注于不同的任务

这就是我对事件驱动机制的理解,有错误欢迎指正!

推荐阅读

JavaScript:彻底理解同步、异步和事件循环(Event Loop)

【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

单线程的JS引擎与 Event Loop

MDN:并发模型与事件循环


关注下面的标签,发现更多相似文章
评论