#javascript #events #ecmascript-6 #this
#javascript #Мероприятия #ecmascript-6 #это
Вопрос:
Я использую ES6 в FF (родной, я не использую переводчик). У меня есть следующая настройка класса:
Обратите внимание, что я показываю только отношения на этой диаграмме, а не правильные типы и что-то еще.
Класс Canvas представляет холст HTML5, а класс Ellipse представляет Эллипс, нарисованный на холсте. Моя цель здесь в том, чтобы я мог перемещать мышь по холсту и получать события для фигур, которые я рисую, таких как эллипс. Поскольку вопрос не касается трансляции события, пожалуйста, предположите, что это работает так же хорошо, как и следовало ожидать с 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
будет выводиться «Подписка на 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] amp;amp; 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);
}
}
}
На этом этапе я хочу щелкнуть по многоточию, и я должен получить следующий поток:
- Обнаружен щелчок мыши на элементе Canvas HTML5, и он отправлен в класс Canvas
- Класс Canvas отправляет событие захвата MouseDown самому себе
- Вызовы Canvas
_propagate
для эллипса - Ellipse отправляет событие захвата и всплывающее окно самому себе << Проблема заключается вот в чем
- Canvas отправляет событие bubble самому себе
Достаточно просто и в значительной степени работает так же, как события мыши в HTML.
Итак, проблема в том, что в моем обратном вызове у меня есть этот код:
objectMouseDown(e){
console.log(e.sender.toString());
}
Я ожидаю, что я снова должен быть зарегистрирован «Ellipse», однако вместо этого я получаю «Canvas».
Следует отметить пару моментов: — Диспетчер сообщает, что отправляет событие Ellipse mouse-down — Вызов обратного вызова, похоже, не происходит до тех пор, пока Canvas get не выполнит обратный вызов для наведения курсора мыши (во время пузырьковой обработки) — Если я удалю подписку на наведение курсора мыши Canvas, тогда наведение курсора мыши Ellipse будет вызвано правильно.
Вот дамп журнала со стандартной настройкой:
> Sending MouseDown event from Ellipse << From the dispatch
> Sending MouseDown event from Canvas << From the dispatch
> Canvas << From the event handler
И, если я удалю наведение курсора мыши на Canvas
> Sending MouseDown event from Ellipse << From the dispatch
> Ellipse << From the event handler
Пожалуйста, дайте мне знать, если я что-то недостаточно хорошо объяснил. Я предполагаю, что это как-то связано с тем, что JavaScript this
действительно странный. Однако я не смог выяснить, как его правильно сохранить. Я даже пытался установить переменную-член на значение this
, когда происходит подписка, поскольку на этом уровне все правильно, но это не сработало. И на данный момент я в растерянности.
Ответ №1:
Проблема, похоже, не имеет ничего общего с this
конкретно, а с тем фактом, что вы откладываете выполнение обработчика событий и что вы разделяете eventData
между несколькими __dispatchEvent
вызовами.
Поскольку вы откладываете выполнение обработчика до следующего тика, каждый обработчик получает одно и то же 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, следовательно, когда f(eventData)
он будет вызван впоследствии на следующем тике, eventData.sender
будет ссылаться на объект canvas.
В комментарии вы говорите
setTimeout = выполнить в новом потоке
но это неверно. setTimeout
добавляет функцию в очередь. Очередь обрабатывается всякий раз, когда поток простаивает, т. Е. в вашем случае после вызова всех __dispatch
методов. Вы можете узнать больше о модели обработки в MDN.
Не зная больше о вашем коде, простым решением было бы отложить настройку отправителя до вызова обработчика:
__dispatchEvent(eventName, eventData, isCapture = false){
if(this._subscribers[eventName] amp;amp; 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
каждому обработчику.
Комментарии:
1. Спасибо! В итоге я просто поместил все в
setTimeout
на данный момент.