首先需要知道的是在不同的环境实现任务的终止和恢复的方法不一样
- 在node环境是通过setimmedate实现
- 在浏览器环境,如果支持MessageChannel则是通过messageChannel实现,如果不支持是通过setTimeOut实现 这里我们主要讲通过MessageChannel实现方法
先讲一下messageChannel的简单用法
js
// 对于MessageChannel用法可以去MDN文档查询
let ch = new MessageChannel();
let {port1,port2} = ch;
// port1执行下面的方法时,会触发port2的事件。并且这个事件是个宏任务,这很重要
port1.postMessage('hello');
// 这个任务是一个宏任务
port2.onmessage = (e=>{
console.log(e)
)
浏览器的任务循环机制需要了解一下,这里主要讲宏任务,就不再说微任务了。
同步任务--->页面刷新(如果需要)--->第一个宏任务--->页面刷新(如果需要)---->第二个宏任务---->页面刷新(如果需要)----省略
javascript
// 用来记录每次启动一个宏任务的时间
let startTime = 0;
// 保存一个宏任务,执行截止时间,endTime = startTime+during
let endTime = 0;
// 一个宏任务可以执行多长时间
const during = 5;
let {port1, port2} = new MessageChannel();
let count = 0;
// 需要不停执行的任务,我们可以把他理解成diff过程需要执行很多的diff任务
function work() {
count++
}
// 当前时间大于任务的截止时间,就需要停止任务
function shouldStop() {
// 为什么采用window.performance.now(),而不是Date.now();
// `Date.now()` 返回的是当前时间戳(以毫秒为单位),精度为毫秒级。并且于系统的当前时间有关
// `window.performance.now()` 返回的是相对于页面加载开始时刻的高精度时间戳(以毫秒为单位),通常精度为微秒级。
return window.performance.now() > endTime;
}
// 用来启动一个宏任务,
function startWork() {
// 需要跟新启动宏任务时,任务的启动时间
startTime = window.performance.now();
// 计算出本次宏任务的截止时间
endTime = startTime + during;
// 发起消息,触发port2绑定的事件开始执行
port1.postMessage(null)
}
// 执行任务
function workConcurrentWork() {
// 每次执行完一个工作的时候,都需要判断是否需要截止执行
while (!shouldStop()) {
// work表示一个又一个需要执行的任务,他应该从任务队列中取出,这里为了简单,没有使用任务队列
work()
}
// 这里我写的是一个死逻辑,正常是判断当前的任务队列中是否还有需要执行的任务
if (count < 100000000) {
// 开启一个新的宏任务
startWork()
}
}
port2.onmessage = workConcurrentWork;
startWork();
let a = document.getElementById('a');
let b = document.getElementById('b');
a.addEventListener('click', () => {
for (let i = 0; i < 100; i++) {
b.append(String(count))
}
})
let a = document.getElementById('a');
let b = document.getElementById('b');
a.addEventListener('click', () => {
let p = document.createElement('p')
p.innerText = String(count)
b.appendChild(p)
})
对应的html代码贴上
css
<body>
<button id="a">add item</button>
<div id="b"></div>
</body>
我们在页面上不停的点击按钮,可以明显发现元素会直接添加进去,页面也不没有发生卡顿,而且我们的count一直在增加。 我们可以看下火焰图
可以看到task任务是一段一段的,表示一个又一个宏任务。而在宏任务的中间,可以执行其他任务,并刷新页面。
总结
我们可以总结出任务机制。就是通过fiber将一个原有的递归调用,变成一个个以fiber节点为执行单元的小任务。通过messageChannel将原本需要同步执行的任务,变成一个又一个宏任务,并且在宏任务中去多次执行fiber任务。 通过时间切片的方法,确定每一个宏任务可以执行多长时间。
至此讲完,如有不足,请指点。