现在的位置: 首页 > 综合 > 正文

JavaScript:认识延迟时间为 0 的 setTimeout

2014年02月02日 ⁄ 综合 ⁄ 共 4015字 ⁄ 字号 评论关闭

由 John Resig 的 How JavaScript Timers Work 可以知道,现有的 JavaScript 引擎是单线程处理任务的。它把任务放到队列中,不会同步去执行,必须在完成一个任务后才开始另外一个任务。

让我们看看我之前的文章:JavaScript的9个陷阱及评点,在第 9 点 Focus Pocus 中提到的问题。原作者对这个认识有所偏差,其实不只是 IE 的问题,而是现有 JavaScript 引擎对于线程实现的问题(关于线程,我的概念其实不多,如果不对,希望读者多多指教)。我们通过一个例子来说明,请访问 http://realazy.org/lab/settimeout.html. 我们来看 1 和 2。如果你能看看源代码,会发现我们的任务很简单,就是给文档增加一个 input 文本框,并聚焦和选中。请现在分别点击一下,可以看到,1 并没有能够聚焦和选中,而 2 可以。它们之间的区别在于,在执行

input.focus();
input.select();

时, 2 多了一个延迟时间为 0 的 setTimeout 的外围函数,即:

setTimeout(function(){
	input.focus();
	input.select();
}, 0);

按照 JavaScript: The Definitive Guide 5th 的 14.1 所说:

在实践中,setTimeout 会在其完成当前任何延宕事件的事件处理器的执行,以及完成文档当前状态更新后,告诉浏览器去启用 setTimeout 内注册的函数。

其实,这是一个把需要执行的任务从队列中跳脱的技巧。回到前面的例子,JavaScript 引擎在执行 onkeypress 时,由于没有多线程的同步执行,不可能同时去处理刚创建元素的 focusselect 事件,由于这两个事件都不在队列中,在完成 onkeypress 后,JavaScript 引擎已经丢弃了这两个事件,正如你看到的例子 1 的情况。而在例子 2 中,由于setTimeout可以把任务从某个队列中跳脱成为新队列,因而能够得到期望的结果。

这才是延迟事件为 0 的setTimeout的真正目的。在此,你可以看看例子 3,它的任务是实时更新输入的文本,现在请试试,你会发现预览区域总是落后一拍,比如你输 a, 预览区并没有出现 a, 在紧接输入 b 时, a 才不慌不忙地出现。其实我们是有办法让预览区跟输入框同步地,在此我没有给出答案,因为上面所说的,就是解决思路,try it yourself!

