基础
1. 什么是时间片?
如上图所说,同一个cpu永远不可能同时真正运行多个任务,只能看起来像是同时运行。
一段cpu时间,也就是从进程开始运行到被抢占的时间。
例: 你同时输入两篇文档:A.txt和B.txt; 你在A中输入一个字之后,再在B中输入一个字,轮流输入,直至完成。 总的看来你似乎在同时进行两篇文章的录入,你可以说我一边写A一边写B。但是具体到某个字时,就是沿着时间的前进,AB交替进行了。 而你每个字输入所占用的这段时间,我们就可以称之为时间片。
2. 帧率
一帧也就是一个静止的画面,帧率也就是每秒处理图形的次数(主要和显卡有关)。
刷新率(主要和显示器有关)1/60hz 代表 1秒 最多能刷新60次屏幕。刷新率决定了帧率的上限。
如果电脑屏刷新率60hz,也就是最多 1s能刷新60次图像。
换句话说,60hz 差不多一帧需要的时间 1/60 ≈ 16.6ms
上面说的和浏览器什么关系?
浏览器
1. 浏览器一帧
life of frame(一帧),下图为约16.6ms 浏览器在这一帧中要做的事情。
主要 :
输入事件(用户点击等事件优先级高,因为防止用户操作觉得卡顿有延迟)
=> javaScript timers(定时器)
=> (开始帧)事件 (window resize scroll change media)
=> requestAnimationFrame
=> layout(计算样式+更新布局) => 绘制
=> idle peroid(空闲阶段) idle callback
如果用户调用了 requestldleCallback方法,则会在一帧的时间的末尾执行requestldleCallback里的callback 方法。
!注意 js线程和 渲染线程 是互斥的。
2. requestAnimationFrame
let btn = document.getElementById('btn');
let onDiv = document.getElementById('progress-bar');
let start;
let current;
function progress() {
onDiv.style.width = onDiv.offsetWidth + 1 + 'px';
onDiv.innerHTML = onDiv.offsetWidth + '%';
if (onDiv.offsetWidth < 100) {
var old = Date.now() -current;
current = new Date;
requestAnimationFrame(progress)
}
}
btn.addEventListener('click', function () {
onDiv.style.width = 0;
onDiv.innerHTML = '0%'
progress();
current = Date.now()
})
结果
我的电脑显示1帧大概13,14 ,刷新率高。
常见面试题 requestAnimationFrame 和setTimeout区别 。不赘述。
requestAnimationFrame用来做动画,跟着帧率走,在计算样式和更新布局前执行,setTimeout在队列的末尾执行。
requestAnimationFrame 和setTimeout 都是宏任务。
3. requestldleCallback
requestldleCallback(callback,{timeout:1000});
(注意! requestIdLeCallback 这个只有chrome浏览器有,React内部自己实现了一个。)
这一帧时间也就是cpu分配给程序的时间,cpu 给浏览器 16.6ms来处理任务。
具体步骤:
上图在约定时间内完成任务,再归还控制权什么意思?
约定时间: 假如还剩下4ms,约定你callback最好只执行4ms.
归还控制权: 如果你在程序里写了 requestldleCallback 回调,其中callback处理需要20ms,那么他要坚持等待该任务处理完,才能归还cpu占用控制权。
也就是说,如果一帧时间大致 16.6ms,那么如果执行到最后阶段(空闲阶段)还有时间,如果空闲阶段还剩余 2ms,callback方法执行需要20ms,那么浏览器就会卡住 18ms ,直到这个一帧走完,才会走下一帧,归还cpu占用控制权。
例:
//首先,通过阻塞写了一个sleep 睡眠函数后续需要使用
function sleep(time) {
var old = new Date();
while (true) {
var now = new Date();
if (now - old == time) {
return;
}
}
}
var works = [() => {
// 这是任务最小单元 无法再切分
console.log('第一个任务开始');
// sleep(20);
console.log('第一个任务结束')
}, () => {
console.log('第二个任务开始');
// sleep(20);
console.log('第二个任务结束')
}, () => {
console.log('第三个任务开始');
// sleep(20);
console.log('第三个任务结束');
}]
// fiber 是把整个任务 分成若干个很多个小任务,每次执行一个任务。
// 执行完成后会看看有没有剩余时间,如果有,继续执行下一个任务。如果没有放弃执行,交给浏览器调度。
// 当你的刷新频率是60Hz 才是 16.6ms
// 执行第一帧
window.requestIdleCallback(workLoop, { timeout: 1000 });
function workLoop(deadline) {
console.log(deadline.timeRemaining(), '-- 剩余时间 --');
//如果当前的这一帧 有空闲时间 在这里执行works第一个函数操作。
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0) {
perFormUnitOfwork()
}
if (works.length > 0) {
window.requestIdleCallback(workLoop, { timeout: 1000 })
//timeout 1000 告诉浏览器即使没有空闲时间也得帮我执行,不能超过1s
}
}
function perFormUnitOfwork() {
works.shift()();
}
结果:
当前works没有阻塞任务时,workLoop 只执行了一次,也就是 利用 callback 中返回的参数 deadline判断当前 帧剩余时间有没有,如果有,继续执行下一个任务,没有则继续走下一帧 (走requestIdleCallback方法)。
var works = [() => {
// 这是任务最小单元 无法再切分
console.log('第一个任务开始');
sleep(20);
console.log('第一个任务结束')
}, () => {
console.log('第二个任务开始');
sleep(20);
console.log('第二个任务结束')
}, () => {
console.log('第三个任务开始');
sleep(20);
console.log('第三个任务结束');
}]
结果:
说明works走了三次。 requestIdleCallback执行了三次,走了三个帧。