事件队列(nextTick5)

只记录个人理解:不涉及底层的知识点。

1. 举个栗子

```
console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});

console.log('script end');

打印:

script start

script end

promise1

promise2

setTimeout
```

2. eventLoop

```
Task(宏任务): 以队列的形式存在。在浏览器环境中(浏览器就可以从其内部进入JavaScript/DOM域),按顺序执行。且在两个task任务间,浏览器可以更新渲染。


Mircotasks (微任务):微任务通常被安排在当前执行脚本之后直接发生的事情上,例如对一批操作做出反应,以免付出一个全新 task 的代价。
mircotask 队列在回调之后处理,只要没有其它执行当中的(mid-execution)代码;或者在每个 task 的末尾处理。在处理 microtasks 队列期间,新添加的 microtasks 添加到队列的末尾并且也被执行。

这也就意味着

script end 在 promise1之前 执行,因为正在运行的代码必须在处理 microtasks 之前完成。

promise2 在 setTimeout 之前打印,因为 microtasks 总是在下一个 task 之前执行。

promise1 在 promise2 前打印:

在处理 microtasks 队列期间,新添加的 microtasks 添加到队列的末尾并且也被执行。

```

3. 举个栗子2

```
这里的p一直都是 resolved 状态

let p = Promise.resolve();
for(let i = 0; i < 5; i++){
a = p.then(()=>{
return new Promise((resolve, reject)=>{
console.log("test")
setTimeout(()=>{
console.log(i);
resolve()
}, 500)
})
})
}
console.log(p)
p.then(()=>{
console.log('完成')
})


流程分析:

1.
主代码块 task 1:

按顺序执行: p的赋值, for循环, p.then(回调),放入microtasks队列中。于是

microtasks1: [1,2,3,4,5],接下来执行 console.log(p); 打印内容

p.then(); 执行

microtasks1: [1,2,3,4,5,6]. 由于这个task还没执行结束,所以依次塞入队列中。

2.
依次完成 microtasks1队列。

每次执行:先打印 'test';

然后依次将 setTimeout的回调 放入 task2,...task6 任务队列中;

最后执行microtasks1队列 打印 完成

microtasks1队列执行完毕后,依次执行后面tasks

然后最后 打印出 0 , 1, 2, 3, 4

```


4. 举个栗子3 (不知道分析的对不对)

promise 一旦解决(settled),或者已解决,调用then时便为它的回调安排一个 microtask。
```
let p = Promise.resolve();
for(let i = 0; i < 5; i++){
p = p.then(()=>{
return new Promise((resolve, reject)=>{
console.log("test")
setTimeout(()=>{
console.log(i);
resolve()
}, 500)
})
})
}
console.log(p)
p.then(()=>{
console.log('完成')
})

过程分析:

1. Promise.resolve() 执行

Promise 已经解决.

2. for 循环 , 第一个 P 是已经解决的 promise.
因此promise.then()其回调函数 放入 microtask 队列;

3. 当for 循环执行到第二次时,这个时候 promise 处于pending 状态. (猜想,这个时候 并不会将 回调推入 microtask队列,而是当promise resolve后才会推入)

按照上述的猜测这个时候,for循环的其他的 回调
暂时不在 microtask队列中,那么是否可以任务,他们还处于(tasks 宏任务队列中呢 还是在哪个特殊的地方)

后面的p.then同理

4. 当这个 task 执行完毕时, 执行 microtask 的代码 。

打印 "test", 把 setTimeout 的回调函数,放入待执行的队列中.

5. 执行下一个 task

打印 i; 并执行 resolve(); 此时 promise 状态发生变化。 then中的回调函数被 推入
microtask队列中();

然后一直循环上面的过程,直到最后一个

p.then(()=>{
console.log('完成')
});

打印 完成 结束;

整体就是:

promise
test
0
test
1
test
2
test
3
test
4
完成
```


5. 再说说 vue 的 nextTick;

```
var callbacks = [];
var pending = false;

function flushCallbacks () {
pending = false;
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++) {
copies[i]();
}
}


function nextTick (cb, ctx) {
var _resolve;
// callbacks 全局属性 所有的回调函数
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
if (useMacroTask) {
macroTimerFunc();
} else {
microTimerFunc();
}
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}


主要分三步: (理解还是很片面,需要在后续的学习中,不断理解)

1. nextTick 本身,往 callback 中推入回调函数。

2. pending 状态值,再上一个 task 宏任务(或者微任务)没有完成前,不会再次 往 任务队列中推入。 也就意味着这个 pending 在一段时间内,一直是 true. 方便我们多次 nextTick 而不是,每一次执行都会 异步操作一次

3. macroTimerFunc. 其实就是一个异步函数 (vue中 setImmediate,MessageChannel,setTimeout)
microTimerFunc (promise or macroTimerFunc)

4. 根据 useMacroTask 来判断 使用 宏任务 还是 微任务 (具体什么情况不清楚)

5. 然后在此次task任务执行完过后,执行下一个
宏任务 或者 微任务.

```


6. Processing model (规范)

```
一个event loop只要存在,就会不断执行下边的步骤:
1.在tasks队列中选择最老的一个task,用户代理可以选择任何task队列,如果没有可选的任务,则跳到下边的microtasks步骤。
2.将上边选择的task设置为正在运行的task。
3.Run: 运行被选择的task。
4.将event loop的currently running task变为null。
5.从task队列里移除前边运行的task。
6.Microtasks: 执行microtasks任务检查点。(也就是执行microtasks队列里的任务)
7.更新渲染(Update the rendering)...
8.如果这是一个worker event loop,但是没有任务在task队列中,并且WorkerGlobalScope对象的closing标识为true,则销毁event loop,中止这些步骤,然后进行定义在Web workers章节的run a worker。
9.返回到第一步。
```


7. microtasks (microtask checkpoint)

```
当用户代理去执行一个microtask checkpoint,如果microtask checkpoint的flag(标识)为false,用户代理必须运行下面的步骤:
1.将microtask checkpoint的flag设为true。
2.Microtask queue handling: 如果event loop的microtask队列为空,直接跳到第八步(Done)。
3.在microtask队列中选择最老的一个任务。
4.将上一步选择的任务设为event loop的currently running task。
5.运行选择的任务。
6.将event loop的currently running task变为null。
7.将前面运行的microtask从microtask队列中删除,然后返回到第二步(Microtask queue handling)。
8.Done: 每一个environment settings object它们的 responsible event loop就是当前的event loop,会给environment settings object发一个 rejected promises 的通知。
9.清理IndexedDB的事务。
10.将microtask checkpoint的flag设为flase。

什么时候调用?

1. 当上下文执行栈为空时,执行一个microtask checkpoint。

2. 在event loop的第六步(Microtasks: Perform a microtask checkpoint)执行checkpoint,也就是在运行task之后,更新渲染之前。
```

猜你喜欢

转载自www.cnblogs.com/jiabaochuan/p/12108229.html