zoukankan      html  css  js  c++  java
  • 深入理解JavaScript定时器

    对于浏览器内部,大部分操作都是异步的生成事件并添加到JavaScript引擎线程的队列中,然后由JavaScript引擎线程进行调度执行。因此浏览器的很多事件都是和JavaScript相结合的,但是也有一些内部的限制。

    首先我们非常确定JavaScript是单线程的,对于浏览器来说,一个窗体中只有一个JavaScript引擎线程。而其他的行为,如:渲染、下载等是由单独的线程进行管理的,且具有不同的优先级。

    异步事件

    前面提到大多数事件都是异步的,触发的时候就将回调函数添加到事件队列。浏览器提供了一个内部的回路,也就是之前所谈到的Event Loop,由它来负责检查队列和处理事件、执行函数等。详细可参考我的前一篇博文。而setTimeoutsetInterval也是将其需要执行的函数添加到事件队列。

    事实上,大多数交互和活动都得通过事件循环。

    事件重叠

    一些情况下,会有多个事件在同一时间附加到事件队列里。

    比如,click事件就会产生两个额外的事件:mousedownmouseup。其中,mouseupclick事件会同时被添加到事件队列;而mousedown事件则很有可能会和另外一个事件重叠:focus

    setTimeout(func, 0)奇巧淫技

    再一次解释关于0ms的误解:如果当前时钟周期内执行队列空闲,则立即执行该定时器,将回调函数加入到事件队列;然后等待下一个时钟周期,再执行该回调函数。不妨来看看下面的测试。

    这段代码在我的浏览器中执行结果如下:

    在我本地的Nodejs环境中执行结果如下:

    上面的这个测试只是想说明setTimeout(func, 0)定时任务的回调函数执行时间是有延迟的,而并不是所谓的立即执行。

    因此,我们可以利用setTimeout(func, 0)来解决事件重叠所产生的负面效果,修正执行顺序。

    奇巧淫技之一:模拟浏览器的事件捕获

    众所周知,浏览器的DOM事件都是采用冒泡的方式,只有个别浏览器是支持事件捕获的。而在实际的开发过程中可能存在需要事件捕获的需求,要求子元素的事件在父元素触发之后才能触发。为了兼容各个浏览器,我们不能使用事件捕获,而setTimeout(func, 0)在这个时候就很乐意帮忙了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    <input type="button" value="click" id="cbtn">
    <div id="result"></div>
    <script type="text/javascript">
      var cbtn = document.getElementById('cbtn')
        , result = document.getElementById('result');
    
      cbtn.onclick = function(e) {
        setTimeout(function() {
          result.innerHTML += 'input click, ';
        }, 0);
      };
    
      document.body.onclick = function(e) {
        result.innerHTML += 'body click -> ';
      };
    </script>
    


    点击查看运行效果:

    奇巧淫技之二:让浏览器更好的工作

    大多数情况下,我们可以在浏览器的默认行为之前对事件进行处理,但是有时我们按照常规的思路去做的时候,往往事与愿违。比如下面的例子。

    1
    2
    3
    4
    5
    6
    7
    
    <input type="text" id="wordInput">
    <script type="text/javascript">
      var wordInput = document.getElementById('wordInput');
      wordInput.onkeypress = function(e) {
        this.value = this.value.toUpperCase();
      };
    </script>
    


    看似一个很简单的需求:每输入一个字符,就将其转换为大写。但是上面的代码完全没有按照指示去做,不信你试试看:

    如果没有下一次输入,文本框中的小写字母永远都不会转换为大写。Why? 因为浏览器在keypress事件处理的时候,还没有将我们输入的值添加到文本框。于是乎换一个事件来handle然后再处理吧,既然键按下的时候还木有值,那就等键弹起来之后再处理。

    1
    2
    3
    4
    5
    6
    7
    
    <input type="text" id="wordInput">
    <script type="text/javascript">
      var wordInput = document.getElementById('wordInput');
      wordInput.onkeyup = function(e) {
        this.value = this.value.toUpperCase();
      };
    </script>
    


    运行试试吧。

    大概似乎是可行了,可是仔细观察就看出问题了。keyup事件触发时,文本框已经具备完整的值了,但先是一个小写的值,键完全释放之后转变为大写。这不科学…这太丑陋...

    是时候关门放出setTimeout(func, 0)了。。。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    <input type="text" id="wordInput">
    <script type="text/javascript">
      var wordInput = document.getElementById('wordInput');
      wordInput.onkeypress = function(e) {
        var self = this;
        setTimeout(function() {
          self.value = self.value.toUpperCase();
        }, 0);
      };
    </script>
    

    已经完美了。keypress事件触发时,将转换大写的操作添加到事件队列,紧接着浏览器添加我们输入的值,然后近乎0延迟的执行我们的转换大写操作函数。

    上面两个小案例只是冰山一角,so...合理利用setTimeout(func, 0),明天更美好!

  • 相关阅读:
    DTD和Schema的区别
    在使用Maven中出现的小错误
    struts2 中 paramsPrepareParamsStack 拦截器
    Hibernate的save()和persist()的区别
    Spring学习笔记
    Hello Spring
    Hibernate3 和Hibernate4 在配置文件上的区别
    今日学习-商品数据库查询
    Java中避免表单重复提交
    Java学习笔记
  • 原文地址:https://www.cnblogs.com/zhepama/p/3077053.html
Copyright © 2011-2022 走看看