Производительность HyperHTML при повторном заказе узлов DOM - PullRequest
0 голосов
/ 06 сентября 2018

Я пытаюсь понять HyperHTML и как добиться от него максимальной производительности.

Читая о о том, как он работает под капотом , кажется, что между шаблоном и DOM установлены прочные связи, что, как я понимаю, требует другого мышления от VirtualDOMдля оптимизации производительности.

Я написал некоторый код, чтобы показать сортировку N элементов в таблице с использованием hyperHtml против normalHtml.Версия normalHtml просто очищает таблицу и перестраивает элементы.Они оба кажутся одинаковыми с точки зрения производительности.Я сравниваю яблоки с апельсинами здесь?Как я могу улучшить версию кода в формате hyperHtml?

Код:

const numberOfElements = 10000
const items = Array.apply(null, Array(numberOfElements)).map((el, i) => i).sort(() => .5 - Math.random())
const sortMethods = [

  () => 0,
  (a, b) => a - b,
  (a, b) => b - a

]

function hyperHtmlTest() {

  const $node = document.createElement('div')
  const $table = document.createElement('table')
  const $button = document.createElement('button')
  const tableRender = hyperHTML.bind($table)

  let sortMethodIndex = 0

  function render () {

    const sortMethod = sortMethods[sortMethodIndex++ % sortMethods.length]

    tableRender`${
      items.concat().sort(sortMethod).map(item => {
        return `<tr><td>${item}</td></tr>`
      })
    }`
  }

  $node.appendChild($button)
  $node.appendChild($table)

  $button.textContent = 'HyperHTml Sort'
  $button.onclick = render  

  return $node

}

function normalHtmlTest() {

  const $node = document.createElement('div')
  const $table = document.createElement('table')
  const $button = document.createElement('button')

  let sortMethodIndex = 0

  function render () {

    const sortMethod = sortMethods[sortMethodIndex++ % sortMethods.length]

    while($table.childNodes.length)
      $table.removeChild($table.childNodes[0])

    const frag = document.createDocumentFragment()

    items.concat().sort(sortMethod).forEach(item => {

      const tr = document.createElement('tr')
      const td = document.createElement('td')

      td.textContent = item
      tr.appendChild(td)

      frag.appendChild(tr)

    })

    $table.appendChild(frag)

  }

  $node.appendChild($button)
  $node.appendChild($table)

  $button.textContent = 'NormalHtml Sort'
  $button.onclick = render  

  return $node

}

document.body.appendChild(hyperHtmlTest())
document.body.appendChild(normalHtmlTest())

Или на CodePen

Подвести итог вопроса:Почему HyperHTML так же производителен, как обычные манипуляции с DOM в моем примере кода, и как я могу сделать HyperHTML более производительным, особенно при переупорядочении узлов DOM?

1 Ответ

0 голосов
/ 07 сентября 2018

Обновление

hyperHTML 2.14 представил использование domdiff V2, обеспечивающего производительность, похожую на petit-dom, с дополнительными 0.6 КБ для библиотеки, что, надеюсь, стоит изменений.

В оригинальной демонстрации также были огромные проблемы в Safari по какой-то странной причине, скорее всего, связанные с тем, что узлы добавляются непосредственно в таблицу TABLE, а не в элемент TBODY таблицы.

В любом случае, теперь вы можете сравнивать производительность проводной демонстрации через этот CodePen .

Обратите внимание, что все сказанное о старом snabdom diffing также больше не актуально.


Похоже, что вы могли бы прочитать немного дальше, достигнув проводной части , поскольку вы получили bind.

По сути, если вы используете массив строк в качестве интерполированного значения, вы не делаете ничего, кроме innerHTML подобной операции, со всеми обычными побочными эффектами:

  • каждый раз, когда вы удаляете все узлы и создаете их с нуля
  • каждая ссылка на любой узел будет потеряна навсегда
  • количество операций GC выше
  • макет подвержен XSS, следовательно, небезопасен

Чтобы избежать репликации всех этих innerHTML ошибок и правильно использовать hyperHTML, вам необходимо wire элементов для узла, который они представляют.

tableRender`${
  items.concat().sort(sortMethod).map(item => {
    return wire(item)`<tr><td>${item.text}</td></tr>`
  })
}`

Однако, поскольку память является проблемой, провод работает через WeakMap, поэтому числа, если они не используются как : идентификаторы провода, невелики.

Реальность такова, что никто не имеет чисел или строк в качестве предметов в реальном мире, 99% времени представлено объектами, и поэтому пусть объект будет для демонстрации.

Array.apply(null, Array(numberOfElements)).map(
  (el, i) => new Number(i)
)

Если у вас есть объекты вместо примитивов, что не относится к объектам, которые я создал для демонстрации, более реалистичный сценарий, каждый раз, когда вы вызываете рендер или обновляете, строки не будут выбрасываться и каждый раз создавая их заново, они будут просто переупорядочены, как вы можете видеть в моей ветке CodePen вашей, в общем виде так:

function hyperHtmlTest() {
  const {bind, wire} = hyperHTML;
  const render = bind(document.createElement('div'));
  let sortMethodIndex = 0;
  return update();
  function update() {
    const sortMethod = sortMethods[sortMethodIndex++ % sortMethods.length];
    return render`
    <button onclick=${update}>HyperHTml Sort</button>
    <table>
      ${items.concat().sort(sortMethod).map(
        item => wire(item)`<tr><td>${+item}</td></tr>`
      )}
    </table>`;
  }
}

Об исполнении

Позади hyperHTML есть движок с именем domdiff , целью которого является реорганизация узлов.

Алгоритм, используемый в domdiff , в среднем чертовски быстр, но есть случаи, когда он может работать медленнее, чем браузер, создающий одну и ту же раскладку одновременно.

Вы можете легко заметить в моей ручке, что когда вы переключаетесь с ASC на DESC или наоборот, он в 5 раз быстрее, чем его ванильный аналог DOM, но когда дело доходит до упорядоченного списка до полностью случайного, domdiff делает много проверок, что DOM-коллеге не будет все равно, поэтому он может быть медленнее.

В нескольких словах, в то время как ванильный DOM-подход линейно быстр (или медленен), алгоритм имеет лучшие и худшие случаи.

Алгоритм, который хорошо работает почти в каждом случае, - это тот, который используется Petit-Dom, но вся эта логическая часть имеет вес, который IMO не нужен для реальных сценариев, но, безусловно, впечатляет для не реальных тестов.

70000 Ряды без пота

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

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

Как вы можете видеть в этой таблице 70 КБ, сортировка - это флэш-память, а также поиск и прокрутка, и это HyperHTMLElement во всей своей красе.

Эти тесты ? Не так интересно для повседневных, реальных задач.

Надеюсь, это ответило на все ваши сомнения.

...