Интервью: страница загружает массивные данные

внешний интерфейс JavaScript анимация опрос

тема

На страницу за один раз выводится массив из 10 записей, как с этим справиться, не зависая при этом в интерфейсе?

материализованный

На странице есть пустой узел ненумерованного спискаul,Этоidдляlist-with-big-data, теперь нужно вставить 10w в списокli, текстовое содержимое каждого элемента списка может быть определено само по себе, и требуется, чтобы при каждом элементе спискаliПри нажатии пройтиalertОтображает текстовое содержимое в элементе списка.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>页面加载海量数据</title>
</head>

<body>
  <ul id="list-with-big-data">100000 数据</ul>
  <script>
    // 此处添加你的代码逻辑
  </script>
</body>

</html>

анализировать

Может увидеть проблему на первый взгляд, мы могли бы подумать о таком решении: получитьulЭлемент, затем построитьliэлемент и наборliТекстовое содержимое привязывается к слушателю, а затем в циклеulпровестиappendоперация, т. е. то, что может прийти на ум, это следующая реализация кода.

(function() {
  const ulContainer = document.getElementById("list-with-big-data");

  // 防御性编程
  if (!ulContainer) {
    return;
  }

  for (let i = 0; i < 100000; i++) {
    const liItem = document.createElement("li");

    liItem.innerText = i + 1;
    // EventListener 回调函数的 this 默认指向当前节点,若使用箭头函数,得谨慎
    liItem.addEventListener("click", function() {
      alert(this.innerText);
    });
    ulContainer.appendChild(liItem);
  }
})();

Практикуя приведенный выше код, мы обнаружили, что интерфейс очень недружественный, а ощущение заикания серьезное. Основная причина зависания заключается в том, что в каждом цикле изменяется структура DOM, и из-за большого количества данных время выполнения цикла слишком велико, а частота кадров рендеринга в браузере слишком низкая.

На самом деле он содержит 100 000liдлинных списков, пользователи не увидят их все сразу, а лишь некоторые из них. Поэтому для большинстваliРендеринг работы, мы можем отложить завершение.

мы можем начать сУменьшите количество манипуляций с DOMа такжеСокращение времени циклаДва аспекта уменьшают время блокировки основного потока.

DocumentFragment

The DocumentFragment interface represents a minimal document object that has no parent. It is used as a lightweight version of Document that stores a segment of a document structure comprised of nodes just like a standard document. The key difference is that because the document fragment isn't part of the active document tree structure, changes made to the fragment don't affect the document, cause reflow, or incur any performance impact that can occur when changes are made.

существуетMDNВо введении мы знаем, что это можно сделать с помощьюDocumentFragmentИспользование DOM сокращает количество операций DOM и снижает влияние перекомпоновки на производительность.

requestAniminationFrame

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.

С точки зрения сокращения времени цикла, мы можемразделяй и властвуйдумал, будет 100000liвставляются на страницу пачками, и мы передаемrequestAniminationFrameНовый узел вставляется перед перерисовкой страницы.

привязка события

Если мы хотим прослушивать большое количество элементов, рекомендуется использовать механизм событий JavaScript для реализации делегирования событий, что может значительно сократить количество регистраций событий DOM.

решение

После вышеизложенного у нас есть следующее решение.

(function() {
  const ulContainer = document.getElementById("list-with-big-data");

  // 防御性编程
  if (!ulContainer) {
    return;
  }

  const total = 100000; // 插入数据的总数
  const batchSize = 4; // 每次批量插入的节点个数,个数越多,界面越卡顿
  const batchCount = total / batchSize; // 批处理的次数
  let batchDone = 0; // 已完成的批处理个数

  function appendItems() {
    // 使用 DocumentFragment 减少 DOM 操作次数,对已有元素不进行回流
    const fragment = document.createDocumentFragment();

    for (let i = 0; i < batchSize; i++) {
      const liItem = document.createElement("li");
      liItem.innerText = batchDone * batchSize + i + 1;
      fragment.appendChild(liItem);
    }

    // 每次批处理只修改 1 次 DOM
    ulContainer.appendChild(fragment);
    batchDone++;
    doAppendBatch();
  }

  function doAppendBatch() {
    if (batchDone < batchCount) {
      // 在重绘之前,分批插入新节点
      window.requestAnimationFrame(appendItems);
    }
  }

  // kickoff
  doAppendBatch();

  // 使用 事件委托 ,利用 JavaScript 的事件机制,实现对海量元素的监听,有效减少事件注册的数量
  ulContainer.addEventListener("click", function(e) {
    const target = e.target;

    if (target.tagName === "LI") {
      alert(target.innerText);
    }
  });
})();