JavaScript 是单线程的吗?如果没有,我如何同步访问共享数据?
-
03-07-2019 - |
题
我有一个网页 DIV
与一个 mouseover
旨在显示弹出信息气泡的处理程序。我不希望一次显示多个信息气泡。但是,当用户将鼠标快速移动到两个项目上时,我有时会看到两个气泡。这种情况不应该发生,因为显示弹出窗口的代码取消了之前的弹出窗口。
如果这是一个多线程系统,那么问题就很明显了:有两个线程试图显示弹出窗口,它们都取消现有的弹出窗口,然后弹出自己的弹出窗口。但我假设 JavaScript 总是单线程运行,这会阻止这种情况发生。我错了吗?事件处理程序是否异步运行,在这种情况下我需要同步访问共享数据,或者我应该在库代码中寻找错误以取消弹出窗口?
编辑添加:
- 有问题的图书馆是 模拟时间轴 及其 Ajax 库;
- 事件处理程序确实调用
SimileAjax.DOM.cancelEvent(domEvt)
, ,我认为根据名称可以取消事件的冒泡; - 只是为了让事情变得更复杂,我实际上正在做的是开始一个超时,如果没有被取消的话
moustout
显示弹出窗口,这是为了防止弹出窗口烦人地闪烁,但会产生相反的效果。
我会再看一下,看看是否能找出哪里出错了。:-)
解决方案
是的,Javascript 是单线程的。即使使用像 Google Chrome 这样的浏览器,每个选项卡也有一个线程。
如果不知道如何尝试从另一个弹出窗口中取消一个弹出窗口,则很难说出问题的原因是什么。
如果您的 DIV 相互嵌套,您可能会有一个 事件传播 问题。
其他提示
我不知道您正在使用的库,但如果您只想一次显示某种工具提示......使用享元对象。基本上,蝇量级是一次性制造并反复使用的东西。想想单例类。因此,您静态调用一个类,该类在首次调用时会自动创建一个自身的对象并存储它。一种情况是,每个静态都引用同一个对象,因此您不会得到多个工具提示或冲突。
我使用 ExtJS,他们将工具提示和消息框作为蝇量级元素。我希望你的框架也有享元元素,否则你只需要创建自己的单例并调用它。
它在浏览器中是单线程的。事件处理程序在一个线程中异步运行,非阻塞并不总是意味着多线程。你的一个div是另一个div的孩子吗?因为事件就像 dom 树中的气泡一样从子级传播到父级。
与 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 请求的执行线程被分离(这就是接口在同步请求期间不会冻结的原因)并且执行继续。同步请求完成后,其线程也会继续。它们不会同时执行,但是依赖于单线程在同步过程(请求)发生时停止的假设不再适用。
可能是显示器刷新速度不够快。根据您使用的 JS 库,您也许可以对弹出的“显示”效果稍加延迟。
这是或多或少的工作版本。创建项目时,我们附加一个 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 毫秒。不确定为什么超时太短会导致发生多气泡事件,但我猜想在布置新添加的元素时,窗口事件的排队或某些事情可能仍然会发生。