JS运行机制

220 阅读4分钟

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