zoukankan      html  css  js  c++  java
  • xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!

    setTimeout 实现原理, 机制

    JS 执行机制说起

    浏览器(或者说 JS 引擎)执行 JS 的机制是基于事件循环。

    由于 JS 是单线程,所以同一时间只能执行一个任务,其他任务就得排队,后续任务必须等到前一个任务结束才能开始执行。

    为了避免因为某些长时间任务造成的无意义等待,JS 引入了异步的概念,用另一个线程来管理异步任务。

    同步任务直接在主线程队列中顺序执行,而异步任务会进入另一个任务队列,不会阻塞主线程;
    等到主线程队列空了(执行完了)的时候,就会去异步队列查询是否有可执行的异步任务了(异步任务通常进入异步队列之后还要等一些条件才能执行,如 ajax 请求、文件读写),如果某个异步任务可以执行了便加入主线程队列,以此循环;

    定时器也是一种异步任务,通常浏览器都有一个独立的定时器模块,定时器的延迟时间就由定时器模块来管理,当某个定时器到了可执行状态,就会被加入主线程队列。

    setTimeout 注册的函数 fn 会交给浏览器的定时器模块来管理,延迟时间到了就将 fn 加入主进程执行队列,如果队列前面还有没有执行完的代码,则又需要花一点时间等待才能执行到 fn,所以实际的延迟时间会比设置的长;

    如在 fn 之前正好有一个超级大循环,那延迟时间就不是一丁点了。

    
    
    (function testSetTimeout() {
        const label = 'setTimeout';
        console.time(label);
        setTimeout(() => {
            console.timeEnd(label);
        }, 0);
        for(let i = 0; i < 1000; i++) {
            console.log(i);
        }
    })();
    
    // setTimeout: 133.2880859375ms
    
    
    

    setInterval 的实现机制跟 setTimeout 类似,只不过 setInterval 是重复执行的。

    对于 setInterval(fn, 100) 容易产生一个误区:
    并不是上一次 fn 执行完了之后再过 100ms 才开始执行下一次 fn。 事实上,setInterval 并不管上一次 fn 的执行结果,而是每隔 100ms 就将 fn 放入主线程队列;
    而两次 fn 之间具体间隔多久就不一定了,跟 setTimeout 实际延迟时间类似,和 JS 执行情况有关。

    
    
    (function testSetInterval() {
        let i = 0;
        const start = Date.now();
        const timer = setInterval(() => {
            i += 1;
            i === 5 && clearInterval(timer);
            console.log(`第${i}次开始`, Date.now() - start);
            for(let i = 0; i < 10000; i++) {}
            console.log(`第${i}次结束`, Date.now() - start);
        }, 100);
    })();
    
    
    
    

    1. Stack 栈
    2. Queue 队列
    3. Heap 堆

    http://www.alloyteam.com/2016/05/javascript-timer/

    requestAnimationFrame

    window.requestAnimationFrame(callback);

    https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

    // animation
    
    var start = null;
    var element = document.getElementById('SomeElementYouWantToAnimate');
    
    function step(timestamp) {
      if (!start) start = timestamp;
      var progress = timestamp - start;
      element.style.transform = 'translateX(' + Math.min(progress / 10, 200) + 'px)';
      if (progress < 2000) {
        window.requestAnimationFrame(step);
      }
    }
    
    window.requestAnimationFrame(step);
    
    
    

    https://caniuse.com/#feat=requestanimationframe

    https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/dev-guides/hh920765(v=vs.85)

    https://css-tricks.com/using-requestanimationframe/

    
    function repeatOften() {
      // Do whatever
      requestAnimationFrame(repeatOften);
    }
    requestAnimationFrame(repeatOften);
    
    
    

    requestAnimationFrame demos

    https://codepen.io/xgqfrms/pen/ZEzeaEL

    https://codepen.io/xgqfrms/pen/zYOZPYd

    
    (function testRequestAnimationFrame() {
        window.count = window.count || 0;
        window.count++;
       if(count < 100) {
        	const label = 'requestAnimationFrame';
            console.time(label);
            requestAnimationFrame(() => {
                console.timeEnd(label);
                testRequestAnimationFrame()
            });
       }
    })();
    
    
    

    setImmediate (IE / Edge)

    do not use it!

    https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate

    next.tick()

    https://stackoverflow.com/questions/15349733/setimmediate-vs-nexttick

    1. 如果要将函数排在事件队列中已有的任何I / O事件回调之后,请使用setImmediate;

    2. 使用process.nextTick有效地将函数排在事件队列头部,以便在当前函数完成后立即执行;


    https://www.haorooms.com/post/js_setTimeout

    setTimeout和setinterval的最主要区别是:

    1. setTimeou t只运行一次,也就是说设定的时间到后就触发运行指定代码,运行完后即结束;
      如果运行的代码中再次运行同样的setTimeout命令,则可循环运行。(即 要循环运行,需函数自身再次调用 setTimeout())

    2. setinterval是循环运行的,即每到设定时间间隔就触发指定代码; 这是真正的定时器。

    setinterval使用简单,而setTimeout则比较灵活,可以随时退出循环,而且可以设置为按不固定的时间间隔来运行,比如第一次1秒,第二次2秒,第三次3秒。

    事件循环模型

    在单线程的 Javascript 引擎中,setTimeout() 是如何运行的呢,这里就要提到浏览器内核中的事件循环模型了;
    简单的讲,在 Javascript 执行引擎之外,有一个任务队列,当在代码中调用 setTimeout() 方法时,注册的延时方法会交由浏览器内核其他模块(以 webkit 为例,是 webcore 模块)处理,当延时方法到达触发条件,即到达设置的延时时间时,这一延时方法被添加至任务队列里;
    这一过程由浏览器内核其他模块处理,与执行引擎主线程独立,执行引擎在主线程方法执行完毕,到达空闲状态时,会从任务队列中顺序获取任务来执行;
    这一过程是一个不断循环的过程,称为事件循环模型。

    Javascript 执行引擎的主线程运行的时候,产生堆(heap)和栈(stack);
    程序中代码依次进入栈中等待执行,当调用 setTimeout() 方法时,即图中右侧 WebAPIs 方法时,浏览器内核相应模块开始延时方法的处理,
    当延时方法到达触发条件时,方法被添加到用于回调的任务队列,只要执行引擎栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些满足触发条件的回调函数。

    http://www.alloyteam.com/2015/10/turning-to-javascript-series-from-settimeout-said-the-event-loop-model/

    https://www.ruanyifeng.com/blog/2014/10/event-loop.html


    Flag Counter

    ©xgqfrms 2012-2020

    www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!


  • 相关阅读:
    HDU 5313 bitset优化背包
    bzoj 2595 斯坦纳树
    COJ 1287 求匹配串在模式串中出现的次数
    HDU 5381 The sum of gcd
    POJ 1739
    HDU 3377 插头dp
    HDU 1693 二进制表示的简单插头dp
    HDU 5353
    URAL 1519 基础插头DP
    UVA 10294 等价类计数
  • 原文地址:https://www.cnblogs.com/xgqfrms/p/11399442.html
Copyright © 2011-2022 走看看