理解异步
同步函数:
当一个函数是同步执行时,当该函数被调用时,会等待这个函数执行完毕返回结果后再接着执行后续的代码。
异步函数:
当一个函数异步执行时,调用该函数时,该函数会立即返回结束,尽管异步任务未完成,依旧执行后续代码。
处理机制:
- 当一个线程调用一个同步函数时,如果该函数没有立即完成规定的操作,则操作会导致调用线程的挂起(将CPU的使用权交给系统,让系统将CPU分配给其他线程使用),直到该同步函数规定的操作完成函数才返回(结束),最终才能导致该调用线程被重新调度,继续执行后续代码。(也就是所谓的同步阻塞)
- 当一个线程调用一个异步函数时,该函数会立即返回(结束)尽管其指定的异步任务还未完成,这样线程就会接着执行异步函数下一条语句,而不会被挂起。异步函数所规定的的异步任务是通过另一个线程完成的。(另一个线程可能是在异步函数中新创建的一个线程,也可能是系统中已经准备好的线程)
我们常说异步不阻塞,那么这一点是如何做到的呢?这就涉及到了进程与线程的知识了。
进程与线程的区别与联系:
进程是程序的一个执行环境,当一个程序开始执行时,操作系统会为其创建内存用于保存代码、运行中的数据以及处理任务的主线程。线程是CPU调度的基本单位,线程是依附于进程的,体现在:一个线程崩溃会导致整个进程崩溃;线程之间共享进程中的数据;当一个进程退出,操作系统会收回所有为他分配的内存,即使某一个线程因操作不当导致内存泄露也会被正确回收。
异步不阻塞的实现:
要使异步函数调用后就立即结束,首先需要解决的问题就是将异步任务保存在另一个地方,然后执行,执行完毕后还需要通知调用方,并返回自己的执行结果给调用方。因为一个进程可以有多个线程,所以就可以通过别的线程来处理异步任务。
如何通知调用方呢?有哪些方式呢?
- 回调:调用异步函数时在参数中放入一个函数地址,异步函数保存了此地址,待有了结果后通知回调函数便可以向调用方发出通知。
- 事件:event是Windows系统提供的一个同步对象,作用是在异步处理中对齐不同线程之间的步点。
- 消息:程序中定义一个用户消息,并由调用方准备好消息处理例程。
事件循环:
为什么JS要设计为单线程?
如果JS是多线程的,当用户在浏览器操作网页上的一个DOM结点时,因为是多线程,可以对同一个DOM结点一边进行删除操作,一边进行修改操作,这样两个任务就发生了冲突,为了避免这样的情况发生,所以JS设计为单线程。
虽然JS是单线程的,但是依然可以处理异步操作,对于异步任务基于事件循环模型来处理,不过在本质上他对异步任务的处理还是通过多线程之间的通信来处理,通讯的方式有回调、事件、消息;异步任务的调用方(如定时器...)与被调用方(定时器参数中的回调)处于不同的线程中,被调用方执行完毕会将结果返回给调用方。
浏览器多线程
JS引擎线程(主线程)执行同步代码,其他的一些事件线程、定时器线程、异步线程、等管理异步任务,再在合适的时机将异步任务的回调放到消息队列当中,然后event loop线程在主线程执行栈为空时从消息队列中取出回调放到主线程执行栈中执行。
事件循环
1.宏任务:(由宿主对象(浏览器或者node等)发起的任务)
- 渲染事件(如解析 DOM、计算布局、绘制);
- 用户交互事件(如鼠标点击、滚动页面、放大缩小等);
- JavaScript 脚本; 4)网络请求完成、文件读写完成事件。
2.微任务:(由js引擎生成)
- Promise任务;(js引擎通过resolve函数将对应的异步任务提交到微任务队列当中保存)
- 使用 MutationObserver 监控某个 DOM 节点,然后再通过 JavaScript 来修改这个节点,或者为这个节点添加、删除部分子节点,当 DOM 节点发生变化时,就会产生 DOM 变化记录的微任务。
1.事件循环机制:
JS是单线程,非阻塞的,主线程按顺序执行代码,其他的一些事件线程、定时器线程、网络线程等异步线程管理与其对应异步任务,再在合适的时机将异步任务的回调放到任务队列当中,然后event loop线程在主线程调用栈为空时轮询任务队列,依次取出回调并放到主线程调用栈中执行。轮询的过程中,先轮询微任务队列,清空微任务队列后然后才会去轮询宏任务队列,在执行宏任务时还是先执行代码,再轮询微任务队列,最后轮询宏任务队列,过程同上,如此反复。通过event loop机制保证单线程不阻塞的同时还能确保异步任务能按预期的顺序执行。