欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JavaScript的单线程和异步

程序员文章站 2024-01-29 10:29:40
...

本文只是讲解单线程和异步的一些基本知识,使用setTimeOut来讲解异步,不包括promise,generator,async等异步的进阶内容。

如想了解,却在了解本文内容后跳转阅读。

promise对象

yeild

async

单线程

所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,当前的任务会「阻塞」其他任务。

JavaScript 从诞生起就是单线程,原因是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。

如果 JavaScript 同时有两个线程,一个线程在网页 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?是不是还要有锁机制?

所以,为了避免复杂性,JavaScript 一开始就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。

常见的浏览器无响应(假死),往往就是因为某一段 JavaScript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

JavaScript 语言本身并不慢,慢的是读写外部数据,比如等待 Ajax 请求返回结果。这个时候,如果对方服务器迟迟没有响应,或者网络不通畅,就会导致脚本的长时间停滞。

因此为了充分利用CPU的计算能力,提高效率,就有了异步操作。

同步任务和异步任务

程序里面所有的任务,可以分成两类:

  • 同步任务(Synchronous, sync)

    同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。

  • 异步任务(Asynchronous, async)

    异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。排在异步任务后面的代码,不用等待异步任务的结束,马上运行,也就是说,异步任务不具有“堵塞”效应。

举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理。

如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;

如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。

事件循环和任务队列

Javascript是单线程的,但是却能执行异步任务,这主要是因为 JS 中存在****事件循环(Event Loop)和任务队列(Task Queue)****

事件循环

为了将执行完毕的异步任务加入到同步任务当中去,JavaScript 引擎有一种循环检查的机制——事件循环(Event Loop),事件循环是一个程序结构,用于等待和发送消息和事件。

事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。

引擎不停地一遍又一遍检查,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。

任务队列

JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)

首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。

如果满足条件,那么取出异步任务(回调)放入执行栈中由主线程执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,它也不会重新进入主线程,因为没有用回调函数指定下一步的操作。

验证

var timeout1 = setTimeout(function () {
    console.log(2);//2
}, 0);

console.log(1);//1

var timeout2 = setTimeout(function () {
    console.log(3);//3
}, 0);

/*setTimeout是异步任务,而timeout1又比timeout2先注册,所以最终输出了这个结果。
输出:
1
2
3

上面的例子不太能够证明同步任务究竟是不是会优先于异步任务执行,因为setTimeout有一个最小的时间间隔限制,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的毫秒数设置为 0 ,被调用的程序也没有马上启动。在这个时间间隔里语句console.log(1)完全可以执行完毕,我们要想办法让同步代码占用更长时间

setTimeout(function () {
    console.log(1);//1
}, 0);

console.log(2);//2

//循环等待三秒后再执行
let end = Date.now() + 1000 * 3;
while (Date.now() < end) {
}

console.log(3);//3

//循环等待三秒后再执行
end = Date.now() + 1000 * 3;
while (Date.now() < end) {
}

console.log(4);//4

/*输出:
2
3
4
1

从上面的输出结果我们可以确定,异步代码是在所有同步代码执行完毕以后才开始执行的。


参考

JavaScript任务队列的顺序机制(事件循环)

异步操作概述

上一篇: 第二周总结

下一篇: smarty有关问题