你不知道的JavaScript:有趣的setTimeout

2018-5-7 Mechau 前端技术资源

     话不多说,先上代码

     for(var j=0;j<10;J++){

         setTimeout(function(){console.log(j);},5000)

     }

     看到这三行代码,你也许会不耐烦道:又要讲闭包?要吐槽了好么?别急,让我们先来思考一下,这段代码在浏览器中的执行结果是什么?

     <!-- more -->

     甲:顺序打印0到9?

     乙:这题我见过,打印十个10!

     哪个答案正确?

     执行结果显示,浏览器打印出十个10,貌似乙对了,但是如果你足够细心,你会发现几个问题:为什么会循环打印十个10,而不是0到9?

     从结果来看,for循环执行完跳出之后,才开始执行setTimeout(所以j才等于10),为什么不是每次迭代都执行一次setTimeout呢?

1、为什么会循环打印十个10?

      许多人习惯用第二个问题中的执行结果来回答这个问题:“for循环执行完毕跳出之后才开始执行setTimeout,所以才打印了十个10”。这样的答案,只能说是既应付了自己,又应付了别人。其实,要解答第一个问题,首先要解答第二个问题。

2、为什么不是每一次迭代都执行一次setTimeout?

     大家都知道,JavaScript在ES6出现以前,是没有块状作用域的,这就意味着,在for循环中用var定义的变量j,其实是属于全局的,那其实整个全局作用域中只有一个j,每次for循环都是更新这个j。

       那么现在的关键问题在于,为什么整个for循环会先于setTimeout执行,而不是我们正常理解的,一次迭代执行一次。这就涉及到了JavaScript的核心特性:单线程。

       JavaScript设计的初衷,是浏览器用来与用户进行交互和DOM操作的,这就决定了它必须是单线程的。设想JavaScript同时有两个线程,一个线程在DOM节点内添加内容,一个线程删除该节点,浏览器就会出现混乱。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成为了这门语言的核心特征,将来也不会改变。

      单线程就意味着,所有任务需要排队,前一个任务结束,才会执行下一个任务,如果前一个任务耗时很长,后一个任务就不得不一直等着。

      为了优化单线程的性能,JavaScript将任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程,而进入“任务队列(task queue)”的任务,只用主线程中的同步任务执行完毕,异步任务才会进入执行队列执行。只要主线程空了,就会去读取“任务队列”,这就是JavaScript的运作机制。这个过程会不断重复。

       而setTimeout就被JavaScript定义为异步任务。每次for循环的迭代,都将setTimeout中的回调函数加入任务队列等待执行。也就是说,只有同步任务中的for循环完全结束,主线程中才会去任务列表中找到尚未执行的十个setTimeout(十次迭代)回调函数并顺序执行(先进先出)。而此时,j已经经过循环结束变成了10,所以此时主线程执行的,是十个一模一样的打印i的回调函数,即打印十个10,。至此完美回答了第一和第二个问题,文章开头的代码与下面的代码其实是等价的:

       for(var i=0;i<10;i++){

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

}

       小小的一个setTimeout,牵扯出了很多JavaScript的深层次问题,可见JavaScript还有许多地方是值得深入探究的。



标签: javascript 单线程


Powered by emlog 京ICP备12006971号-1 sitemap