js单线程执行引起的setTimeout和ajax执行的迷之bug

问题描述

近期,在做倒计时动画时,用到了setTimeout()这个计时函数,在使用时,由于不太理解js的执行原理,光看函数的用法写出了一段代码,结果发现setTimeout的执行非常的迷,和自己想象的完全不同,行为可以用“诡异”来形容。同样的问题在我写一个ajax异步请求时,又遇到了一个神奇的执行次序,由这两个bug情景,我认识到了理解js执行原理的重要性。

情景一:

for(var i = 0;i<100;i++)
{
	setTimeout(function(){
		console.log('exeute');
    },1000);
}

以上这段代码按照我之前的理解,应该是每隔1S在控制台输出一个'exeute',但是可以按F12在控制台运行一下试试,结果是在停顿1s后,立即输出了100个'exeute',这有些难以理解。

情景二:

export default {
	data(){
		return {
			message:[]   // 这是一个数组
		}
	}
	method:{
		getMessage(){
			/*
			* 如果信息仓库里存储着信息,就直接赋值
			* 如果为空,就异步请求
			*/
			if (store.length===0) {
				axios({
					method: 'get',
					url: '/common/'
				})
					.then(function (response) {
						if (response) {
						  this.message = store = response;
						}
					})
					.catch(function (error) {
						console.log(error);
					});
			}   
			else{
				// 直接用仓库里的数据赋值
				message = store;
			}
			// 赋完值就跳转到 /newpath 新路由
			this.$router.push({path:'/newpath'});
		}
	}
}

按照预想,应该是message获得值后,就跳转到新的界面,然而事实上,当跳转到新页面后,需要的message却是空的,这种行为也是很迷。

问题分析

关于Js的单线程执行,必备的背景知识

浏览器的每一个窗口都只有一个执行js脚本的线程,在每一个特定的时间,只有一个特定的代码被执行,会阻塞其他的代码,整个执行就像一条流水线,未执行的代码加入执行队列的先后排序,浏览器会按次序执行这些代码。

但是我们在写js时,有着大量的异步事件和事件监听,如果只有一个线程执行,怎么能实现呢?

要注意,浏览器不是单线程的,在一个窗口下,通常有

一个javascript执行线程,界面渲染线程,浏览器事件触发线程,Http请求线程

其中,浏览器事件触发线程是一个内部大消息循环,会轮询事件监听队列中收到监听的信号或事件,与js执行线程是并行的,如果一个监听的事件触发比如一次点击,轮询线程就会将点击对应的处理代码加入js执行线程队列中,让它等待去执行。

理解了事件驱动的原理,再来理解setTimeout的执行:

当遇到调用setTImeout()时,js引擎会启动定时器,在大约的执行时间到了,定时器就会将setTimeout对应的代码加入js执行线程去执行,起到延时执行的效果。

最后来看看异步请求是如何实现的:

当遇到异步请求代码时,,浏览器会将请求交给上文提到的Http请求线程去发送请求,之后有一段时间的网络传输时间,请求线程就处于轮询监听状态,一旦有消息接收到,就将异步请求接收信息的处理代码加入到js执行队列中等待执行。

网络请求的延时不会对当前页面和动作的执行产生任何影响,这样就达到了异步请求的效果。

..........................................................................................................................................................................................................................................

在理解了js的相关原理后,再来看看问题描述中的情景一:

在100次循环执行

setTimeout(function(){

console.log('exeute');

    },1000);

时,浏览器会几乎瞬间的将100次对setTimeout的调用交给计时器去处理,所以这100次的打印输出都是在大约1s后加入js执行队列,得到执行,所以就出现了100个'exeute'几乎同时的打印出来 的现象。

如果想要每隔一秒打印一个'exeute',需要这样写:

for(var i = 0;i<100;i++)
{
	setTimeout(function(){
		console.log('exeute');
    },1000*i);
}

即将设定的定时时间 依次设定成 1s,2s,3s .........即可,到了需要的时间,代码就会加入js执行队列得到执行。

..........................................................................................................................................................................................................................................

情景二的分析:

执行器在遇到ajax函数时,会将请求交给Http请求线程去执行发送,等待和接收的动作,自己不会等待,只会立马执行之后的路由跳转代码,立即实现页面的跳转,当跳转完成时,请求线程还没有接收到信息,所以新页面的message数据就是空的。

要想实现在message得到数据后跳转,需要将页面跳转代码放到 ajax接收到信息后的回调函数中,这样在接收到信息就才会执行页面的跳转,到达预想的目标。


在理解js的执行原理后,如果以后遇到类似的异步请求,异步时间监听,如果遇到一些奇异的bug,首先就要回想一遍js的执行流程,这样就能正确的分析代码的执行过程,快速的debug了。


猜你喜欢

转载自blog.csdn.net/Clark_Fitz817/article/details/79313755
今日推荐