1. 背景介绍
javascript的单线程特性
由于javascript语言是一门“单线程”的语言,所以,javascript就像一条流水线,仅仅是一条流水线而已,要么加工,要么包装,不能同时进行多个任务和流程。
任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。于是就有一个概念——任务队列。如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。于是JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
事件循环
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
2. 知识剖析
名词解析
那么这里说的同步和异步到底是什么呢?js官方的文档在使用这两个词的时候并不准确,包括其他文档和很多其他词汇,都只是听起来高深,但实际应用好像跟这些词没半毛钱关系。例如“路由”这个词,不知道的人从字面意义上谁又能说出“路由”是什么意思呢?倒是路由器在生活中经常遇到不会感到陌生,将route使用谷歌翻译之后,其实就是路径和线路的意思,这样路由的概念也就跃然于脑海之中了,也就是说遇到陌生的概念和词汇,大可不必惊慌失措,重要的是理解其背后的本质
同步和异步的概念
“同步”—— 一下就让人想到“一起”这个词;“异步”呢,从字面来讲,好像是在不同的(异)的ways上do something,那首先想到的词可能是“一边...一边...”,比如‘小明一边吃雪糕一边写作业’,这完全没毛病,雪糕吃完了,作业也写完了,这就是异步?这种解释十分表面,停留在这个层面显然是不够的
无论如何,做事情的时候都是只有一条流水线(单线程),同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。
可以简单地理解为:可以改变程序正常执行顺序的操作就可以看成是异步操作。例如setTimeout和setInterval函数,Ajax通信等
同步和异步的区别
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,ok,这不难理解;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
异步运行机制
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的第三步。
回调函数
所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应,异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。例如ajax的success,complete,error也都指定了各自的回调函数,这些函数就会加入“任务队列”中,等待执行。
3. 常见问题
回调函数,同步,异步,单线程,任务队列,事件循环,这么多吓人的概念,彼此的关联又是如此的紧密,得想个办法把这些串起来,并进行总结梳理.
4. 解决方案
无法回避的核心:JavaScript 代码执行机制
JavaScript是单线程,意味着任务要一个接着一个完成,但是,如果前一个任务执行时间很长,那么后面的任务就得一直阻塞着,这样用户体验十分差。
JavaScript的设计者考虑到了这一点,所以他将JavaScript的任务分为两种,在主线程上执行的任务"同步任务",被主线程挂载起来的任务"异步任务",后者一般是放在一个叫任务队列的数据结构中
一旦单线程内的所有同步任务执行完毕了,系统就会读取“任务队列,这相当于一个while循环,所以也称为事件循环
读取任务队列中的异步任务所采用的方法就是使用回调函数
5. 编码实战
6. 扩展思考
同步和异步在社会生活中的映射
在公路上,汽车一辆接一辆,有条不紊的运行。这时,有一辆车坏掉了。假如它停在原地进行修理,那么后面的车就会被堵住没法行驶,交通就乱套了。幸好旁边有应急车道,可以把故障车辆推到应急车道修理,而正常的车流不会受到任何影响。等车修好了,再从应急车道回到正常车道即可。唯一的影响就是,应急车道用多了,原来的车辆之间的顺序会有点乱。
同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性。改变顺序性其实也没有什么大不了的,只不过让程序变得稍微难理解了一些。
回调函数(callback)
约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息(call me back),我很担心你。” 对不,然后你女朋友回家以后还真给你发了条信息。小伙子,你有戏了。其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。
回调,回调,就是回头调用的意思。主函数的事先干完,回头再调用传进来的那个函数。
7. 参考文献
参考一:http://www.ruanyifeng.com/blog/2014/10/event-loop.html
参考二:https://blog.csdn.net/qq_22855325/article/details/72958345
8. 更多讨论
问题一 为什么选择单线程?
JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
问题二 单线程意味着什么?
单线程就意味着,所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就需要一直等着。这就会导致IO操作(耗时但cpu闲置)时造成性能浪费的问题。
问题三 如何解决单线程带来的性能问题?
答案是异步!主线程完全可以不管IO操作,暂时挂起处于等待中的任务,先运行排在后面的任务。等到IO操作返回了结果,再回过头,把挂起的任务继续执行下去。于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)
原文:https://blog.csdn.net/web_zyx/article/details/81880702 \