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 发布文章使用:只允许注册用户才可以访问!


  • 相关阅读:
    我与酷派手机N900+的艰难之旅
    从ASPNET1.1迁移到了ASPNET2.0遇到ORA01000: 超出打开游标的最大数
    彩铃平台接口独立取得阶段性胜利
    中央音乐平台的MD5算法问题
    彩铃接口独立完毕
    'OraOLEDB.Oracle.1' provider is not registered on the local machine.错误的解决
    程序员的陷阱
    ASP NET页面下载程序
    从aspnet1.1升级到aspnet2.0的小问题
    彩铃平台接口的计费服务问题解决
  • 原文地址:https://www.cnblogs.com/xgqfrms/p/11399442.html
Copyright © 2011-2022 走看看