JavaScript进阶之同步和异步

290 阅读4分钟

理解同步和异步

同步:就是调用之后得到结果,再继续执行别的任务,同步代码是按照顺序执行,只有上面的代码执行完成之后下面的代码执行,同步会阻塞下面代的码执行。下面我们来看一段同步代码

function test() {
  let t = +new Date();
  while (true) {
    if ((+new Date() - t) >= 2000) {
      break;
    }
  }
}
console.log(1);     
test();
console.log(2);

上面这段代码执行首先会输出1,然后等待两秒之后输出2,其中test方法就是一个等待两秒的方法,这个时候因为是同步的原因会阻塞线程两秒钟,后面的代码必须要等到test方法执行完了才会执行后面的代码,这就是同步的表现。

异步:就是调用之后先不管结果,先继续后面的任务,异步代码不是按照顺序执行的,当代码的执行遇到异步任务时,会先跳过继续执行后面的代码,等到异步执行结果返回来了再执行异步的处理,异步代码不会阻塞下面代码的执行,我们看下面的代码。

console.log(1)
setTimeout(() => {
  console.log(2);
}, 2000);
console.log(3)

上面这段代码执行后首先会输出1,然后遇到定时器后先不会执行定时器里面的代码,而是会把这个回调扔进异步队列里面,然后继续执行后面的代码,也就是输出3。当同步的代码执行完了之后,然后再去异步队列中把刚才的异步任务放到执行栈中执行,此时等待两秒之后输出2。我们常见的异步场景有“ajax请求”,“定时器”,“事件绑定”,“ES6的Promise”。

那么问题来了,我们都知道JavaScript是单线程的,它一次只能执行一个任务,那它是怎么实现异步的呢?在了解JavaScript是怎么实现异步之前呢,我们先来了解一下什么是进程,什么是线程。阮一峰老师有一篇文章对进程和线程做了一个很形象的比喻,他把CPU比喻成一个工厂,进程就好比是一个车间,线程就是这个车间的工人,一个工人一次执行做一件事,这就是为什么我们说JavaScript是单线程的。

JavaScript单线程

我们的浏览器每次打开就会启动“浏览器进程”,“渲染进程”,“GPU进程”,“网络进程”,“插件进程”,每个进程都会负责不同的功能,我们这里重点讲述的是“渲染进程”。

渲染进程的主要功能就是负责页面的渲染,它包含了“GUI线程”,“JS引擎线程”,“定时器触发线程”,“事件触发线程”,“异步HTTP请求线程”这五个线程,下面我们来了解下这五个线程分别是做什么的。

  • GUI线程: 页面渲染,解析HTML和CSS,构建DOM树和渲染树。
  • JS引擎线程: 解析和执行js,与GUI线程互斥,因为js也是可以操作DOM的,所以当GUI线程和JS引擎线程同时操作DOM时,这个时候就不知道已哪个为准了,会引起页面的混乱,所以我们要减少js文件的大小,不要让js执行的时间过长。
  • 定时器触发线程: 负责setTimeout和setInterval定时器的执行。
  • 事件触发线程: 将满足触发条件的事件放入事件队列。
  • 异步HTTP请求线程: XHR所在线程,处理ajax请求的,当请求完成时,如果有请求回调函数,它就会触发“事件触发线程”往任务队列里面添加事件。

我们所说的JavaScript单线程其实就是说的JS引擎线程,上面第二段代码中异步的实现,因为setTimeout定时器不是由“JS引擎线程”来执行的,而是由“定时器触发线程”来执行的,他会在定时任务完成之后通知“事件触发线程”往任务队列里面添加事件,所以它通过浏览器内核多线程实现的异步,这样代码就可以同时执行了。