javascript 同步、异步、事件循环

一:单线程

首先-> “JavaScript是单线程的”

我们说的单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。也可以叫它主线程。

但是实际上还存在其他的线程。例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(Node.js中)等等。这些线程可能存在于JS引擎之内,也可能存在于JS引擎之外,在此我们不做区分。我们可以叫他它工作线程。

二:同步和异步

C(csdn...);

同步:如果在函数C返回的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。

例如:

Math.sqrt(2);
console.log('csdn');

  • 第一个函数返回时,就拿到了预期的返回值:2的平方根.
  • 第二个函数返回时,就看到了预期的效果:在控制台打印了一个字符串。

异步:如果在函数C返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。

例如:

fs.readFile('foo.txt', 'utf8', function(err, data) {
    console.log(data);
});

通过fs.redaFile()函数读取文件foo.txt中的内容,并打印出来。
但是在fs.readFile()函数返回时,我们期望的结果并不会发生
,而是要等到文件全部读取完成后。如果文件大可能等很长时间。

三:异步过程的构成元素

以上可知,异步过程实际很快调用完成。但是后面还有工作线程执行异步任务、通知主线程、主线程调用回调函数等很多步骤。我们把整个过程叫做异步过程。异步函数的调用在整个异步过程中,只是一小部分。

总结:

主线程发起一个异步请求,相应的工作线程接收请求并告知主线程已收到(异步函数返回);主线程可以继续执行后面的代码,同时工作线程执行异步任务;工作线程完成工作后,通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)。

C(args...,callback)

在主线程的角度:

  • 发起函数(或叫注册函数)C
  • 回调函数callback

它们都是在主线程调用的,其中的注册函数用来发起异步过程,回调函数用来处理结果。

例如:

setTimeout(Fn,1000);

其中的setTimeout就是异步过程的发起函数,Fn是回调函数。

注意:前面说的形式C(args..., callbackFn)只是一种抽象的表示,并不代表回调函数一定要作为发起函数的参数,例如:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回调函数
xhr.open('GET', url);
xhr.send(); // 发起函数

发起函数和回调函数就是分离的。

四:消息队列和事件循环

上文讲到,异步过程中,工作线程在异步操作完成后需要通知主线程。那么这个通知机制是怎样实现的呢?答案是利用消息队列和事件循环。

用一句话概括:

工作线程将消息放到消息队列,主线程通过事件循环过程去取消息。
  • 消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。
  • 事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。

事件循环例如:

while(true){
    var messgae=queue.get();
    exexute(message);
}

简单起见:

消息就是注册异步任务是添加的回调函数。

AJAX,例如:

$.ajax('http://csdn.com', function(resp) {
    console.log('我是响应:', resp);
});

// 其他代码
...
...
...

主线程在发起AJAX请求后,会继续执行其他代码。AJAX线程负责请求csdn.com,拿到响应后,它会把响应封装成一个JavaScript对象,然后构造一条消息:

// 消息队列中的消息就长这个样子
var message = function () {
    callbackFn(response);
}

其中的callbackFn就是前面代码中得到成功响应时的回调函数。

主线程在执行完当前循环中的所有代码后,就会到消息队列取出这条消息(也就是message函数),并执行它。到此为止,就完成了工作线程对主线程的通知,回调函数也就得到了执行。如果一开始主线程就没有提供回调函数,AJAX线程在收到HTTP响应后,也就没必要通知主线程,从而也没必要往消息队列放消息。

异步过程的回调函数,一定不在当前这一轮事件循环中执行。

五:异步与事件

上文中说的“事件循环”,为什么里面有个事件呢?那是因为:

消息队列中的每条消息时间上都对应着一个事件

DOM事件

例如:

var button = document.getElement('#btn');
button.addEventListener('click', function(e) {
    console.log();
});

从事件的角度来看,上述代码表示:在按钮上添加了一个鼠标单击事件的事件监听器;当用户点击按钮时,鼠标单击事件触发,事件监听器函数被调用。

从异步过程的角度看,addEventListener函数就是异步过程的发起函数,事件监听器函数就是异步过程的回调函数。事件触发时,表示异步任务完成,会将事件监听器函数封装成一条消息放到消息队列中,等待主线程执行。

事件的概念实际上并不是必须的,事件机制实际上就是异步过程的通知机制。我觉得它的存在是为了编程接口对开发者更友好。

另一方面,所有的异步过程也都可以用事件来描述。例如:setTimeout可以看成对应一个时间到了!的事件。前文的setTimeout(fn, 1000);可以看成:

timer.addEventListener('timeout', 1000, fn);

总结:

同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性。

猜你喜欢

转载自blog.csdn.net/WCBandCTZ/article/details/84616915