使用场景
我们在修改一个数据后,立刻获取绑定该数据的dom,会发现dom上的数据并没有变。 而放在this.$nextTick中就可以打印出变化之后的数据。
<div ref="name">{{name}}</div>
data() {
return {
activeName: [],
name: 'mashaodong'
};
},
mounted() {
this.name = '222222';
console.log(this.$refs.name.innerHTML) // mashaodong
this.$nextTick(() => {
console.log(this.$refs.name.innerHTML) // 222222
})
}
看源码前戏必备
要理解dom修改和页面渲染是两件事:
- dom修改是微任务,所以改完数据立刻获取dom,获取的是之前的数据。
- 页面渲染是微任务执行完和下一次宏任务执行前。
Promise
Promise是微任务,修改数据在Promise之前执行,所以微任务列表中dom改变先执行,Promise后执行。就能获取到dom改变后的数据。
MutationObserver
MutationObserver用于对一个dom进行监听,设置一个回调,dom改变后就回执行回调。关于它的使用很巧妙,文章最后我们分析一下。
setImmediate
目前只有最新版本的 Internet Explorer 和Node.js 0.10+实现了该方法。我们可以先不管它。
setTimeout
下一次的宏任务执行,在渲染之后,肯定也就在dom更新之后。是一个向后兼容的方案。
👀源码去喽
nextTick
- 下面重点部分:把函数执行放在一个数组callbacks内,然后看callbacks在哪执行的。目的是连续执行$nextTick时,进行整合,优化程序。
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
function nextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}
callbacks在哪执行
- flushCallback函数把callbacks的方法全执行一边。然后看flushCallback什么时候执行
const 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]();
}
}
flushCallbacks在哪执行
四个地方分别用了执行了它,把flushCallback放在了timerFunc中执行,四种方法了做兼容,顺序是:
- Promise
- MutationObserver
- setImmediate
- setTimeout
var timerFunc;
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function () {
// 标记1
p.then(flushCallbacks);
if (isIOS) { setTimeout(noop); }
};
isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 标记2
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = function () {
// 标记3
setImmediate(flushCallbacks);
};
} else {
timerFunc = function () {
// 标记4
setTimeout(flushCallbacks, 0);
};
}
整体分析(重点)执行顺序
- 标题:callbacks在哪执行:声明callbacks数组,声明flushCallbacks遍历执行callbacks中的函数。声明一个pending,保证下面的timerFunc只执行一次(因为是异步,所以会滞后执行)。
- 标题:flushCallbacks在哪执行:判断环境,决定flushCallbacks采用哪种方式执行,声明timerFunc函数,把flushCallbacks()放在里面,等待执行,timerFunc的执行环境都是异步的
- 标题:nextTick:两件事 a:每次执行把fn放在callbacks中; b:利用pedding保证timerFunc只执行一次;
MutationObserver的使用
MutationObserver并没有监听改变的dom,而是利用每次$nextTick都会执行一次timerFunc,声明一个文本元素textNode,监听textNode,timerFunc执行改变文本元素,从而执行flushCallbacks
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};