javascript 事件顺序-JavaScript风暴循环机制

线程

由于JavaScript是单线程的,所谓单线程是指JS引擎中只有一个线程负责解释和执行JavaScript代码,可以称为主线程。

不仅是主线程,还有其他线程。 如:处理AJAX请求的线程、处理DOM扰动的线程、定时器线程、读写文件的线程(如Node.js中)等。

异步过程

一个异步流程一般是这样的:主线程发起异步请求,对应的工作线程请求并通知主线程已收到异步函数的返回; 主线程可以继续执行前面的代码。 同时,工作线程执行异步任务。 工作线程完成工作后,通知主线程; 主线程收到通知后,执行一定的工作。

异步函数一般有以下几种模式:

A(args..., callbackFn)

可以称为异步流程的启动函数,也可以称为异步任务注册函数。 args 是该函数所需的参数。 callbackFn也是该函数的一个参数,比较特殊所以单独列出。

因此,从主线程的角度来看,一个异步进程包括以下两个要素:

它们都是在主线程上调用的,其中注册的函数用于启动异步过程,反弹函数用于处理结果。

举个具体例子:

setTimeout(fn, 1000);

其中setTimeout是异步进程的发起函数,fn是反弹函数。

后面提到的方法A(args...,callbackFn)只是一个具体的表示,并不意味着必须使用反弹函数作为启动函数的参数,例如:

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

起爆功能和反弹功能分离。

消息队列和风暴循环

异步过程中javascript 事件顺序,工作线程需要在异步操作完成后通知主线程。 通知机制是怎样的? 只需使用消息队列和风暴循环即可。

工作线程将消息放入消息队列,主线程通过storm循环过程获取消息。

其实主线程只能做一件事,就是从消息队列中取出消息,执行消息,取出消息,再执行。 当消息队列为空时,会一直等待,直到消息队列变为非空。 而主线程只有执行完当前消息后才能获取下一条消息。 这些机制称为风暴周期机制,获取消息并执行消息的过程称为一个周期。

风暴周期的代码表示大致如下:

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

那么,消息队列中到底放入了什么消息呢? 消息的具体结构其实和具体实现有关,为了简单起见,我们可以认为:

该消息是注册异步任务时添加的弹跳功能。

再次以异步AJAX为例,假设存在以下代码:

$.ajax('http://segmentfault.com', function(resp) {
    console.log('我是响应:', resp);
});
// 其他代码
...
...
...

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

// 消息队列中的消息就长这个样子
var message = function () {
    callbackFn(response);
}
// 其中的callbackFn就是前面代码中得到成功响应时的回调函数。

主线程执行完当前循环中的所有代码后,就会去消息队列中取出这条消息(即消息函数)并执行。 至此,工作线程向主线程的通知已经完成,并且执行了bounce函数。 如果主线程一开始就没有提供反弹功能javascript 事件顺序,那么AJAX线程在收到HTTP响应后就不需要通知主线程,也就不需要将消息放入消息队列中。

用图形表示这个过程是:

从上面我们还可以得出一个重要的推论:异步过程的反弹函数一定不能在当前波浪周期内执行。

异步且暴风雨

上面提到的“事件循环”,为什么上面会出现扰动呢? 这是由于:

消息队列中的每条消息实际上都对应着一个storm。

上面仍然没有提到异步过程中非常重要的一类:DOM扰动。

例如:

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

从风波的角度来看,上述代码的意思是:在按钮上添加键盘点击风波的bug; 当用户点击按钮时,会触发键盘点击Fengbo,并调用Fengbo bug函数。

从异步流程的角度来看,addEventListener函数是异步流程的发起函数,风暴窃听函数是异步流程的反弹函数。 当storm被触发时,就意味着异步任务完成,storm窃听器函数会被封装成消息,放入消息队列中,等待主线程执行。

风暴的概念其实没有必要。 风暴机制实际上是异步进程的通知机制。 我认为它的存在是为了使编程套接字对开发人员更加友好。

