之前在学习JavaScript在浏览器的应用时,也知道JavaScript是一个事件驱动语言,但对JavaScript事件驱动机制只是概念上的认识,因此,对异步机制也存在困惑,最近接触到这方面,做以下学习总结
一、javascript 在浏览器端的事件驱动机制
首先,javascript 在浏览器端运行是单线程的,这是由浏览器决定的,这是为了避免多线程执行不同任务会发生冲突的情况。也就是说我们写的javascript 代码只在一个线程上运行,称之为主线程(HTML5提供了web worker API可以让浏览器开一个线程运行比较复杂耗时的 javascript任务,但是这个线程仍受主线程的控制)。单线程的话,如果我们做一些“sleep”的操作比如说:
1 | var now = + new Date() |
那么在这将近一秒内,线程就会被阻塞,无法继续执行下面的任务。
还有些操作比如说获取远程数据、I/O操作等,他们都很耗时,如果采用同步的方式,那么进程在执行这些操作时就会因为耗时而等待,就像上面那样,下面的任务也只能等待,这样效率并不高。 为了解决单线程带来的阻塞问题很多操作系统实现了异步编程机制,浏览器中也是这么做的,主要表现如下:
1、只在主线程中运行JavaScript代码
2、主线程一启动就进入事件循环,整个过程就是不断循环,不断执行回调函数的过程
3、遇到网络请求、I/O操作等,浏览器就会单独开一个工作线程来处理这些比较耗时的操作,并且会设置相应的观察者,然后就回到主线程,继续执行下面的代码
4、浏览器单开的线程处理好任务时,或者有监听的事件后会用得到的数据形成一个事件,放在相应的观察者事件队列中,事件队列是在主线程中的
5、主线程不断地循环,不断的检查事件队列,通过遍历事件依次执行其回调函数(事件循环是在主线程中执行栈里的代码执行完毕之后才开始执行的)
从图中可以看出,其发送了一个ajax请求,浏览器单开一个线程用于执行它,然后继续回到主线程去执行其他的命令,当ajax执行完后就会把信息放在事件队列中,主线程检查其事件队列,然后执行其对应的回调函数
二、观察者机制(watcher)
watcher,观察者,是事件驱动系统的重要的机制
setTimeout
称为定时器,这是浏览器给的API。每当你使用定时器,这个函数将会设置一个watcher
,观察者。主线程会不断的循环,不断的”经过”这里检查时间,当主线程检查时间间隔符合要求时,就会产生一个定时器事件,加入到这个watcher
事件队列中并执行回调函数。因此执行setTimeout
只是在时间到的时候产生了要调用回调函数的消息加入到了事件队列中,因此,回调函数并不一定在指定的时间时调用,它取决于前面有多少等待处理的事件。
刚才讲的是定时器观察者,还有I/O观察者、网络请求观察者、鼠标事件观察者、键盘事件观察者等等等等,我们经常遇到事件监听函数会让你绑定一个回调函数,这种监听函数一般就会设置watcher
,其他线程产生的事件也会放到相应watcher
的事件队列中,因此每个watcher
会产生自己的事件队列。主线程在循环的时候,实际上是在依次调用这些watcher
,检查每个watcher
的事件队列,有事件就执行相应的回调。
三、javaScript在node.js上的事件驱动机制
javascript 在 node.js上的事件驱动机制与浏览器端大致相同,都是单线程,都有event loop,上面讲的javascript在浏览器端的事件循环机制在node上也是大致一样的,不同的是执行者和执行者的行为不一样,因为他们关注的任务不一样:
1、node端异步机制和事件循环更加纯粹一些。node为了支持高并发,所有的API几乎都是异步的,这样会充分利用操作系统的其他线程来帮忙完成任务,主线程只负责事件消费。例如当web server接收到请求,node就把它关闭,交给其他线程进行处理,然后去服务下一个web请求。当这个请求完成,它被放到处理队列,当到达队列开头,这个结果被返回给用户。这样的话webserver一直接受请求而不等待任何读写操作,这种非阻塞型I/O性能很强。
2、 浏览器端是浏览器负责执行BOM API,管理线程,处理用户输入信息等,在node上是node的一个核心库libuv负责执行node API,管理主线程(运行javascript)和工作线程等。
3、 因为前端和后端关注的内容不同,因此两个运行环境的API也专注于不同的任务
作者:莫凡_Tcg链接:https://juejin.im/post/59e21e8551882578db27c364来源:掘金著作权归作者所有。
- 本文作者: gtt
- 本文链接: https://gtt011029.github.io/posts/55049/