一、介紹
vue中的nextTick(組件實例是$nextTick)方法是比較常見的,一般想延遲執行某個邏輯時可以使用它。之前我們一般是用setTimeout(0)來模擬類似的操作。
其實,在瀏覽器中nextTick是用es6中的Promise實現的。它創建了一個resolve狀態的Promise示例,以在下一輪微任務中執行它。在不支持Promise的環境中,優先用setImmediate,如果setImmediate也不支持則用setTimeout。
二、示例
來個簡單的示例:
<template>
<div>
<a-button @click="changeState">點我</a-button>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { getWeekdayConfig } from "./http";
@Component({})
export default class Nexttick extends Vue {
public state = 0;
public changeState() {
window.setTimeout(() => {
console.log("settimeout 1");
}, 0);
console.log("同步 1");
Promise.resolve().then(() => {
console.log("promise 1");
});
this.$nextTick(() => {
console.log("nexttick");
});
Promise.resolve().then(() => {
console.log("promise 2");
});
console.log("同步 2");
window.setTimeout(() => {
console.log("settimeout 2");
}, 0);
}
}
</script>
結果:
來看vue的源碼:
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
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(resolve => {
_resolve = resolve
})
}
}
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
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)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
三、拓展-手動實現一個nextTick方法
現在,我們自己寫一個瀏覽器下的nextTick方法
<template>
<div>
<a-button @click="changeState">點我</a-button>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { getWeekdayConfig } from "./http";
@Component({})
export default class Nexttick extends Vue {
public state = 0;
public changeState() {
window.setTimeout(() => {
console.log("settimeout 1");
}, 0);
console.log("同步 1");
this.myNextTick(() => {
console.log("mypromise 1");
});
this.$nextTick(() => {
console.log("nexttick");
});
this.myNextTick(() => {
console.log("mypromise 2");
});
console.log("同步 2");
window.setTimeout(() => {
console.log("settimeout 2");
}, 0);
}
public myNextTick(callback: Function) {
return Promise.resolve().then(() => {
callback();
});
}
}
</script>