另一方面,所有的异步过程也可以用波来描述。 比如:setTimeout就可以看做是对应一个时间即将到来! 动荡。 前面的setTimeout(fn,1000); 可以看作:

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

了解JS代码的执行1

console.log('start');
// Timer1
setTimeout(function() {
    console.log('hello');
},200);
// Timer2
setTimeout(function() {
    console.log('world');
},100);
console.log('end');

让我们逐步完成这个过程。

栗子2

console.log(1);
// Time1
setTimeout(function() {
    console.log(2);
},300);
// Time2
setTimeout(function() {
    console.log(3)
},400);
for (var i = 0;i<10000;i++) {
    console.log(4);
}
// Time3
setTimeout(function() {
    console.log(5);
},100);

首先复制1,然后复制10000个4。 那么Time1、Time2、Time3的顺序是什么呢?

在这段代码中,for循环持续的时间相对较长。 Time1和Timer加入执行队列后,主线程仍在执行for循环中的代码,处于阻塞状态。 队列中的Time1和Time2不会被执行。 当for循环结束时,Time3交给Timer模块管理,并清空执行栈。 看来这里Time3的延迟时间最短,加入任务队列后,仍然会排在Time1和Time2的前面,所以此时任务队列中的代码是按顺序执行的,复制2、3 ,依次为 5 个。

栗子3

console.log(1);
//Time2
setTimeout(function() {
    console.log(3)
},400);
//Time1
setTimeout(function() {
    console.log(2);
},300);
for (var i = 0;i<10000;i++) {
    console.log(4);
}
//Time3
setTimeout(function() {
    console.log(5);
},100);

让我们交换 Time1 和 Time2 的顺序。 根据上面的说法,Time2首先被添加到任务队列中,然后是Time1,然后是Time3。 而执行结果仍然是1,4,2,3,5,这是为什么呢? 事实上,Time1的执行时间很短,并且比Time2更晚加入任务队列。

为了验证这个问题,我们可以提出这样一个假设:

如果添加到队列中的setTimeout的阻塞时间小于两个setTimeout的执行间隔,则最先添加到任务队列中的setTimeout会先被执行,尽管其上设置的时间大于另一个setTimeout的时间。

验证1

// Time2
setTimeout(function() {
    console.log(2);
},400);
var start=new Date();
for (var i = 0;i<5000;i++) {
    console.log('这里只是模拟一个耗时操作');
};
var end=new Date();
console.log('阻塞耗时:'+Number(end-start)+'毫秒');
// Time1
setTimeout(function() {
    console.log(3)
},300);

Time1比Time2设定的执行时间早了100ms,但Time2先被添加到任务队列中,并且Time2和Time1之间有一个阻塞的for循环。

验证2

setTimeout(function() {
    console.log(2);
},400);
var start=new Date();
for (var i = 0;i<500;i++) {
    console.log('这里只是模拟一个耗时操作');
};
var end=new Date();
console.log('阻塞耗时:'+Number(end-start)+'毫秒');
// Time1
setTimeout(function() {
    console.log(3)
},300);

此时Time1先执行,因为阻塞时长大于Time1和Time2之间的执行间隔100纳秒;

验证3

我们再验证一下,设置Time2的执行时间为350纳秒;

// Time2
setTimeout(function() {
    console.log(2);
},350);
var start=new Date();
for (var i = 0;i<500;i++) {
    console.log('这里只是模拟一个耗时操作');
};
var end=new Date();
console.log('阻塞耗时:'+Number(end-start)+'毫秒');
//Time1
setTimeout(function() {
    console.log(3)
},300);

直接结果就是:

Time2先执行,因为阻塞时间小于两次setTimeout之间的间隔。

通过前面的假设,我们可以得出这样的推论:如果添加到队列中的setTimeout的阻塞时间小于两个setTimeout的执行间隔,那么最先添加到任务队列中的会先被执行,尽管它上设置的时间可能比其他setTimeout 的时间长。 做大一点。

参考文章

使用setTimeout来谈论Events

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 javascript javascript 事件顺序-JavaScript风暴循环机制 https://www.wkzy.net/game/195352.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务