setTimeout(fn,0)的解释和应用

20984次浏览

前言

关于setTimeout,我之前的文章介绍过。setTimeout我们经常用来做延迟执行,那么setTimeout(fn,0),是不是就不延迟执行了!答案肯定是否定的。这篇文章,我详细介绍一下setTimeout(fn,0)及其应用。

案例分析

for循环中有setTimeout,是我们在闭包案例中经常遇到和解决的一个问题,看一下下面这个案例

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
    console.log(i);
}

对于上面的这个题目中的for循环和setTimeout结合,我之前文章中好像有写过,但是记不清在哪里写的了!这个题目的打印结果是什么呢?

分析

对于这个题目,我们可以这么考虑,先把for循环里面的内容抽出来

   setTimeout(function() {
        console.log(i);
    }, 0);
    console.log(i);

对于这个,肯定是先执行 console.log(i),后执行setTimeout。那么,我们把上面的for循环拆解开来,如下:

var i = 0;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;

因为setTimeout是注册事件,要等到当前脚本的同步任务和“任务队列”中已有的事件,全部处理完以后,才能执行。因此,上面执行结果是把所有的console都执行完毕之后才能执行setTimeout,因此,执行结果如下:

0
1
2
3
3
3

for循环和setTimeout

这个我之前文章中好像有提及过,针对这种情况,我们将上面案例如下修改,就可以将setTimeout也可以输出1,2,3.

我们将setTimeout里面的匿名函数修改成自调用匿名函数

function() {
        console.log(i);
    }

修改成

 (function(n) {
        console.log(n);
    })(i)

因为setTimeout的第一个参数必须是fn,因此,我们要对这个匿名函数返回一个function

(function(n) {
            return function() {
                console.log(n);
            };
        })(i)

那么,代码如下:

for (var i = 0; i < 3; i++) {
    setTimeout((function(n) {
        return function() {
            console.log(n);
        };
    })(i), 0);
    console.log(i);
}

这样运行结果就是

0
1
2
0
1
2

应用

我们看下面的运行结果

console.log(1);

setTimeout(function() {
  console.log(2);
}, 0);

Promise.resolve().then(function() {
  console.log(3);
}).then(function() {
  console.log(4);
});

console.log(5);

// 1
// 5
// 3
// 4
// 2

上面代码的执行结果说明,setTimeout(fn, 0)在Promise.resolve之后执行。这是因为setTimeout语句指定的是“正常任务”,即不会在当前的Event Loop(事件循环)执行。而Promise会将它的回调函数,在状态改变后的那一轮Event Loop(事件循环)指定为微任务。所以,3和4输出在5之后、2之前。

setTimeout(fn, 0)的一大应用是,可以调整事件的发生顺序。比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,我们先让父元素的事件回调函数先发生,就要用到setTimeout(fn, 0)。

案例如下:

document.getElementById("haoroomsID").onclick = function A() {
  setTimeout(function B() {
    console.log("触发子元素事件")
  }, 0)
};

document.body.onclick = function C() {
 console.log("触发父元素事件")
};

上面案例,点击haoroomsID会先触发父级元素事件,然后再触发子元素事件。

扩展阅读

关于setTimeout(fn,0)就先写到这里,有关Event Loop(事件循环)的相关文章,请看:http://www.ruanyifeng.com/blog/2014/10/event-loop.html

Tags: jssetTimeout

相关文章: