JS的运行机制总结:
- 1、js的单线程:也就是说一次只能做一件事。js的单线程有好处也有坏处,好处就是,当用户在与页面进行交互时,假设同时对某一个DOM进行删除和新增操作,那么浏览器不知道以哪一个为准,这时js的单线程操作就避免了这样的问题;坏处就是,如果前面一个任务耗时很长或者阻塞了,那么后面的任务都无法继续进行,必须等前面进程完成之后才可以进行后面的操作。
- 2、任务队列:基于js单线程存在的问题,就把任务分成了同步任务和异步任务。同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕才能后面的任务;异步任务是指不进入主线程而进入‘任务队列’的任务,只有任务队列通知主线程可以执行了,某个异步任务可以执行了,该异步任务才进入主线程进行执行。(在所有同步任务执行完之前,异步任务都不会执行)
- 3、Event Loop:1、所有同步任务都在主线程上执行,形成一个执行栈;2、主线程之外,还存在一个任务队列,只有异步任务有了运行结果,就在任务队列中放置一个事件;3、一旦执行栈中的所有同步任务都执行完毕,系统会读取任务队列,看看里面有哪些事件,那些对应的异步任务就会进如执行栈执行。主线程不断重复这三个步骤就是整个事件循环。只要主线程空了,就会去读取任务队列,这就是js的运行机制。
- 4、异步任务:1、setTimeout/setInterval;2、DOM事件;3、Promise;4、Ajax异步请求。
for(var i=0;i<5,i++>){ setTimeout(function(){ console.log(i) },1000) } //for循环一次碰到一个setTimeout(),并不是马上把setTimeout拿到异步任务中,而要等到1秒之后, //才将其放到任务队列中。一旦执行栈中所有同步任务执行完毕(此时for循环结束,i为5), //系统读取任务队列中存放的setTimeout(有5个),于是会一次打印出5个5。 //5,5,5,5,5 //如果要将上述改成输入1,2,3,4,5 1、将var改成let 2、将setTimeout外层包一个立即执行函数 for(var i=0;i<5,i++>){ (function(){ setTimeout(function(){ console.log(i) },1000) })(i) } 3、闭包 for(var i=0;i<5,i++>){ var fn = function(){ var j=i; setTimeout(function(){ console.log(j) },1000) } fn() }
setTimeout存在的问题:在实现延时多少秒的操作时,我们往往会发现,延时的时间比实际的时间多。
setTimeout(()=>{
console.log('延时3s)
task()
},3000)
//1、明明写的延时3秒,实际却5,6秒才执行函数
我们知道setTimeout是经过3000s将需要执行的任务放到任务队列中,
又因为同步任务需要一个一个执行,等同步任务执行完毕之后才会
去读取任务队列中的任务,如果同步任务消耗的时间过长,那么就
会导致真正的延迟远远大于3s。
解决的办法就是获取到同步任务开始的时间和结束时间差,
然后用3s减去时间差,就可以得到延时器实际的时间。
//2、我们经常会遇到setTimeout(fn,0)这种写法。这样做实际意思是
等主线程上同步任务都执行完毕之后会立即执行这个任务。
-
5、宏任务/微任务:除了广义的同步任务和异步任务,还可以将任务分为宏任务和微任务。
宏任务(macro-task):整体javascript代码,setTimeout,setInterval 微任务(micro-task):Promise,process.nextTick- 1、不同类型的任务会进入对应的任务队列,比如setTimeout和setInterval会进入相同的任务队列
- 2、事件循环的顺序,决定js的执行顺序:首先是进入整体代码(宏任务)后,开始第一次循环,接着执行所有的微任务,然后再从宏任务开始,找到其中一个任务队列执行完毕,再执行所有微任务。(宏任务->微任务->宏任务->微任务...)
setTimeout(function() { console.log('setTimeout'); }) new Promise(function(resolve) { console.log('promise'); }).then(function() { console.log('then'); }) //打印结果依次是:promise then setTimeout //首先第一次整体代码(宏任务),遇到setTimeout,加入宏任务事件队列, 遇到Promise,立即打印promise,并将then函数加入微任务事件队列, 第一次宏任务执行完毕,查看微任务有then函数,执行第一次微任务, 打印then,第一次循环结束;第二次,宏任务有setTimeout,打印setTimeout//题目 console.log('1'); setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) }) //依次打印 1 7 6 8 2 4 3 5 9 11 10 12