浅析 NextTick
loo41 opened this issue · 3 comments
Next-tick
NextTick 是做什么到?
来自 Vue 官网讲述: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
是否似曾相似
Vue.nextTick(() => {})
this.$nextTick(() => {})
在 vue 中 created 函数钩子函数执行的时候DOM 其实并未进行任何渲染,所以得放在 nextTick 中去获取 dom,与其对于得生命周期钩子函数是 mounted
BUT 中 Vue 中 for 渲染 dom 就算是中 mounted 调用 nextTick也不能获取到具体到 dom,为什么
现在来一探究竟
先来看一下下面这个问题
打印的是什么,请先思考【下面有答案】
console.log(1)
setTimeout(() => {
console.log(8)
}, 2000)
setTimeout(() => {
console.log(3)
Promise.resolve().then(() => {
console.log(4)
})
setTimeout(() => {
console.log(6)
}, 3000)
}, 1000)
new Promise((resolve, reject) => {
console.log(5)
resolve()
}).then(() => {
console.log(7)
})
console.log(2)
谈 Event-Loop
js 是单线程执行,当然,现在又有了一个 worker 创造了多线程环境,但是 worker 受限很多,
js 执行是有一个执行栈,主要分了,宏任务(macro-task)和 微任务(micro-task)
-
宏任务有那些
- setTimeout
- I/O
- setInterval
- setImmediate
- 主线程
- MessageChannel
-
微任务有那些
- Promise 系列 .then .catch .finally
- process.nexttick
- MutationObserver
// 执行流程
(执行一个宏任务,产生宏任务,入栈)
↑
--------↑------- 宏任务 ←--—--
| | |
| | |
| | |
| ↓ | 没有
| 微任务 ------
| |↘
| |(执行所有微任务过程中产生微任务,继续执行)
| |
| | 有 执行完所以微任务
| |
|___________↓
--------------------↓------------------
直到栈为空
我们在来看上面的问题
- 主线程 (宏任务) 打印 1 - 5 - 2 【new Promise 会立即执行,不属于,微任务】
- 执行所有微任务 promise.then 打印 7
- 在执行栈中抛出一个【可以执行的 -> 到时间,虽然,第一个 setTimeout 首先注册,在任务队列栈底】宏任务执行 3 - 4
- 在执行所有微任务 【没有】
- 在抛出一个宏任务执行 8
- 在执行所有微任务 【没有】
- 在抛出一个宏任务执行 6
- 任务执行完毕
- 1 5 2 7 3 4 8 6
在谈 NextTick
我去.......,上面这一大堆和 NextTick 有什么关系?
还真有点关系,看源码,我只剪取了主要部分,源码来自 wepy
// 微任务
let microTimerFunc
// 宏任务
let macroTimerFunc
// 默认不用宏任务,因为微任务到优先级高于宏任务
let useMacroTask = false
// 判断 setImmediate 能不能使用
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 能使用注册宏任务
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
// 是否支持 MessageChannel
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
// 通过命名通道来通信
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// 判断是否可以使用 Promise ie 8 以下不能吧.....
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
// 注册微任务
microTimerFunc = () => {
p.then(flushCallbacks)
}
} else {
// 如果不存在,微任务注册微宏任务
microTimerFunc = macroTimerFunc
}
/**
执行流程就是
宏任务 检测 setImmediate ----- 不能 ----> 降级 MessageChannel ------不能-----> 降级 setTimeout
微任务 Promise ---- 不能 ---> 微任务注册微宏任务
上面的 flushCallbacks 就是你要执行的函数
在 Vue 整个 nextTick 的作用
// 盗用官方的一个例子
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: '未更新'
}
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})
// 在 updateMessage 方法中,更新数据,立即获取更新后的 dom 是获取不到的,所以得把获取 dom 加到事件队列的栈,异步获取更新后的dom
主线程更新前 ---> 遇到宏任务或微任务 ---> 放入栈 ---> 主线程执行完成,更新完成 ----> 执行栈 ---- > 获取更新后的dom
总结
- 到此 nextTick 就结束了
看第一遍我的的结果是:1 2 5 7 3 4 8 6
,看了你的答案,我错了-_- ~~,经常忽略 new Promise 在.then前面动作是同步的。谈 NextTick的源码写的很仔细,清晰明了。Vue.nextTick([callback, context])
的第二个参数也可以说明一下,如果 callback换成箭头函数又需要注意什么
this.$nextTick( ()=> {
console.log(this.$el.textContent) //报错
})
期待你写更多的文章哦
第一段结尾有两句话读不太通顺
看第一遍我的的结果是:
1 2 5 7 3 4 8 6
,看了你的答案,我错了-_- ~~,经常忽略 new Promise 在.then前面动作是同步的。谈 NextTick的源码写的很仔细,清晰明了。Vue.nextTick([callback, context])
的第二个参数也可以说明一下,如果 callback换成箭头函数又需要注意什么this.$nextTick( ()=> { console.log(this.$el.textContent) //报错 })
期待你写更多的文章哦
第一段结尾有两句话读不太通顺
- 嗯嗯 Vue.nextTick
- 最后两句
- 第一个是 vue.nextTick 和 生命周期钩子函数 mounted 相对应,一个获取更新后的 dom,一个页 面挂载完成
- 第二个是,vue中页面挂载完成,也不一定获取得到 v-for 循环中的 dom,遇到这种情况如何去处理
我从来没用过他的第二个参数 context