前言:结合vue2源码和自己的理解,不对的地方希望指正
nextTixk
作用:nextTick中的回调函数是在下次DOM更新循环结束之后才会执行。
原理:基于javascript的事件循环机制与vue内部的异步更新策略。响应式数据发生变化时,vue内部会创建一个异步任务队列,将视图更新任务添加到这个异步任务队列中。而nextTick是将用户提供的回调函数添加到异步队列的末尾,保证回调函数的执行一定是在视图更新完之后。
实现:
- 把nextTick中的回调函数塞入一个异步任务中,这个异步任务可以是微任务也可以是宏任务,优先考虑微任务
- 根据浏览器的兼容性进行兼容处理,如浏览器支持Promise的使用时,把回调函数塞入Promise.reslove().then()中进行处理。支持MutationObserver接口时,使用此接口创建一个观察器并在回调中处理nextTick中的回调函数
- 使用类封装模拟一下:
class MYVue {
constructor() {
this.pending = false // 防止多次调用 timerFunc 反正是异步处理 多次push后一次处理即可
this.callbacks = [] // 扩展性 可以多次频繁调用 $nextTick
this.timerFunc = null
this.initTimeFunc()
}
$nextTick(fn) {
return this.nextTick(fn, this)
}
nextTick(cb, ctx) {
this.callbacks.push(function() {
cb && cb.call(ctx)
})
if (!this.pending) {
this.pending = true
this.timerFunc()
}
}
initTimeFunc() {
if (typeof Promise === 'function') {
this.timerFunc = function() {
Promise.resolve().then(() => (this.flushCallbacks()))
}
} else if (typeof MutationObserver === 'function') {
let counter_1 = 1
const observer = new MutationObserver(()=>(this.flushCallbacks()))
let textNode = document.createTextNode(String(counter_1))
observer.observe(textNode, {
characterData: true // 监听节点上的所有字符的变化
})
this.timerFunc = function () {
counter_1 = (counter_1 + 1) % 2
textNode.data = String(counter_1)
}
} else if (typeof setImmediate === 'function') {
this.timerFunc = function() {
setImmediate(()=>(this.flushCallbacks()))
}
} else {
this.timerFunc = function() {
setTimeout(()=>(this.flushCallbacks()), 0)
}
}
}
flushCallbacks() {
this.pending = false
let arr = this.callbacks.slice(0)
this.callbacks.length = 0
arr.forEach(fn => fn())
}
}
const vue = new MYVue()
vue.$nextTick(function() {
console.log(this.pending);
})