如何理解js单线程的问题

为什么JavaScript是单线程?

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。
所以,这个新标准并没有改变JavaScript单线程的本质。

任务队列

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

javascript事件处理器在线程空闲之前不会运行

for(var i=0;i<5;i++){
    setTimeout(function(){
        console.log(i);
    },0);
}

setTimeout:

for(var i=0;i<5;i++){
    setTimeout((function(pars){
        return function(){console.log(pars)};
    })(i),0);
}

setTimeout回调在while循环结束运行之前不可能被触发

var start = new Date;
setTimeout(function(){
    var end = new Date;
    console.log('time:',end-start,'ms');
},500);
while(new Date - start <2000){}

那运行到setTimeout函数时到底干了些什么事呢?
setTimeout函数只是在延时时间后把回调扔进队列中去

$('ID').onclick = function(){};
var ...
......

onclick函数会把事件扔到队列中去,然后去执行下面的代码,在下面的代码没有运行完之前点击是毫无反应的。

function a(){
    $.get('/asyncdata/userInfo',function(){
        console.log('success1');
    });
}
a();
............
............

a函数执行后,不会直接发送请求,只是将get方法的url与里面的function函数放进了事件的队列里,待后面所有的程序执行完成之后,再询问队列里有没有东西,如果有再去执行队列里的

for(var i=0;i<10;i++){
    setTimeout(function(){
        console.log(i);
    },0)
}

for循环执行10次,每循环一下就执行一次setTimeout,也就是执行了10次setTimeout,每次setTimeout执行后,只是将里面的function放进一个队列里,也就是往队列里放了10个function,
把这10个function放进队列,但都没有直接运行,等待后面所有的程序执行完成之后,再去询问列队里有没有东西,如果有再去执行,所以for循环之后,i值就变成了10,所以会输出10个10;

console.log('最先输出');//因为上述所有的函数只是进入了队列
console.log('它会第二次输出');//最后的这一句执行完成之后,js去询问队列里有没有事件,有的话就再去执行队列里的

click:

function b(){
    console.log('click事件')
}
$('a').on('click',b);

当程序读到这一行时,b方法不会执行,它会执行on方法,on方法执行后,只是将b方法放进了一个对象里,也可以简称是一个队列里,但这个里面的方法,是需要触发click的时候才去运行的。
不过点击触发之前,必须等待下面的每行代码运行完成之后,click才会起作用,如果下面的代码没有运行完,就click点击$(‘a’),b方法也是不会执行的

$.get('/asyncdata/userInfo',function(){
        console.log('success1');
 });

当程序读到这一块的时候,它不行运行,只是将它放进事件队列里,需要将下面所有的代码运行完成之后,才会去发送请求,请求完成后,才能去执行function。

这里面可以充分的体现出的是:js是一门单线程的语言,也就是说是在一条线程上运行程序。它不会 一条 线程正运行着,另外再并行一条 线程 去发送请求,如果这样的话,就变成多线程的语言了

总上述:隐含着一个意思,即触发的每个事件都会位于堆栈的最底部。

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

这就是JavaScript 运行机制。
主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。