Чем я поделился на FDConf на прошлой неделе«Сделайте свою веб-страницу более гладкой»В статье было упомянуто «квантование времени». Из-за временных отношений более подробное обсуждение квантования времени в то время не проводилось. Поэтому, когда я вернулся, я подумал о том, чтобы добавить статью для подробного обсуждения «кванта времени».
От ввода пользователя до визуального вывода дисплея пользователю, если этот процесс превышает 100 мс, пользователь будет воспринимать веб-страницу как застрявшую, поэтому для решения этой проблемы каждая задача не может превышать 50 мс, производительность W3C. Группа В спецификации LongTask задачи, длительность которых превышает 50 мс, также определяются как длинные задачи.
Про эти 50 миллисекунд я дал очень подробное пояснение в шеринге FDConf.Для тех, кто не слышал, не беспокойтесь.Я добавлю статью в содержание этого шеринга в будущем.
Онлайн-адрес PPT:ppt.baomitu.com/d/b267a4a3
Таким образом, чтобы избежать длинных задач, одним из решений является использование Web Worker и выполнение длинных задач в рабочем потоке Недостаток заключается в том, что доступ к DOM невозможен, а другое решение — использовать разделение времени.
Что такое квант времени
Основная идея квантования времени такова: если задача не может быть выполнена в течение 50 миллисекунд, то, чтобы не блокировать основной поток, задача должнаyield контроль над основным потоком, позволяя браузеру обрабатывать другие задачи. Отказ от перезапуска означает останавливать текущую задачу, позволяя браузеру выполнять другие задачи, а затем возвращаться к продолжению незавершенной задачи.
Поэтому цель тайм-слайсинга — не блокировать основной поток, а технические средства достижения цели — разбить длинную задачу на множество мелких задач, не превышающих 50 мс, и выполнить их в очереди макрозадач.
На приведенном выше рисунке видно, что в основном потоке есть длинная задача, которая блокирует основной поток. После использования разделения времени, чтобы разрезать его на множество небольших задач, как показано на изображении ниже.
Вы можете видеть, что в текущем основном потоке много мелких задач, мы увеличим его, как показано на рисунке ниже.
Видно, что между каждой маленькой задачей есть промежуток, а это означает, что после того, как задача будет выполнена в течение короткого периода времени, контроль над основным потоком будет передан, и браузер будет выполнять другие задачи.
Недостатком использования квантования времени является то, что общее время выполнения задачи больше, потому что после каждой обработанной небольшой задачи основной поток освобождается и возникает небольшая задержка перед началом обработки следующей небольшой задачи.
Но чтобы избежать зависания браузера, этот компромисс необходим.
Как использовать временные срезы
Разделение времени — это концепция, которую также можно понимать как техническое решение, а не как название API или инструмента.
Фактически, разделение времени в полной мере использует «асинхронность», и в первые дни можно было использовать таймеры, например:
btn.onclick = function () {
someThing(); // 执行了50毫秒
setTimeout(function () {
otherThing(); // 执行了50毫秒
});
};
В приведенном выше коде при нажатии кнопки задача, которая должна выполняться 100 мс, теперь разделена на две задачи по 50 мс.
В практических приложениях мы можем выполнить некоторую инкапсуляцию, и эффект использования после инкапсуляции аналогичен следующему:
btn.onclick = ts([someThing, otherThing], function () {
console.log('done~');
});
Конечно, оts
Дизайн API этой функции не является предметом этой статьи.Я хочу объяснить здесь, что таймер можно использовать для реализации «квантования времени» в первые дни.
ES6 привносит концепцию итераторов и предоставляет функцию генератора для создания объектов итератора.Хотя наиболее ортодоксальным использованием функции генератора является создание объектов итератора, мы также можем использовать его функции для выполнения некоторых других задач.
Функция генератора обеспечиваетyield
ключевое слово, это ключевое слово может заставить функцию приостановить выполнение. Затем передайте объект итератораnext
метод, чтобы функция продолжала выполняться.
Учащиеся, не знакомые с функцией генератора, должны сначала научиться использовать функцию генератора.
Используя эту функцию, мы можем создавать более удобные временные срезы, такие как:
btn.onclick = ts(function* () {
someThing(); // 执行了50毫秒
yield;
otherThing(); // 执行了50毫秒
});
Как видите, нам просто нужно использоватьyield
Это ключевое слово разбивает задачу, которая должна занять 100 мс, на две задачи по 50 мс.
Мы даже можем поместить ключевое слово yield внутрь цикла:
btn.onclick = ts(function* () {
while (true) {
someThing(); // 执行了50毫秒
yield;
}
});
Мы написали бесконечный цикл в приведенном выше коде, но он все равно не блокирует основной поток, и браузер не зависает.
Принцип реализации ts на основе генератора
В предыдущем примере мы обнаружим, что интервал времени на основе генератора очень полезен, но на самом деле принцип реализации функции ts очень прост, это одна из самых простых функций ts.Всего девять строк кода.
function ts (gen) {
if (typeof gen === 'function') gen = gen()
if (!gen || typeof gen.next !== 'function') return
return function next() {
const res = gen.next()
if (res.done) return
setTimeout(next)
}
}
Хотя код состоит всего из 9 строк, а ключевой код — всего из 3 или 4 строк, в этих строках кода в полной мере используется механизм цикла обработки событий и характеристики функции Генератора.
Я все еще очень рад создавать такой код.
Основная идея приведенного выше кода:yield
Ключевое слово может приостанавливать выполнение задачи, тем самым отказываясь от управления основным потоком, через таймер «незавершенная задача» может быть повторно помещена в очередь задач для продолжения выполнения.
Не разбивайте задачи на части
использоватьyield
Это очень удобно для задач резки, но если детализация резки особенно мелкая, это неэффективно. Предположим, что наша задача выполняется100ms
, лучше всего разрезать на два исполнения50ms
задачи вместо разбиения на 100 исполнений1ms
задача. Предположим, что интервал между сокращаемыми задачами равен4ms
, затем сократить на 100 казней1ms
Общее время выполнения задачи:
(1 + 4) * 100 = 500ms
Если разрезать на два времени выполнения50ms
задача, то общее время выполнения равно:
(50 + 4) * 2 = 108ms
Видно, что общее время выполнения ниже в 4,6 раза меньше, чем предыдущее, не влияя на пользовательский опыт.
Убедитесь, что задачи по резке закрываются50ms
, который может быть использован пользователемyield
самооценка наts
Функция определяет, следует ли выполнять несколько задач одновременно, в зависимости от времени выполнения задач.
мы будемts
Функция немного улучшена:
function ts (gen) {
if (typeof gen === 'function') gen = gen()
if (!gen || typeof gen.next !== 'function') return
return function next() {
const start = performance.now()
let res = null
do {
res = gen.next()
} while(!res.done && performance.now() - start < 25);
if (res.done) return
setTimeout(next)
}
}
Теперь протестируем:
ts(function* () {
const start = performance.now()
while (performance.now() - start < 1000) {
console.log(11)
yield
}
console.log('done!')
})();
Этот код печатается 215 раз на моем компьютере в предыдущих версиях11
, который в более поздних версиях может быть напечатан 6300 раз.11
, что указывает на то, что за то же общее время можно выполнить больше задач.
Посмотрите на другой пример:
ts(function* () {
for (let i = 0; i < 10000; i++) {
console.log(11)
yield
}
console.log('done!')
})();
На моем компьютере этот код был разрезан на 10 000 небольших задач в предыдущих версиях с общим временем выполнения46
Секунды в более поздних версиях разбиты на 52 небольших задачи с общим временем выполнения1.5
Второй.
Суммировать
Я поместил код разделения времени на свой Github, если вам интересно, вы можете посетить:GitHub.com/не Эрвин/время…