澄清IE中clearTimeout的行为

4
我有以下代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
    </head>
<body>
<div id="logger"></div>
<script>

function log(txt) {
    document.getElementById('logger').innerHTML += txt + '<br>';
}

var int = 10;
var a= setTimeout(function(){
    a = null;
    log("A fired!");
    clearTimeout(b);
    b = null;
}, int);

var b = setTimeout(function(){
    b = null;
    log("B fired!");
    clearTimeout(a);
    a = null;
}, int);


</script>

</body>
</html>

两个超时回调都应该防止另一个触发。在Opera、FF和Chrome中,只有第一个(打印“A fired”的)被执行。但是当我在IE6和IE8中运行相同的代码时,两个回调都被执行。这是我的脚本中的错误还是这些浏览器充满了错误之一?clearTimeout()/clearInterval()能保证在调用它们后不会调用回调函数吗?


正如@qwerty所指出的那样,您对var avar b的赋值非常接近,我认为稍微慢一些的IE JavaScript引擎需要更长时间来完成您的log语句,因此clearTimeout调用发生在函数已经被调用之后。当分配var b时,尝试使用2 * int作为setTimeout调用,您将不会看到它被调用,因为有足够的延迟来完成var a调用并清除超时。 - Dave Anderson
尝试使用“2 * int”来调用setTimeout —— 这样做不太好 :) 在复杂的脚本中,您无法保证超时不会同时触发。 - Egorinsk
@Egorinsk:也许你可以更改这个问题的已接受答案? - robocat
@robocat,由于您的答案得分更高,我已更改了接受的答案。 - Egorinsk
2个回答

3
我认为正在发生的是:
  • JavaScript有一个事件队列。
  • IE处理超时,将两个事件排队。
  • 第一个超时事件被处理,并为B调用clearTimeout。但是B的事件已经排队,所以它仍然会被触发。
  • 第二个超时事件被处理,并为A调用clearTimeout。
我怀疑在IE中,事件被排队,调用clearTimeout不会从事件队列中删除事件。
也可能是IE如何将同时超时推入队列中存在一些奇怪的问题......通过使用两个不同的超时时间、使用100% CPU处理循环x时间,并将其他事件排队/插槽(可以使用window.postMessage()将事件注入队列并使用window.onMessage()捕获它们)来诊断根本原因。
我修改了您现有的代码,以更好地演示问题。它将日志项目排队而不是立即执行它们,因为调用display()可能会导致布局或渲染发生,这很容易引入其他奇怪的干扰。

编辑:您可以使用 http://jsbin.com/ucukez/2 进行测试 - 如果浏览器存在故障,则会出现“在A timeout fired”“在B timeout fired”。

编辑:这在IE9中已修复 - 我无法在IE9 / IE10 / IE11中重现。

HTML代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>setTimeout queued test</title>
    <script>
        function display(txt) {
            document.getElementById('logger').innerHTML += txt + '<br>';
        }

        var log = {
            items: [],
            push: function(text) {
                this.items.push(text);
            },
            display: function() {
                var items = this.items;
                this.items = [];
                for (var i = 0; i < items.length; i++) {
                    display(items[i]);
                }
            }
        };

        function startTest() {
            var ms = 10;

            display("startTest()");
            log.push('before A setTimeout');
            var a = setTimeout(function(){
                log.push('in A timeout fired');
                display("A fired!");
                log.push('in A clear timer B');
                clearTimeout(b);
                log.push('in A cleared timer B');
            }, ms);
            log.push('after A setTimeout');

            log.push('before B setTimeout');
            var b = setTimeout(function(){
                log.push('in B timeout fired');
                display("B fired!");
                log.push('in B clear timer A');
                clearTimeout(a);
                log.push('in B cleared timer A');
            }, ms);
            log.push('after B setTimeout');

            setTimeout(function(){
                display("");
                display("Displaying logged items:");
                log.display();
            },1000);
        }
    </script>
</head>
<body onload="startTest()">
    <div id="logger"></div>
</body>
</html>

谢谢您的回答。这似乎是IE的许多怪异之一,所以我想这里唯一的解决方法就是添加一些标志并在运行某些操作之前检查它们。 - Egorinsk

0

你将超时设置为完全相同的时间,由于它们都是分叉进程,因此会得到不一致的结果。

你最好先像这样测试超时是否仍然有效:

var int = 10;
var a= setTimeout(function(){
    if (!a) return;
    a = null;
    log("A fired!");
    clearTimeout(b);
    b = null;
}, int);

var b = setTimeout(function(){
    if (!b) return;
    b = null;
    log("B fired!");
    clearTimeout(a);
    a = null;
}, int);

2
但是JavaScript是单线程的,这两个函数不能同时运行!这很奇怪。但是你的代码似乎修复了这个错误。 - Egorinsk
1
@Egorinsk:我同意,但这是微软的方式。另外,如果你觉得我回答了你的问题,你可以点击我答案旁边的绿色勾选。 - qwertymk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接