秒懂JS中的同步/异步编程是什么

2,204 阅读6分钟

1. 进程(process)/线程(thread)

进程process: 电脑端安装很多的应用软件,每当运行一个应用程序,相当于开辟一个进程(而对于浏览器来说,每新建一个页卡访问一个页面,都是新开辟一个进程)

  • 任务管理器可以查看进程

线程thread: 每一个进程中可能还会同时做很多事情,如果程序中需要同时处理很多事情,则需要开辟多个线程(一个线程同时只能做一个事情)

=> 一个进程中,会包含0~多个线程

理解:

  • 每一个饭店是一个进程
  • 饭店里面每一个点餐的服务员就是一个线程

2. JS是单线程的线程

浏览器是‘多线程’的,但是JS渲染或者页面渲染是‘单线程’的

2.1 浏览器中线程的分类

  • GUI渲染线程(渲染和绘制页面)
  • JS引擎线程(运行和渲染JS代码)
  • 事件管控和触发线程
  • 定时器管控和触发线程
  • 异步HTTP请求线程
  • ...

2.2 JS代码的渲染是单线程的

浏览器是多线程的(打开一个页面以后,浏览器至少要分配好几个线程同时去处理事情),但是浏览器只会分配出一个线程去渲染代码(GUI渲染线程),所以说JS是单线程的:‘在JS代码执行过程中,一次只能处理一个事情’

3. 同步与异步

  • 同步编程【单线程】:任务是一次执行,上面的任务没有执行完成,下面的任务是不能去操作的
  • 异步编程【多线程】:同时可以处理很多事情,但是JS中的异步编程是利用浏览器的相关机制构造出来的异步效果

通俗的理解:比如我们在食堂打饭这个场景: 我们在排队打饭,但是当排到B打饭的时候,他的王者荣耀游戏还没有打完(吃饭不积极,思想...😅)

  • 此时同步编程思想处理的方式就是:我们大家都等B打完游戏,只要B不打饭,后面的也不能打饭(B不会就是食堂老板的儿子吧,这么多人等他打游戏🤔)
  • 但是异步编程思想处理方式是这样的:不管你的后台有多大,腾不出手,就去旁边的任务队列等着去,等我们大家都打完饭,你的游戏也打完了,再来打饭(这你还怎么猖狂😂)
同步编程
同步编程
异步编程
异步编程

3.1 常见的异步代码

  • 定时器:设置定时器是同步(立即设置),异步指的是间隔多久后执行指定的函数
  • 事件绑定(监听)
  • AJAX的异步请求
  • promise/async/await

4 定时器

  • setTimeout
  • setInterval

4.1 定时器的返回值

返回值:是一个数字,代表当前是第几个定时器

  • 我们后期可以基于clearTimeout / clearInterval 清除定时器
  • 手动把timer赋值为null,后期基于它的值验证是否存在定时器

4.2 浏览器的最小反应时间

  • 定时器是异步的:遇到定时器先不执行,先去执行其他事情,等到所有事情做完再看那个定时器到达时间然后可以立马执行;
  • 定时器的等待时间即使设置为零,也不是立即执行,浏览器有一个最小的等待时间(谷歌5~6MS IE浏览器10~13MS
  • 最小反应时间:在滚动相同的距离下,事件被触发多少次,取决于滚动所用的时间(速度)来决定的,浏览器有最小的反应时间,假设是5MS,整体运动时间100MS,这段时间内,浏览器能够识别出来的次数是100/5 = 20次。同理,如果我们运动1000MS,那么识别触发的次数就是1000/5=200次

4.3 从定时器的执行过程理解它的异步的

  • 把代码拿到栈中执行,当遇到异步代码定时器的时候,会立即把定时器拿到任务队列中去等待一定时间
  • 当把主线程的所有代码执行完毕之后,去任务队列中查看哪个定时器到达时间,把到达时间的定时器拿到栈中执行(这种操作是异步)

=> 遇到定时器不是不处理,而是把它放在任务队列,等到主线程空闲下来,再去任务队列查看,这种操作是异步。中途定时器到达时间了,但是主线程并没有到达时间,此时也不会立即执行定时器,必须等到主线程空闲下来。

4.4 下面是关于定时器的对异步代码的理解,配有图片和注释的说明👇

4.4.1 题目一

let n = 0;
setTimeout(() => {
   n++;
   console.log(n);   //=> 3 (2)
}, 1000);  //一秒钟之后执行
n += 2;
console.log(n);    //=> 2 (1)
题目一
题目一

4.4.2 题目二

let n = 0;
setTimeout(() => {
   n++;
   console.log(n);   //=> 3 (2)
},0);  //写零也不是立即执行,而是有一个最小的等待时间:10ms左右
n += 2;
console.log(n);   //=> 2 (1)

4.4.3 题目三

let n = 10;
setTimeout(() => {
   n++;
   console.log(n);    //=>11(3)
}, 0);        
console.log(n);  //=>10(1)
for(let i = 0;i < 99999999; i++){}
console.log(n);  //=>10(2)
题目三
题目三

4.4.4 题目四

time / timeEnd:获取他们中间代码执行所需要的时间(这个时间需要受到电脑配置、和当前电脑运行的环境等多方面因素影响),时间只作为参考

setTimeout(() => {
    console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
    console.log(3);
}, 10);
console.log(4);
console.time('AA');
for (let i = 0; i < 90000000; i++) {
    // do soming
}
console.timeEnd('AA'); //=>AA: 79ms 左右
console.log(5);
setTimeout(() => {
    console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
    console.log(8);
}, 15);
console.log(9);
题目四
题目四

4.4.5 题目五

console.log(1); //=>1(1)
setTimeout(function () {
 console.log(2);
}, 20);
console.log(3); //=>3(2)
for (let i = 0; i > -1; i++) {} //=>死循环,GUI线程啥都做不了,一直在这加载(其它什么事情都干不了)
console.log(4);
setTimeout(function () {
 console.log(5);
}, 10);
console.log(6);

5. 事件循环 Event Loop

定义:JS是单线程的,因为浏览器只分配一个线程自上而下加载代码。所以JS中大部分任务都是同步任务。但是一定也有异步任务,定时器、事件绑定等这些都属于异步任务。

而浏览器处理JS中的异步任务是:在JS代码自上而下执行的时候,代码进栈执行,执行完出栈,在这反反复复进行的过程中。当遇到定时器等异步任务的时候,会把当前任务放在等待任务队列(Event Queue)中存起来,并且存起来之后不会影响下面代码的执行,主线程会继续执行。当 把下面的同步任务执行完成之后,主线程空闲下来了会去等待队列找哪一个任务到达指定的时间点,就拿到主线程中去执行。执行完之后再去等待队列中查看...