留言

  1. 现学现卖

    §

    get(’input’).onkeypress = function(){
    setTimeout(function() {get(’preview’).innerHTML = get(’input’).value;}, 0)
    }

  2. Lunatic Sun

    §

    JavaScript 引擎并不会丢弃事件,在你的例子中

    input.focus();
    input.select();

    已经被执行。并且input不能获取焦点的解决方法不一定是使用setTimeout,在Firefox和Safari中,只要使用return false取消默认行为就能够达到目的。另外Opera不需要任何技巧直接能够正确执行以上两行代码,我想可能是因为Opera没有在button的mousedown事件上设置默认行为。

    不过return false没有解决IE的这个问题,IE确实是执行focus和select方法,input也确实得到了焦点,但是似乎select的默认行为没有被执行,所以IE中看不到input中的文本被选中。

  3. Lunatic Sun

    §

    我刚才说的IE的情况看来是IE的mousedown事件下的一个bug,使用onclick事件就没有这个问题了。

  4. Realazy

    §

    @Lunatic Sun 实际上,从一个不是很专业的角度来说,click = mousedown + mouseup. 虽然我没有深入研究,但可以这么假定:mouswdown 时执行创建 dom 事件,而 mouseup 时执行 focus 和 select 的事件,因而没有问题。这也是在追求速度时,推荐使用 mousedown 替换 click 的原因,只不过 mousedown 不像 click 一样,click 时,用户可以不释放鼠标,从而有反悔的机会。

  5. Lunatic Sun

    §

    @Realazy - 从用户体验的角度,我推荐使用click事件的原因有两个:

    1 我们应当让用户有反悔的机会;
    2 在button的mousedown事件中使用移开焦点的代码input.focus()会使浏览器本身的绘画button被按下和弹起的那种效果消失。

  6. xiaowei

    §

    应该是这样,input.focus();input.select(); 都执行成功。 但由于采用了onmousedown事件,mousedown后随后触发mouseup于是焦点立即移回到button。这样虽然input.select(); 已执行成功但确看不出来

  7. zamanewby

    §

    我前一段时间在写js代码时, 经常出现在firefox ie7 opera等浏览器工作正常的代码, 在ie6下失效的情况。 后来我发现失效部分的代码用一个setTimeout函数延时一下就可以正常工作了, 我通常都是设1ms的延时, 没试过0, 一直以为是ie6的效率问题。 看过这篇文章比较受启发。 回去试一下, 估计正好能解决因为设了1ms延时对后续部分代码影响的问题了:)

  8. dexter_yy

    §

    ……问题不是阻断罢,而是onkeydown/onkeypress的时候,根本就还没有完成输入罢, value本来就没改变,同样, onmousedown的时候点击事件还未完成,select和focus实际上执行过了,只不过又被点击事件取消了而已……

    你可以把那个测试页面的代码改成这样试试:

    get(’input’).onkeydown = function(){
    get(’preview’).innerHTML += this.value+’1′;
    var me = this;
    setTimeout(function(){
    get(’preview’).innerHTML += me.value+”2″;
    }, 0);
    };
    get(’input’).onkeyup = function(){
    get(’preview’).innerHTML += this.value+’3′;
    var me = this;
    setTimeout(function(){
    get(’preview’).innerHTML += me.value+”4″;
    }, 0);
    };
    get(’input’).onkeypress = function(){
    get(’preview’).innerHTML += this.value+”5″;
    var me = this;
    setTimeout(function(){
    get(’preview’).innerHTML += me.value+”6″;
    }, 0);
    };

  9. Realazy

    §

    @dexter_yy 或许我所举的例子不是很好。你所说的 onkeydown/onkeypress的时候,根本就还没有完成输入,我是这样认为的:正是因为 正在输入 这个进程阻断了其他事件,因此才需要 setTimeout 来为被隔断的进程重新排程。

    p.s. 你的 blog 很棒!

  10. Carffuca

    §

    我认为在第3个例子中,我们定义了对onkeypress事件的处理函数(即在span中显示input内的值),而浏览器自身也有一个对onkeypress事件的处理函数(即在input框中显示你输入的那个值)。我认为浏览器把这两个函数放在了一个对onkeypress事件监听的队列里,并且用户定义的函数先运行了,浏览器自己的函数后运行。可以简单的修改一下第3个例子就能看出这个效果。
    get(’input’).onkeypress = function(){
    alert(this.value);
    }
    注意当alert出现的时候input框中并没有值,当点击alert的确定后,input框中出现了输入的值。
    在这里使用setTimeout其实就是推迟了用户定义的那个函数的运行时间,浏览器会在处理好所有onkeypress事件监听函数后运行setTimeout中的内容。

  11. lone

    §

    我之前对click的理解也是click=down+up, 这些实例把down或者press换成up就行了,理由或许正是LZ讲的 追求速度时推荐使用up而不是down,这样同样允许用户反悔而且会保留按钮的动画效果~
    有个实例是 www.clickclickclick.com和www.diandiandian.net的点击效果,赫赫

  12. lone

    §

    1) Javascript不会丢弃事件, 只要在select()语句后再加上一句 alert(’after select’)方法,执行就可以看到此时,input已经被选中;
    2) JS引擎首先执行用户自定义事件处理方法,然后才执行默认行为;
    3) JS引擎碰到setTimeout方法会将其放入队列等待, 并”跳过”其程序块而继续执行其所在方法的后续代码,执行完成之后才从队列中调用setTimeout~
    因此,以下代码同样可以解决问题:
    get(’makeinput’).onmousedown = function(){
    setTimeout(function(){
    var input = document.createElement(’input’);
    input.setAttribute(’type’, ‘text’);
    input.setAttribute(’value’, ‘test1′);
    get(’inpwrapper’).appendChild(input);
    input.focus();
    input.select();
    },0);
    }
    1> 执行onmousedown程序体;
    2> 跳过setTimeout方法,没有其他代码;
    3> 执行setTimeout方法,没有问题;

抱歉!评论已关闭.