我最近遇到了一个相当严重的错误,其中代码通过JavaScript动态加载<select>。这个动态加载的<select>有一个预先选定的值。在IE6中,我们已经有代码来修复选中的<option>,因为有时<select>的selectedIndex值会与选中的<option>的index属性不同步,如下所示:

field.selectedIndex = element.index;

然而,这段代码没有工作。即使字段的selectedIndex设置正确,最终还是会选择错误的索引。但是,如果我在正确的时间插入alert()语句,则会选择正确的选项。考虑到这可能是某种时间问题,我尝试了一些以前在代码中见过的随机方法:

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

这招奏效了!

我找到了解决问题的方法,但我不知道为什么这能解决我的问题,这让我感到不安。有人有官方解释吗?什么浏览器问题,我避免调用我的函数“稍后”使用setTimeout()?


当前回答

如果你不想看完整个视频,这里有一个简单的解释,你需要理解的东西,为了能够理解这个问题的答案:

JavaScript是单线程的,这意味着它在运行时一次只做一件事。 但是JavaScript运行的环境可以是多线程的。例如,浏览器通常是多线程生物,也就是说,能够在同一时间做多件事情。所以他们可以运行JavaScript,同时也可以跟踪处理其他东西。

从这一点开始,我们讨论的是“浏览器中的”JavaScript。像setTimeout这样的东西确实是浏览器的东西,而不是JavaScript本身的一部分。

允许JavaScript异步运行的是多线程浏览器!除了Javascript用来放置每行代码并逐个运行的主要空间(称为调用堆栈)之外,浏览器还为Javascript提供了另一个空间来放置内容。

现在我们称另一个空间为第二个空间。

假设fn是一个函数。这里需要理解的重要一点是fn();调用不等于setTimeout(fn, 0);调用,下面将进一步解释。

不是0延迟,让我们先假设另一个延迟,例如,5000毫秒:setTimeout(fn, 5000);。重要的是要注意,这仍然是一个“函数调用”,所以它必须放在主空间上,并在完成时从主空间中删除,但请等待!我们不喜欢冗长无聊的5秒延迟。这将阻塞主空间,并且不允许JavaScript在此期间运行任何其他内容。

值得庆幸的是,这并不是浏览器设计者设计它们工作的方式。相反,这个调用(setTimeout(fn, 5000);)是立即完成的。这一点非常重要:即使有5000毫秒的延迟,这个函数调用也会在瞬间完成!接下来会发生什么?它被从主空间中移除。演出地点在哪里?(因为我们不想失去它)。您可能猜对了:浏览器听到这个调用并把它放在第二个空格上。

浏览器会跟踪5秒的延迟,一旦它过去了,它就会查看主空间,“当它是空的”,把fn();回调它。这就是setTimeout的工作方式。

回到setTimeout(fn, 0),即使延迟为0,这仍然是对浏览器的调用,浏览器会立即听到它并接收它,把它放在第二个空间上,只有当主空间再次为空时才把它放回主空间,而不是真正的0毫秒后。

我真的建议大家也去看看那个视频,因为他解释得非常好,而且更多地讲解了技术方面的东西。

其他回答

Javascript是单线程应用程序,因此不允许同时运行函数,因此使用事件循环来实现此目标。setTimeout(fn, 0)所做的就是在调用栈为空时将它推入任务任务。我知道这个解释很无聊,所以我建议你看一下这个视频,这将帮助你在浏览器中如何工作。 看看这个视频:- https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

通过调用setTimeout,您可以给页面时间来响应用户正在做的任何事情。这对于在页面加载期间运行的函数特别有用。

setTimout on 0在设置一个延迟承诺的模式中也非常有用,你想要立即返回:

myObject.prototype.myMethodDeferred = function() {
    var deferredObject = $.Deferred();
    var that = this;  // Because setTimeout won't work right with this
    setTimeout(function() { 
        return myMethodActualWork.call(that, deferredObject);
    }, 0);
    return deferredObject.promise();
}

由于传递给它的持续时间为0,我认为这是为了从执行流中删除传递给setTimeout的代码。因此,如果它是一个可能需要一段时间的函数,它不会阻止后续代码的执行。

问题是您试图在一个不存在的元素上执行Javascript操作。元素还没有被加载,setTimeout()给了一个元素更多的时间,以以下方式加载:

setTimeout() causes the event to be ansynchronous therefore being executed after all the synchronous code, giving your element more time to load. Asynchronous callbacks like the callback in setTimeout() are placed in the event queue and put on the stack by the event loop after the stack of synchronous code is empty. The value 0 for ms as a second argument in function setTimeout() is often slightly higher (4-10ms depending on browser). This slightly higher time needed for executing the setTimeout() callbacks is caused by the amount of 'ticks' (where a tick is pushing a callback on the stack if stack is empty) of the event loop. Because of performance and battery life reasons the amount of ticks in the event loop are restricted to a certain amount less than 1000 times per second.