javascript - 使用递归、继承和封装时如何在 JavaScript 中保留正确的 `this` 上下文

标签 javascript events ecmascript-6 this

我在 FF 中使用 ES6(原生,我没有使用翻译器)。我有以下类(class)设置:

Class Diagram

请注意,我仅在该图中显示关系,而不是正确的类型和其他内容。

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/

相关文章:

javascript - 将使用 CSS 显示 :none negatively affect my search engine ranking?

c# - 用于处理事件订阅的实用程序类 (AddSubscription()/UnsubscribeAll())

javascript - Node.js - 语法错误 : Unexpected token import while running on node 6. 10.2

javascript - 导航菜单不会在点击时打开或关闭

javascript - Jquery C# 中的数据集值

java - Akka:动态添加Actors到BroadcastRouter

forms - Symfony2 : get post data from PRE_SET_DATA event

javascript - npm install package-name 非常慢

javascript - 我们如何在 readline.on 函数下使用 promise on (Node.js 8. Currently)

javascript - 如何将div内的内容克隆到其他div