函数调用之间的javascript延迟数组中的每个索引

时间:2012-10-17 03:10:15

标签: javascript jquery

目标:

  • 对数组中的每个元素执行逻辑。
  • 在下次执行之间等待X ms。
  • mouseover(#slider)暂停延迟 - 如果延迟= 1000毫秒,并且已经过了300毫秒,mouseout(#slider)将触发恢复倒计时剩余的700毫秒延迟。
  • 在最后一个元素上执行后,循环返回再做一次 - 永远。

以下是一个直观的解释:

var = s Array(1,2,3)

var x = s[1];   //get first element   
console.log(x); //do something to it
wait();         //START wait timer 1000ms

//------------> timer : 300ms
//------------> user  : mouseover (#slider) : pause timer
//------------> user  : waited 5000ms
//------------> user  : mouseout  (#slider) : resume timer
//------------> timer : 300ms --> still 700ms to go!
//------------> timer : 500ms
//------------> user  : mouseover (#slider) : pause timer
//------------> user  : waited 10000ms
//------------> user  : mouseout  (#slider) : resume timer
//------------> timer : 500ms --> still 500ms to go!

var x = s[2];   //get second element   
console.log(x); //do something to it
wait();         //START wait timer 1000ms

//------------> timer : 200ms
//------------> user  : mouseover (#slider) : pause timer
//------------> user  : onclick   (.slideButton1) : change index of array and clear timer
//------------> user  : waited 6000ms
//------------> user  : mouseout  (#slider) : resume timer
//------------> timer : 0ms --> still 1000ms to go!

var x = s[1];   //get first element   ( index was changed by clicking button )
console.log(x); //do something to it
wait();         //START wait timer 1000ms

// ... s[2] ... s[3] ...
//theres nothing else in the array, lets start back from s[1] again!

解决方案:

jQuery的:

http://jsfiddle.net/zGd8a/8/

此解决方案来自相关的post。可以找到此插件的官方来源here

原生JS:

http://jsfiddle.net/SyTFZ/4/

Answer Aadit M Shah 非常有帮助。他还详细介绍了 Delta Timing 以及它在类似案例中的用途。

新目标:

提取其中任何一种方法,以便用于其他事情。

3 个答案:

答案 0 :(得分:6)

好了,既然你已经完全简化了问题,这里是一个通用数组迭代器函数,它在数组的每个元素的迭代之间放置一个延迟,并且它会一直循环,直到回调函数返回false:< / p>

function iterateArrayWithDelay(arr, delay, fn) {
    var index = 0;

    function next() {
        // protect against empty array
        if (!arr.length) {
            return;
        }

        // see if we need to wrap the index
        if (index >= arr.length) {
            index = 0;
        }

        // call the callback
        if (fn(arr[index], arr, index) === false) {
            // stop iterating
            return;
        }

        ++index;

        // schedule next iteration
        setTimeout(next, delay);
    }
    // start the iteration
    next();
}

而且,对于您的示例,您可以像这样使用它:

iterateArrayWithDelay(s, 1000, myFunction);

myFunction定义为处理每个元素的回调函数。回调传递了三个项目:

myFunction(item, array, i){
    // your code here to process item
}

.delay()仅适用于使用动画队列的jQuery方法。在你的代码示例中,.delay('1000')没有做任何事情,因为在同一个对象之后没有jQuery动画方法。

至于内存泄漏,很难跟踪你正在做的事情的整体上下文,因为我们无法看到this所代表的对象的生命周期及其属性。这个序列看起来很奇怪:

var x = t.s[i];
...
delete t.s[i];
t.s.push(x);

特别是,我没有看到delete语句实际上是如何做的,因为你仍然引用x中的内容,所以什么都不会被垃圾收集。此外,javascript中的delete用于删除对象属性,而不是用于释放对象或删除数组元素。要释放对象,您必须删除对该对象的所有引用(将它们设置为其他值,以便它们不再包含引用或让它们超出范围)。因此,既然你永远不会删除对t.s[i]中任何内容的引用,那么什么都不会被释放。


使用setTimeout()不会导致递归。当您调用setTimeout()时,它会设置一个计时器并将函数引用放入与该计时器关联的数据结构中。然后,调用函数继续运行并完成它的执行。因此,它在setTimeout()触发之前完成执行并再次调用它。所以,它实际上不是递归。它是一系列顺序函数调用,以时间间隔分隔,一个在下一个可以运行之前完成(因为javascript是单线程的,因为计时器是为将来设置的)。

答案 1 :(得分:4)

好吧,我不使用jquery,并且很可能我不知道你想要实现什么。但是根据我的理解,我认为你应该这样做:

var i = 0;
var t = this;

var timer = new DeltaTimer(function (time) {
    // your animation
    var x = t.s[i];
    x.delay("1000").css("background-color", "#FAAF16");
    delete t.s[i];
    t.s.push(x);
    // increment i
    i++;
}, 1000);

var start = timer.start();

你会注意到我使用了一个名为DeltaTimer的构造函数。此构造函数在此gist中定义。它允许您使用startstop函数精确控制动画。传递的render函数被赋予time参数,该参数是Date。表达式time - start给出了调用函数的确切时间(例如410002000,...)。

使用DeltaTimer优于setTimeoutsetInterval的优势是:

  1. corrects itself。这意味着动画更平滑,滞后更少。
  2. 可以通过启动和停止计时器来控制动画。
  3. 函数调用的确切时间传递给函数。这有助于跟踪正在渲染的帧,渲染精灵的位置等等。
  4. 动画的逻辑与时序控制的逻辑分开。因此,代码更具凝聚力,更松散地耦合。
  5. 您可以阅读有关增量时间hereherehere的其他答案。

    编辑1:这实际上非常简单。我们只是移出数组的第一个元素,处理它然后在最后将其推回。这是逻辑:

    function loopIterate(array, callback, interval) {
        var timer = new DeltaTimer(function (time) {
            var element = array.shift();
            callback(element, time - start);
            array.push(element);
        }, interval);
    
        var start = timer.start();
    };
    

    现在我们可以创建一个数组并循环遍历它:

    var body = document.body;
    
    loopIterate([1, 2, 3], function (element, time) {
        body.innerHTML += element + ": " + time + "<br/>";
    }, 1000);
    

    您可以在此处查看输出:http://jsfiddle.net/aGQfr/

    编辑2:哎呀,我发现了一个问题。根据我的理解,你希望在之后当前元素完成处理时,处理下一个元素。我的delta计时脚本不会这样做。它只以固定的时间间隔执行功能。

    因此,您根本不需要增量计时。您需要在处理完每个元素后调用setTimeout

    function loopIterate(array, callback, interval) {
        var start = + new Date;
        process();
    
        function process() {
            var element = array.shift();
            callback(element, new Date - start);
            array.push(element);
    
            setTimeout(process, interval);
        }
    };
    

    之后只需创建一个数组并循环遍历它:

    loopIterate([1, 2, 3], function (element, time) {
        alert(element);
    }, 1000);
    

    您可以在此处查看演示(请注意您的浏览器可能不喜欢它):http://jsfiddle.net/aGQfr/1/

    编辑3:您还可以合并方法一和二,以便您拥有以下脚本:

    1. 在将下一个要处理的元素添加到事件队列之前,等待处理完成。
    2. 可以使用startstop功能进行控制。
    3. 给出回调被调用的确切时间。
    4. 将处理与时间控制分开。
    5. 我们将创建一个名为LoopIterator的构造函数,它返回一个带有startstop方法的迭代器对象:

      function LoopIterator(array, callback, interval) {
          var start, iterate, timeout;
      
          this.start = function () {
              if (!iterate) {
                  start = + new Date;
                  iterate = true;
                  loop();
              }
          };
      
          this.stop = function () {
              if (iterate) {
                  clearTimeout(timeout);
                  iterate = false;
              }
          };
      
          function loop() {
              var element = array.shift();
              callback(element, new Date - start);
              array.push(element);
      
              if (iterate) timeout = setTimeout(loop, interval);
          }
      }
      

      现在我们可以创建并启动一个新的迭代器,如下所示:

      var iterator = new LoopIterator([1, 2, 3], function (element, time) {
          alert(element);
      }, 3000);
      
      iterator.start();
      

      如果您希望,当鼠标分别移动到元素上或元素外时,您甚至可以停止并启动迭代器:

      var div = document.getElementsByTagName("div")[0];
      div.addEventListener("mouseout", iterator.start, false);
      div.addEventListener("mouseover", iterator.stop, false);
      

      停止时,迭代器的状态将被保留,再次启动时,它将从停止的位置继续。

      您可以在此处看到演示:http://jsfiddle.net/PEcUG/

      编辑4:所以你想创建一个简单的滑块?让我们从HTML开始,然后是CSS,然后是JavaScript。

      HTML:

      <div class="slider">
          <div class="slide">Slide 1</div>
          <div class="slide">Slide 2</div>
          <div class="slide">Slide 3</div>
      </div>
      

      我们有一个名为div的{​​{1}}元素(因为页面上可能有多个滑块)。每个滑块都有零个或多个slider元素,类别为div。每张幻灯片可以包含任意内容。滑块也有按钮,但我们不在HTML中包含它,因为它将由JavaScript自动生成。没有冗余。另请注意,没有任何幻灯片是手动编号的。一切都由JavaScript处理。

      CSS:

      slide

      您可以提供符合您口味的任意CSS。但重要的一点是.slide { background-color: #EEEEEE; -moz-border-radius: 0.25em; -webkit-border-radius: 0.25em; border-radius: 0.25em; display: none; padding: 1em; } .slider-button { background-color: #CCCCCC; -moz-border-radius: 0.25em; -webkit-border-radius: 0.25em; border-radius: 0.25em; cursor: pointer; float: right; height: 1.25em; margin: 0.5em; width: 1.25em; } 必须.slide,因为最初必须隐藏幻灯片。 display: none;也必须.slider-button。这很重要,因为向右浮动的元素的顺序可以颠倒。因此,第一个按钮实际上是最后一个按钮。这必须由JavaScript正确处理,因此除非您知道自己在做什么,否则不要更改它。

      JavaScript:

      好吧,我会自下而上解释一下:

      float: right;

      这里window.addEventListener("DOMContentLoaded", function () { var sliders = document.querySelectorAll(".slider"); var length = sliders.length; for (var i = 0; i < length; i++) new Slider(sliders[i], 2000); }, false); 是一个构造函数,它初始化并启动它传递的滑块元素。它接受两个幻灯片之间的时间间隔作为第二个参数。这是Slider的代码:

      Slider

      代码非常明显。 function Slider(slider, interval) { var slides = slider.querySelectorAll(".slide"); var iterate, start, timeout, delay = interval; slides = Array.prototype.slice.call(slides); var buttons = [], numbers = [], goto = []; var length = slides.length; for (var i = 0; i < length; i++) { var button = document.createElement("div"); button.setAttribute("class", "slider-button"); slider.appendChild(button); buttons.unshift(button); numbers.push(i + 1); var handler = getHandler(length - i); button.addEventListener("click", handler, false); goto.unshift(handler); } this.goto = function (index) { var gotoSlide = goto[index]; if (typeof gotoSlide === "function") gotoSlide(); }; slider.addEventListener("mouseover", stop, false); slider.addEventListener("mouseout", start, false); this.start = start; this.stop = stop; showSlide(); start(); function start() { if (!iterate) { iterate = true; start = + new Date; timeout = setTimeout(loop, delay); } } function stop() { if (iterate) { iterate = false; clearTimeout(timeout); delay = interval - new Date + start; } } function loop() { hideSlide(); slideSlider(); showSlide(); if (iterate) { start = + new Date; timeout = setTimeout(loop, interval); } } function hideSlide() { slides[0].style.display = "none"; buttons[0].style.backgroundColor = "#CCCCCC"; } function slideSlider() { slides.push(slides.shift()); buttons.push(buttons.shift()); numbers.push(numbers.shift()); } function showSlide() { slides[0].style.display = "block"; buttons[0].style.backgroundColor = "#FAAF16"; } function getHandler(number) { return function () { hideSlide(); while (numbers[0] !== number) slideSlider(); showSlide(); }; } } 的每个实例都有Sliderstartstop方法,以便更好地控制。 goto方法采用幻灯片索引号。对于goto幻灯片,索引范围从n0。就是这样。

      滑块的演示位于:http://jsfiddle.net/SyTFZ/4/

答案 2 :(得分:1)

plugin jquery-timing可以帮助您使用非常短的代码完成此效果。 已有example with waiting on mouseover

我相信这也可以适应您的用例:

function noHover() {
   return this.is(':hover') ? this.wait('mouseleave') : this;
}

$('.images').repeat().each($).fadeIn($).wait(1000).wait(noHover).fadeOut($);