разрезание времени
Он направлен на то, чтобы разбить длительную задачу на более мелкие задачи и выполнять их блоками, потому что задачи, превышающие 50 мс, будут считаться длительными задачами, и пользователи могут воспринимать рендеринг и взаимодействие, поэтому мы можем сократить время непрерывного выполнения функции.
причина
Коллега столкнулся с проблемой с отображением анимации, то есть внизу нужно выполнить функцию с большим объемом вычислений.Хочет загрузить загрузку, но обнаружил, что элемент загрузки display:block; анимация загрузки не появится на странице сразу, и анимация появится.Время после выполнения функции операции.
Решение
Есть два способа справиться с этой трудоёмкой задачей.Первый - это webWorker, но некоторые DOM-операции нельзя сделать, поэтому я подумал решить её через функции-генераторы.Давайте вкратце разберёмся с циклом обработки событий.
цикл событий
Микрозадачи:
1. Promise.then
2. Object.observe
3. MutaionObserver
Задача макроса:
1. script(整体代码)
2. setTimeout
3. setInterval
4. I/O
5. postMessage
6. MessageChannel
Время рендеринга в браузере
За исключением особых случаев, рендеринг страницы будет выполняться после того, как очередь микрозадач будет очищена и до выполнения задачи макроса, поэтому мы можем позволить функции, помещенной в основной стек выполнения, выполняться в течение определенного периода времени, а затем перейти к спать, а затем разбудить его в задаче макроса после рендеринга, чтобы рендеринг или взаимодействие с пользователем не зависали!
исходный код
Давайте сначала смоделируем длинную задачу js
код
// style
@keyframes move {
from {
left: 0;
}
to {
left: 100%;
}
}
.move {
position: absolute;
animation: move 5s linear infinite;
}
// dom
<div class="move">123123123</div>
// script
function fnc () {
let i = 0
const start = performance.now()
while (performance.now() - start <= 5000) {
i++
}
return i
}
setTimeout(() => {
fnc()
}, 1000)
Эффект
Как показано на рисунке ниже, когда анимация выполняется в течение 1 с, функция js начинает выполняться, анимация сначала останавливает рендеринг, а затем анимация продолжается после того, как основной стек выполнения js простаивает.
преобразование функции
Преобразуем исходную функцию в функцию генератора
код
// generator 处理原来的函数
function * fnc_ () {
let i = 0
const start = performance.now()
while (performance.now() - start <= 5000) {
yield i++
}
return i
}
// 简易时间分片
function timeSlice (fnc) {
if(fnc.constructor.name !== 'GeneratorFunction') return fnc()
return async function (...args) {
const fnc_ = fnc(...args)
let data
do {
data = fnc_.next()
// 每执行一步就休眠,注册一个宏任务 setTimeout 来叫醒他
await new Promise( resolve => setTimeout(resolve))
} while (!data.done)
return data.value
}
}
setTimeout(async () => {
const fnc = timeSlice(fnc_)
const start = performance.now()
console.log('开始')
const num = await fnc()
console.log('结束', `${(performance.now() - start)/ 1000}s`)
console.log(num)
}, 1000)
Эффект
На анимацию это никак не повлияло, а частота кадров была стабильной, потому что мы разделили трудоемкие задачи на части для выполнения.
Оптимизируйте разделение времени
Вышеупомянутая функция разделения времени будет спать каждый раз, когда она выполняет один шаг, а затем пробуждает ее с помощью задачи макроса, но эта эффективность выполнения определенно относительно низка, мы оптимизируем эффективность выполнения и улучшим время непрерывного выполнения.
код
// 精准时间分片
function timeSlice_ (fnc, time = 25) {
if(fnc.constructor.name !== 'GeneratorFunction') return fnc()
return function (...args) {
const fnc_ = fnc(...args)
function go () {
const start = performance.now()
let data
do {
data = fnc_.next()
} while (!data.done && performance.now() - start < time)
if (data.done) return data.value
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve(go())
} catch(e) {
reject(e)
}
})
})
}
return go()
}
}
setTimeout(async () => {
const fnc1 = timeSlice_(fnc_)
let start = performance.now()
console.log('开始')
const num = await fnc1()
console.log('结束', `${(performance.now() - start)/ 1000}s`)
console.log(num)
}, 1000);
Эффект
Мы разбили функцию на более крупные куски, чтобы эффективность выполнения функции была выше, а на fps немного повлияло, но в пределах допустимого.
Сравните до и после оптимизации
Давайте сравним эффекты до и после оптимизации функции разделения времени.
код
setTimeout(async () => {
const fnc = timeSlice(fnc_)
const fnc1 = timeSlice_(fnc_)
let start = performance.now()
console.log('开始')
const a = await fnc()
console.log('结束', `${(performance.now() - start)/ 1000}s`)
console.log('开始')
start = performance.now()
const b = await fnc1()
console.log('结束', `${(performance.now() - start)/ 1000}s`)
console.log(a, b)
}, 1000);
Эффект
По сравнению с оптимизированной функцией разделения времени эффективность в 4452 раза выше, чем у предыдущей.Что мы сделали, так это улучшили время непрерывного выполнения функции.
наконец
Положение доходности в функции генератора очень критично и должно быть помещено в место, требующее много времени.Оптимизированная функция разделения времени также предоставляет переменную времени, и вы можете изменить свое значение времени в соответствии с реальной ситуацией.