盒子
文章目录
  1. 线程和异步是什么?
  2. js是单线程的
  3. 浏览器是多线程的
  4. 结语
  5. 参考

关于javascript的单线程和异步

线程和异步是什么?

其实在js里,是没有线程这个概念的,所谓的单线程也只是相对于多线程而言。于是,针对js这种不具备并行任务处理的特性,我们称之为“单线程”。

另一方面,异步又是什么东西呢?
js的点击等事件回调和setTimeout,这些都是js的异步特性,使用的时候感觉就好像打开了一个新的线程去执行代码。
但实际上,这些都是通过队列去完成的。

1
2
3
4
5
6
7
/*
* 当我们声明一个setTimeout和它的回调的时候。
* 实际上js解析引擎是把回调放在了执行队列(线程)的最末尾。
*/

setTimeout(function(){
console.log('Hello World");
},0);

js是单线程的

怎么证明它是这样子运作的呢?
其实很简单,如果说setTimeout回调被放在了队列的末尾,那就意味着,如果队列前面的内容(js代码)没有执行完毕,就不能执行末尾的回调。

简单的说就是,setTimeout回调在线程空闲之前是不会运行的。

1
2
3
4
5
6
7
8
9
10
11
12
//开始时间
var start = new Date;
setTimeout(function(){
//setTimeout回调函数真正执行的时间
var end = new Date;
console.log('响应的时间:', end - start, 'ms');

}, 500 /* 我们给setTimeout设置的延迟,这是我们以为它会执行的时间*/);

//执行循环,直到过了1000ms才中断循环。
//这行代码的作用就是阻塞进程。
while(new Date - start < 1000){};

如果js真的是多线程的,那么计时函数就会在500毫秒后运行。
然后事实上,计时器响应时间不可能会小于1000毫秒(被while的循环阻塞住了),这里可能会因为浏览器有细微的差异。

下图是我在chrome控制台直接运行代码的结果。
证明js单线程

结论就是:在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。而浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的。这些事件会创建的回调函数并放入执行队列中。javascript引擎是单线程处理它的任务队列。如果同一时间有多个事件(如点击和获得焦点事件)同时触发,这些事件会按照一定顺序被放入队列中,然后一个一个响应。

浏览器是多线程的

另一方面,运行在浏览器中的js是单线程的,但浏览器并不是单线程的。浏览器中很多异步行为都是由浏览器新开一个线程去完成。javascript引擎线程仅仅只是浏览器多个线程中的一个,它本身是单线程的。浏览器还包括很多其他线程,如界面渲染线程,浏览器事件触发线程,http请求线程等。

除了上面说的这些浏览器的线程之外,每一个页面的标签,都可以算是一个js线程。js线程之间不能直接通信,也就是说,浏览器打开了a,b两个页面,有两个标签,a不知道b发生了什么事情,反过来b也不知道a。

当然这里可以使用cookie轮询来实现多个线程的间接通信。这又是另一个话题了。

结语

一旦我们能够接受这种语言的单线程设计,其实就会发现js事件模型既优雅又实用。
它意味着我们的代码是不可中断的,也意味着调度的事件会整整齐齐排好队,有条不紊地运行。

参考

如何证明JavaScript是单线程的?