Почему существует разница в порядке выполнения задачи/микрозадачи, когда кнопка программно нажата и кнопка DOM нажата?
Существует разница в порядке выполнения очередей микрозадач/задач при нажатии кнопки в DOM по сравнению с программным нажатием.
const btn = document.querySelector('#btn');
btn.addEventListener("click", function() {
Promise.resolve().then(function() { console.log('resolved-1'); });
console.log('click-1');
});
btn.addEventListener("click", function() {
Promise.resolve().then(function() { console.log('resolved-2'); });
console.log('click-2');
});
<button id='btn'>Click me !</button>
Ответы
Ответ 1
Увлекательный вопрос.
Во-первых, самая простая часть: когда вы вызываете click
, это синхронный вызов, запускающий все обработчики событий на кнопке. Вы можете видеть это, если вы добавляете логирование вокруг вызова:
const btn = document.querySelector('#btn');
btn.addEventListener("click", function() {
Promise.resolve().then(function() { console.log('resolved-1'); });
console.log('click-1');
});
btn.addEventListener("click", function() {
Promise.resolve().then(function() { console.log('resolved-2'); });
console.log('click-2');
});
document.getElementById("btn-simulate").addEventListener("click", function() {
console.log("About to call click");
btn.click();
console.log("Done calling click");
});
<input type="button" id="btn" value="Direct Click">
<input type="button" id="btn-simulate" value="Call click()">
Ответ 2
dispatchEvent
В отличие от "собственных" событий, которые запускаются DOM и асинхронно вызывают обработчики событий через цикл событий, dispatchEvent синхронно вызывает обработчики событий. Все соответствующие обработчики событий будут выполняться и возвращаться до того, как код продолжится после вызова dispatchEvent.
dispatchEvent - это последний шаг процесса create-init-dispatch, который используется для отправки событий в модель событий реализации. Событие может быть создано с помощью конструктора событий.
Ответ 3
Итак, Chrome отвечает только потому, что это интересно (см. Отличный ответ TJ Crowder для общего ответа DOM).
btn.click();
Вызывает HTMLElement::click()
в C++, который является аналогом элемента DOMElement:
void HTMLElement::click() {
DispatchSimulatedClick(nullptr, kSendNoEvents,
SimulatedClickCreationScope::kFromScript);
}
Который в основном работает с dispatchMouseEvent и имеет дело с крайними случаями:
void EventDispatcher::DispatchSimulatedClick(
Node& node,
Event* underlying_event,
SimulatedClickMouseEventOptions mouse_event_options,
SimulatedClickCreationScope creation_scope) {
// This persistent vector doesn't cause leaks, because added Nodes are removed
// before dispatchSimulatedClick() returns. This vector is here just to
// prevent the code from running into an infinite recursion of
// dispatchSimulatedClick().
DEFINE_STATIC_LOCAL(Persistent<HeapHashSet<Member<Node>>>,
nodes_dispatching_simulated_clicks,
(MakeGarbageCollected<HeapHashSet<Member<Node>>>()));
if (IsDisabledFormControl(&node))
return;
if (nodes_dispatching_simulated_clicks->Contains(&node))
return;
nodes_dispatching_simulated_clicks->insert(&node);
if (mouse_event_options == kSendMouseOverUpDownEvents)
EventDispatcher(node, *MouseEvent::Create(event_type_names::kMouseover,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
if (mouse_event_options != kSendNoEvents) {
EventDispatcher(node, *MouseEvent::Create(event_type_names::kMousedown,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
node.SetActive(true);
EventDispatcher(node, *MouseEvent::Create(event_type_names::kMouseup,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
}
// Some elements (e.g. the color picker) may set active state to true before
// calling this method and expect the state to be reset during the call.
node.SetActive(false);
// always send click
EventDispatcher(node, *MouseEvent::Create(event_type_names::kClick,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
nodes_dispatching_simulated_clicks->erase(&node);
}
Это полностью синхронно по замыслу, чтобы сделать тестирование простым, а также по устаревшим причинам (подумайте, DOMActivate странные вещи).
Это просто прямой вызов, при этом не требуется планирование задач. EventTarget в целом - это синхронный интерфейс, который не откладывает вещи и предшествует семантике микротик и обещаниям:]