-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
nextTick的实现本质是采用了js Event Loop的知识实现。
其使用场景在赋值state后使用,目的是等待watcher触发更新界面后调用回调。本文旨在理解Event Loop的基础上查看vue nextTick的实现,以便于在使用过程中更好的解决问题。
nextTick 伪代码
function nextTick(cb, context) {
// 处理cb
callbacks.push(() => {
if (cb) {
// 回调模式
cb.call(context)
} else {
// 不传cb时,为promise形式
_resolve()
}
})
// 保证只有一次调用
if (!pending) {
pending = true
// 当前事件队列结束后, 触发回调数组
// 此处涉及事件队列知识,请戳https://github.com/Jmingzi/blog/issues/2
// 至于为何要用microtasks和tasks,注释说明
// 在vue2.4以下都只用microtasks,但是后来发现在连续的事件或同一事件的冒泡中,会有问题
// 因为microtasks的优先级始终是最高的。默认使用microtasks
// 何时用tasks?
// 当组件内部的变化导致state变化时,就会使用tasks
if (useMacroTask) {
macroTimerFunc()
} else {
microTimerFunc()
}
}
// 如果没传cb,则返回一个promise对象
if (!cb) {
return new Promise(resolve => {
_resolve = resolve
})
}
}然后再就是定义macroTimerFunc和microTimerFunc时所涉及的点
- 定义macroTimerFunc
在ie中支持setImmediate,非ie中,都是使用MessageChanel发送消息来保持callback始终以队列的形式调用的。除非二者不支持,最后才用的setTimeout保底。
if (setImmediate) {
setImmediate(flushCallbacks)
} else if (MessageChanel) {
// 关于MessageChanel,请戳
// http://www.zhangxinxu.com/study/201202/web-messing-channel-messaging-two-iframe.html
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
setTimeout(() => {
flushCallbacks()
}, 0)
}- 定义microTimerFunc
用Promise模拟的,但是在iOS UIWebViews中有个bug,Promise.then并不会被触发,除非浏览器中有其他事件触发,例如处理setTimeout。所以手动加了个空的setTimeout
如果Promise都不支持,那microTimerFunc = macroTimerFunc
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}疑问
- 关于vue实例中使用microTask与task的区别(也就是注释中的问题描述)
- 为何要使用MessageChanel来模拟task,而不是直接使用setTimeout
w3c html规范中定义了setTimeout的默认最小时间为4ms,而嵌套的timeout表现为10ms,也就是说即使你赋值0,而实际却不是。再由于,setTimeout的时间,会受到任务队列的影响(或其它原因?)其实际时间远大于10ms,这或许就是vue不优先使用setTimeout的原因吧。
此处参考 - 何时用task,何时用microTask
vue内置了一个函数withMacroTask,当组件的state变化时,就会使用task,其它默认都是microTask
推荐阅读
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels