当前位置: 首页 > Web前端 > HTML5

深入理解同步和异步js

时间:2023-04-05 17:08:52 HTML5

JavaScript语言的一大特点就是单线程,也就是说同一时间只能做一件事。那么,为什么JavaScript不能有多线程呢?这样可以提高效率啊。JavaScript的单线程与它的使用有关。作为一种浏览器脚本语言,JavaScript的主要目的是与用户交互和操作DOM。这就决定了它只能是单线程的,否则会带来非常复杂的同步问题。例如,假设JavaScript同时有两个线程,一个线程向某个DOM节点添加内容,另一个线程删除这个节点,那么浏览器应该以哪个线程为基础呢?因此,为了避免复杂性,JavaScript从一开始就是单线程的,这已经成为这门语言的核心特征,以后也不会改变。为了利用多核CPU的计算能力,HTML5提出了WebWorker标准,允许JavaScript脚本创建多个线程,但子线程完全由主线程控制,不得操作DOM。因此,这个新标准并没有改变JavaScript的单线程特性。其实同步和异步,不管怎样,做事的时候只有一个管道(单线程)。同步和异步的区别在于每个进程在这条管道上的执行顺序不同。最基本的异步函数就是setTimeout和setInterval函数,很常见,但是很少有人知道这其实是异步的,因为它们可以控制js的执行顺序。我们也可以简单的理解为,能够改变程序正常执行顺序的操作,都可以认为是异步操作。虽然我们在setTimeout(function,time)中设置了超时等待时间为0,后面会执行里面的函数。Firefox浏览器的api文档有这样一句话:因为即使调用setTimeout的延迟为零,它也会被放置在队列中并安排在下一次机会时运行,而不是立即运行。当前正在执行的代码必须在队列中的函数执行之前完成,结果执行顺序可能与预期不符。意思是:虽然setTimeout的延时为0,但是函数会被放入一个队列,等待下一次执行的机会,当前代码(指不需要加入队列的程序)必须在那个程序之前完成队列完成,因此结果可能与预期不同。这里我们讲一个“队列”(任务队列),队列里放什么,setTimeout里的函数放,这些函数依次加入队列,也就是队列里所有函数里的程序都会在队列中其他所有代码都执行完后,才执行。为什么是这样?因为在程序执行的时候,浏览器会默认将setTimeout、ajax请求等方法作为耗时程序(虽然不一定很耗时),并加入到一个队列中,这个队列是用来存储时间的——消费程序,非耗时程序全部执行完毕后,依次执行队列中的程序。回到最初的起点——javascript是单线程的。单线程是指所有的任务都需要排队,上一个任务完成后才会执行下一个任务。如果前一个任务耗时很长,后一个任务就得一直等下去。于是就有了一个概念——任务队列。如果排队是因为计算量大,CPU太忙,那就算了,但大部分时间CPU是空闲的,因为IO设备(输入输出设备)很慢(比如Ajax操作读来自网络的数据),你必须等待结果出来才能继续。于是JavaScript语言的设计者意识到,此时主线程完全可以忽略IO设备,挂起等待的任务,运行最先排队的任务。等到IO设备返回结果,再回去继续执行挂起的任务。因此,所有的任务可以分为两种,一种是同步任务(synchronous),一种是异步任务(asynchronous)。同步任务是指在主线程上排队等待执行的任务,只有上一个任务执行完才能执行下一个任务;异步任务是指不进入主线程而是进入“任务队列”(taskqueue)的任务,只有主线程任务执行完毕后,“任务队列”才开始通知主线程请求执行任务,任务会进入主线程执行。具体的异步运行机制如下:(1)所有同步任务都在主线程上执行,形成一个执行上下文栈。(2)除了主线程之外,还有一个“任务队列”。只要异步任务有运行结果,就会在“任务队列”中放入一个事件。(3)一旦“执行栈”中的所有同步任务都执行完毕,系统就会去读取“任务队列”,看看里面有什么事件。那些对应的异步任务结束等待状态,进入执行栈,开始执行。(4)主线程不断重复上面的第三步。只要主线程是空的,它就会读取“任务队列”,这是JavaScript的运行机制。这个过程一遍又一遍地重复。“任务队列”是一个事件队列(也可以理解为消息队列)。当一个IO设备完成一个任务时,一个事件被添加到“任务队列”中,表明相关的异步任务可以进入“执行栈”。主线程读取“任务队列”,也就是读取里面有哪些事件。“任务队列”中的事件,除了IO设备事件外,还包括一些用户产生的事件(如鼠标点击、页面滚动等),如$(selectot).click(function),它们是比较耗时的操作。只要指定了这些事件的回调函数,这些事件就会在发生时进入“任务队列”,等待主线程读取。所谓“回调函数”(callback)就是会被主线程挂掉的代码。上面提到的点击事件$(selectot).click(function)中的函数是一个回调函数。异步任务必须指定一个回调函数。当主线程开始执行异步任务时,执行相应的回调函数。比如ajax的success、complete、error也都指定了自己的回调函数,这些函数会被加入到“任务队列”中等待执行。