JavaScript是单线程的吗?如果没有,我如何获得对共享数据的同步访问?
我有一个带有DIV和mouseover处理程序的网页,旨在显示弹出信息气泡。我不希望一次看到多个信息气泡。但是,当用户在两个项目上快速移动鼠标时,有时会出现两个气泡。这不会发生,因为用于显示弹出窗口的代码会取消先前的弹出窗口。
如果这是一个多线程系统,那么问题将很明显:有两个线程试图显示弹出窗口,并且它们都取消了现有的弹出窗口,然后弹出了自己的弹出窗口。但是我认为JavaScript总是在单线程上运行,这可以防止这种情况。我错了吗?事件处理程序是否异步运行,在这种情况下,我需要对共享数据进行同步访问,还是应该在库代码中寻找错误来取消弹出窗口?
编辑添加:
- 有问题的库是SIMILE Timeline及其Ajax库。
- 事件处理程序确实调用了SimileAjax.DOM.cancelEvent(domEvt),我根据名称假定它会取消事件的冒泡。
- 为了使事情变得更复杂,我实际上正在做一个超时操作,该超时操作如果没有被
moustout
取消,则会显示弹出窗口,这是为了防止弹出窗口烦人地闪烁但烦人地具有相反效果。
我还要再戳一遍,看看是否可以找出我要去哪里。 :-)
解决方案
是的,Javascript是单线程的。即使使用谷歌浏览器之类的浏览器,每个标签也只有一个线程。
不知道我们如何尝试从另一个弹出窗口中取消弹出窗口,很难说出问题的原因是什么。
如果DIV相互嵌套,则可能会发生事件传播问题。
它在浏览器中是单线程的。事件处理程序在一个线程中异步运行,非阻塞并不总是意味着多线程。一个div是另一个的孩子吗?因为事件像dom树中的气泡一样从孩子传播到父母。
可能是显示器的刷新速度不够快。根据我们使用的JS库,我们可能可以对弹出的"显示"效果稍加延迟。
与pkaeding所说的类似,如果不查看标记和脚本就很难猜出问题。但是,我冒昧地说我们没有适当地停止事件传播和/或者我们没有适当地隐藏现有元素。我不知道我们是否正在使用框架,但是以下是使用Prototype的可能解决方案:
// maintain a reference to the active div bubble this.oActiveDivBubble = null; // event handler for the first div $('exampleDiv1').observe('mouseover', function(evt) { evt.stop(); if(this.oActiveDivBubble ) { this.oActiveDivBubble .hide(); } this.oActiveDivBubble = $('exampleDiv1Bubble'); this.oActiveDivBubble .show(); }.bind(this)); // event handler for the second div $('exampleDiv2').observe('mouseover'), function(evt) { evt.stop(); if(this.oActiveDivBubble) { this.oActiveDivBubble.hide(); } this.oActiveDivBubble = $('exampleDiv2Bubble'); this.oActiveDivBubble .show(); }.bind(this));
当然,可以通过使用相同的类获取所有元素,然后对它们进行迭代,并对每个元素应用相同的事件处理函数,来进一步推广这一点。
无论哪种方式,希望对我们有所帮助。
仅供参考:从Firefox 3开始,与该讨论非常相关的变化是:导致同步XMLHttpRequest请求的执行线程被分离(这就是为什么在同步请求期间接口不会在那里冻结的原因),并且执行继续进行。同步请求完成后,其线程也会继续。它们不会在同一时间执行,但是基于这样的假设:不再发生同步过程(请求),而单线程停止。
这是工作版本,或者多或者少。创建项目时,我们会添加一个mouseover
事件:
var self = this; SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mouseover", function (elt, domEvt, target) { return self._onHover(labelElmtData.elmt, domEvt, evt); });
这将调用一个设置超时的函数(不同项目的先前存在的超时将首先被取消):
MyPlan.EventPainter.prototype._onHover = function(target, domEvt, evt) { ... calculate x and y ... domEvt.cancelBubble = true; SimileAjax.DOM.cancelEvent(domEvt); this._futureShowBubble(x, y, evt); return false; } MyPlan.EventPainter.prototype._futureShowBubble = function (x, y, evt) { if (this._futurePopup) { if (evt.getID() == this._futurePopup.evt.getID()) { return; } else { /* We had queued a different event's pop-up; this must now be cancelled. */ window.clearTimeout(this._futurePopup.timeoutID); } } this._futurePopup = { x: x, y: y, evt: evt }; var self = this; this._futurePopup.timeoutID = window.setTimeout(function () { self._onTimeout(); }, this._popupTimeout); }
这反过来显示了气泡,如果气泡在被取消之前就触发了:
MyPlan.EventPainter.prototype._onTimeout = function () { this._showBubble(this._futurePopup.x, this._futurePopup.y, this._futurePopup.evt); }; MyPlan.EventPainter.prototype._showBubble = function(x, y, evt) { if (this._futurePopup) { window.clearTimeout(this._futurePopup.timeoutID); this._futurePopup = null; } ... SimileAjax.WindowManager.cancelPopups(); SimileAjax.Graphics.createBubbleForContentAndPoint(...); };
现在我将超时设置为200毫秒而不是100毫秒,这似乎可以正常工作。不知道为什么超时时间太短会导致多气泡事件发生,但是我猜想窗口事件的排队或者在布局新添加的元素时可能仍在发生某些事情。
我不知道我们正在使用的库,但是如果我们只想一次显示某种分类的工具提示,请使用flyweight对象。从根本上说,飞锤是一次制造并反复使用的东西。想一想单例课程。因此,我们可以静态调用一个类,该类在首次调用时会自动创建其自身的对象并进行存储。每个静态对象都引用同一个对象的情况就会发生这种情况,因此我们不会遇到多个工具提示或者冲突。
我使用ExtJS,它们会同时提供工具提示和消息框,两者都是flyweight元素。我希望框架也具有flyweight元素,否则我们只需要制作自己的单例并调用它即可。