setTimeout的误区

很多人对 setTimeout 函数的理解就是:延时为 n 的话,函数会在 n 毫秒之后执行。事实上并非如此,这里会存在几个问题:

setTimeout 函数的及时性问题

  1. var d = new Date, count = 0, f, timer;
  2. timer = setInterval(f = function (){
  3. if(new Date - d > 1000)
  4. clearInterval(timer), console.log(count);
  5. count++;
  6. }, 0);

可以看出 1s 中运行的次数大概在 200次 左右,有人会说那是因为 new Date 和 函数作用域的转换消耗了时间,其实不是,而是setInterval 和 setTimeout 函数运转的最短周期是 5ms 左右,这个数值在 HTML规范 中也是有提到的:

  1. 5. Let timeout be the second method argument, or zero if the argument was omitted.
  2. 如果 timeout 参数没有写,默认为 0
  3. 7. If nesting level is greater than 5, and timeout is less than 4, then increase timeout to 4.
  4. 如果嵌套的层次大于 5 ,并且 timeout 设置的数值小于 4 则直接取 4.

如果需要更加短的周期,可以使用:

  • requestAnimationFrame 它允许 JavaScript 以 60+帧/s 的速度处理动画,他的运行时间间隔比 setTimeout 是要短很多的。
  • process.nextTick 这个是 NodeJS 中的一个函数,利用他可以几乎达到上面看到的 while 循环的效率
  • ajax 或者 插入节点 的 readState 变化
  • MutationObserver
  • setImmediate

    会被阻塞

由于Javascript是单线程的,所以会存在被阻塞的情况:

  1. var d = new Date;
  2. setTimeout(function(){
  3. console.log("show me after 1s, but you konw:" + (new Date - d));
  4. }, 1000);
  5. while(1) if(new Date - d > 2000) break;

我们期望 console 在 1s 之后出结果,可事实上他却是在 2075ms 之后运行的,这就是 JavaScript 单线程给我们带来的烦恼,while循环阻塞了 setTimeout 函数的执行。

无法捕获

try…catch捕捉不到它的错误:

  1. try{
  2. setTimeout(function(){
  3. throw new Error("我不希望这个错误出现!")
  4. }, 1000);
  5. } catch(e){
  6. console.log(e.message);
  7. }

在与DOM事件打交道的时候

因为浏览器端主要由三个线程:javascript执行,UI渲染,事件触发队列。javascript执行的线程与UI渲染的线程又是互斥的,所以如果在一个dom事件,特别是onmousexxxonkeyxx事件中,对另外的dom元素进行操作,比如focus的时候,需要设置setTimeout将这些操作添加到事件触发的队列中,等当然的dom操作执行完成后执行。

比如这个代码:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  5. <title>setTimeout</title>
  6. <script type="text/javascript">
  7. function get(id) {
  8. return document.getElementById(id);
  9. }
  10. window.onload = function () {
  11. //第一个例子:未使用setTimeout
  12. var makeBtn = get('makeinput');
  13. makeBtn.onmousedown = function (e) {
  14. console.log(e.type);
  15. var input = document.createElement('input');
  16. input.setAttribute('type', 'text');
  17. input.setAttribute('value', 'test1');
  18. get('inpwrapper').appendChild(input);
  19. input.onfocus = function (e) {//观察我们新生成的input什么时候获取焦点的,或者它有没有像原文作者说的那样被丢弃了
  20. console.info('input focus');
  21. };
  22. input.focus();
  23. input.select();
  24. }
  25. makeBtn.onclick = function (e) {
  26. console.log(e.type);
  27. };
  28. makeBtn.onmouseup = function (e) {
  29. console.log(e.type);
  30. };
  31. makeBtn.onfocus = function () {//观察我们生成按钮什么时候获取焦点的
  32. console.log('makeBtn focus');
  33. }
  34. //第二个例子:使用setTimeout
  35. var makeBtn2 = get('makeinput2');
  36. makeBtn2.onmousedown = function (e) {
  37. console.log(e.type);
  38. var input = document.createElement('input');
  39. input.setAttribute('type', 'text');
  40. input.setAttribute('value', 'test1');
  41. get('inpwrapper2').appendChild(input);
  42. input.onfocus = function (e) {//观察我们新生成的input什么时候获取焦点的,或者它有没有像原文作者说的那样被丢弃了
  43. console.info('input focus');
  44. };
  45. //setTimeout
  46. setTimeout(function () {
  47. input.focus();
  48. input.select();
  49. }, 0);
  50. }
  51. makeBtn2.onclick = function (e) {
  52. console.log(e.type);
  53. };
  54. makeBtn2.onmouseup = function (e) {
  55. console.log(e.type);
  56. };
  57. makeBtn2.onfocus = function () {//观察我们生成按钮什么时候获取焦点的
  58. console.log('makeBtn2 focus');
  59. }
  60. //第三个例子,onkeypress输入的时候少了一个值
  61. get('input').onkeypress = function () {
  62. get('preview').innerHTML = this.value;
  63. }
  64. }
  65. </script>
  66. </head>
  67. <body>
  68. <h1><code>setTimeout</code></h1>
  69. <h2>1、未使用 <code>setTimeout</code></h2>
  70. <button id="makeinput">生成 input</button>
  71. <p id="inpwrapper"></p>
  72. <h2>2、使用 <code>setTimeout</code></h2>
  73. <button id="makeinput2">生成 input</button>
  74. <p id="inpwrapper2"></p>
  75. <h2>3、另一个例子</h2>
  76. <p>
  77. <input type="text" id="input" value="" /><span id="preview"></span>
  78. </p>
  79. </body>
  80. </html>

你能说出点击对应的输出结果吗,并告知原因?

参考资料

原文: https://leohxj.gitbooks.io/front-end-database/content/problems-in-develop-webapp/problem-with-settimeout.html