我在 FF 中使用 ES6(原生,我没有使用翻译器)。我有以下类(class)设置:
请注意,我仅在该图中显示关系,而不是正确的类型和其他内容。
Canvas 类表示 HTML5 Canvas ,Ellipse 类表示在 Canvas 上绘制的 Ellipse。我的目标是我可以在 Canvas 上移动鼠标,并获取我绘制的形状(例如椭圆)上的事件。由于问题与事件翻译无关,因此请假设它的效果与您对 HTML 元素的期望一样好。不过,我会注意到 _propagate
调用就是处理这个问题的,并且它与递归一起工作(稍后解释)。
subscribe
的完整定义类似于 addEventListener
:(eventName,callback, [isCapture])
。
在 Canvas 内部,我有以下代码:
addObject(object){
this.__children.unshift(object);
console.log("Susbscribing to " + object.toString());
object.subscribe(MouseEventType.MouseDown, this.objectMouseDown);
}
// ...
canvas.addObject(new Ellipse());
因此,我向 Canvas 添加了一个椭圆,当我这样做时,我订阅了该椭圆的 MouseDown 事件。请注意,此时 console.log
将输出“Subscribing to Ellipse”
现在,订阅代码是这样的:
subscribe(eventName, func, useCapture = false){
// If the event exists, add the method, otherwise, throw an exception
if(this._subscribers[eventName]){
this._subscribers[eventName][useCapture].push(func);
console.log(this.toString() + " has had somebody subscribe to " + eventName);
}
else{
throw eventName + " is not a valid event";
}
}
此时,console.log
再次正确地将对象识别为 Ellipse。
最后,我们到达__dispatchEvent
代码,如下所示:
__dispatchEvent(eventName, eventData, isCapture = false){
if(this._subscribers[eventName] && this._subscribers[eventName][isCapture]){
for(var func of this._subscribers[eventName][isCapture]){
// Set the sender to `this`
console.log("Sending " + eventName + " event from " + this.toString());
eventData.sender = this;
// setTimeout = run in new thread
// Really weird syntax so things are kept in scope correctly
// -- func is passed into an anonymous method, which then calls that function with the event data
((f) => setTimeout(() => f(eventData), 0))(func);
}
}
}
此时,我想单击椭圆,我应该得到以下流程:
- 在 HTML5 Canvas 元素上检测到鼠标点击,并将其发送到 Canvas 类
- Canvas 类将 MouseDown 捕获事件分派(dispatch)给自身
- Canvas 在椭圆上调用
_propagate
- Ellipse 将捕获事件和气泡事件分派(dispatch)给自身<<问题就出在这里
- Canvas 将气泡事件分派(dispatch)给自身
足够简单,并且几乎就像 HTML 中的鼠标事件一样工作。
所以,问题是在我的回调中,我有以下代码:
objectMouseDown(e){
console.log(e.sender.toString());
}
我期望我应该再次记录“椭圆”,但是,我得到的是“ Canvas ”。
有几点需要注意: - 调度程序说它正在发送 Ellipse 鼠标按下事件 - 对回调的调用似乎不会发生,直到 Canvas 获取其 MouseDown 的回调之后(冒泡时) - 如果我删除 Canvas MouseDown 的订阅,则 Ellipse MouseDown 会被正确调用。
这是标准设置的日志转储:
> Sending MouseDown event from Ellipse << From the dispatch
> Sending MouseDown event from Canvas << From the dispatch
> Canvas << From the event handler
而且,如果我删除 Canvas 上的 MouseDown
> Sending MouseDown event from Ellipse << From the dispatch
> Ellipse << From the event handler
如果我解释得不够好,请告诉我。我猜测这与 JavaScript 的 this
非常奇怪有关。但是,我一直无法弄清楚如何正确存储它。我什至尝试在订阅发生时将成员变量设置为 this
,因为在该级别上情况是正确的,但这不起作用。此时此刻,我已经不知所措了。
最佳答案
这个问题似乎与 this
具体没有任何关系,但事实上您正在推迟事件处理程序的执行并且在多个 __dispatchEvent
调用之间共享 eventData
。
由于您将处理程序的执行推迟到下一个刻度,因此每个处理程序都会获得相同的 eventData.sender
值。
这正是您在输出中看到的内容:
> Sending MouseDown event from Ellipse << From the dispatch
> Sending MouseDown event from Canvas << From the dispatch
> Canvas << From the event handler
第二次调用会将 eventData.sender
设置为 Canvas 对象,因此当在下一个tick中调用 f(eventData)
时,eventData.sender
将引用 Canvas 对象。
您在评论中所说的内容
setTimeout = run in new thread
但这不正确。 setTimeout
将函数添加到队列中。只要线程空闲,即在您的情况下,在调用所有 __dispatch 方法之后,就会处理队列。您可以详细了解处理模型on MDN .
如果不了解有关代码的更多信息,一个简单的解决方案是推迟设置发送者,直到调用处理程序:
__dispatchEvent(eventName, eventData, isCapture = false){
if(this._subscribers[eventName] && this._subscribers[eventName][isCapture]){
// Use `let` for block scope
for(let func of this._subscribers[eventName][isCapture]){
setTimeout(() => {
// Set the sender to `this`
console.log("Sending " + eventName + " event from " + this.toString());
eventData.sender = this;
func(eventData);
}, 0);
}
}
}
另一种方法是将 eventData
的新实例传递给每个处理程序。
关于javascript - 使用递归、继承和封装时如何在 JavaScript 中保留正确的 `this` 上下文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40181804/