Почему существует разница в порядке выполнения задачи/микрозадачи, когда кнопка программно нажата и кнопка 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 в целом - это синхронный интерфейс, который не откладывает вещи и предшествует семантике микротик и обещаниям:]