Vue2和Vue3的nextTick怎么实现

其他教程   发布日期:前天 18:37   浏览次数:76

这篇“Vue2和Vue3的nextTick怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue2和Vue3的nextTick怎么实现”文章吧。

    一次弄懂 Vue2 和 Vue3 的 nextTick 实现原理

    Vue2 中的 nextTick

    在 Vue2 中,nextTick 的实现基于浏览器的异步任务队列和微任务队列。

    异步任务队列

    在浏览器中,每个宏任务结束后会检查微任务队列,如果有任务则依次执行。当所有微任务执行完成后,才会执行下一个宏任务。因此可以通过将任务作为微任务添加到微任务队列中,来确保任务在所有宏任务执行完毕后立即执行。

    而使用 setTimeout 可以将任务添加到异步任务队列中,在下一轮事件循环中执行。

    在 Vue2 中,如果没有指定执行环境,则会优先使用 Promise.then / MutationObserver,否则使用 setTimeout。

    1. javascript
    2. // src/core/util/next-tick.js
    3. /* istanbul ignore next */
    4. const callbacks = []
    5. let pending = false
    6. function flushCallbacks() {
    7. pending = false
    8. const copies = callbacks.slice(0)
    9. callbacks.length = 0
    10. for (let i = 0; i < copies.length; i++) {
    11. copies[i]()
    12. }
    13. }
    14. let microTimerFunc
    15. let macroTimerFunc
    16. let useMacroTask = false
    17. if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    18. // 使用 setImmediate
    19. macroTimerFunc = () => {
    20. setImmediate(flushCallbacks)
    21. }
    22. } else if (
    23. typeof MessageChannel !== 'undefined' &&
    24. (isNative(MessageChannel) ||
    25. // PhantomJS
    26. MessageChannel.toString() === '[object MessageChannelConstructor]')
    27. ) {
    28. const channel = new MessageChannel()
    29. const port = channel.port2
    30. channel.port1.onmessage = flushCallbacks
    31. macroTimerFunc = () => {
    32. port.postMessage(1)
    33. }
    34. } else {
    35. // 使用 setTimeout
    36. macroTimerFunc = () => {
    37. setTimeout(flushCallbacks, 0)
    38. }
    39. }
    40. if (typeof Promise !== 'undefined' && isNative(Promise)) {
    41. // 使用 Promise.then
    42. const p = Promise.resolve()
    43. microTimerFunc = () => {
    44. p.then(flushCallbacks)
    45. }
    46. } else {
    47. // 使用 MutationObserver
    48. const observer = new MutationObserver(flushCallbacks)
    49. const textNode = document.createTextNode(String(1))
    50. observer.observe(textNode, {
    51. characterData: true
    52. })
    53. microTimerFunc = () => {
    54. textNode.data = String(1)
    55. }
    56. }
    57. export function nextTick(cb?: Function, ctx?: Object) {
    58. let _resolve
    59. callbacks.push(() => {
    60. if (cb) {
    61. try {
    62. cb.call(ctx)
    63. } catch (e) {
    64. handleError(e, ctx, 'nextTick')
    65. }
    66. } else if (_resolve) {
    67. _resolve(ctx)
    68. }
    69. })
    70. if (!pending) {
    71. pending = true
    72. if (useMacroTask) {
    73. macroTimerFunc()
    74. } else {
    75. microTimerFunc()
    76. }
    77. }
    78. if (!cb && typeof Promise !== 'undefined') {
    79. return new Promise(resolve => {
    80. _resolve = resolve
    81. })
    82. }
    83. }

    宏任务和微任务

    在 Vue2 中,可以通过设置 useMacroTask 来使 nextTick 方法使用宏任务或者微任务。

    Vue2 中默认使用微任务,在没有原生 Promise 和 MutationObserver 的情况下,才会改用 setTimeout。

    1. javascript
    2. let microTimerFunc
    3. let macroTimerFunc
    4. let useMacroTask = false // 默认使用微任务
    5. if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    6. // 使用 setImmediate
    7. macroTimerFunc = () => {
    8. setImmediate(flushCallbacks)
    9. }
    10. } else if (
    11. typeof MessageChannel !== 'undefined' &&
    12. (isNative(MessageChannel) ||
    13. // PhantomJS
    14. MessageChannel.toString() === '[object MessageChannelConstructor]')
    15. ) {
    16. const channel = new MessageChannel()
    17. const port = channel.port2
    18. channel.port1.onmessage = flushCallbacks
    19. macroTimerFunc = () => {
    20. port.postMessage(1)
    21. }
    22. } else {
    23. // 使用 setTimeout
    24. macroTimerFunc = () => {
    25. setTimeout(flushCallbacks, 0)
    26. }
    27. }
    28. if (typeof Promise !== 'undefined' && isNative(Promise)) {
    29. // 使用 Promise.then
    30. const p = Promise.resolve()
    31. microTimerFunc = () => {
    32. p.then(flushCallbacks)
    33. }
    34. } else {
    35. // 使用 MutationObserver
    36. const observer = new MutationObserver(flushCallbacks)
    37. const textNode = document.createTextNode(String(1))
    38. observer.observe(textNode, {
    39. characterData: true
    40. })
    41. microTimerFunc = () => {
    42. textNode.data = String(1)
    43. }
    44. }
    45. export function nextTick(cb?: Function, ctx?: Object) {
    46. let _resolve
    47. callbacks.push(() => {
    48. if (cb) {
    49. try {
    50. cb.call(ctx)
    51. } catch (e) {
    52. handleError(e, ctx, 'nextTick')
    53. }
    54. } else if (_resolve) {
    55. _resolve(ctx)
    56. }
    57. })
    58. if (!pending) {
    59. pending = true
    60. if (useMacroTask) {
    61. macroTimerFunc()
    62. } else {
    63. microTimerFunc()
    64. }
    65. }
    66. if (!cb && typeof Promise !== 'undefined') {
    67. return new Promise(resolve => {
    68. _resolve = resolve
    69. })
    70. }
    71. }

    总结

    在 Vue2 中,nextTick 的实现原理基于浏览器的异步任务队列和微任务队列。Vue2 默认使用微任务,在没有原生 Promise 和 MutationObserver 的情况下才会改用 setTimeout。

    Vue3 中的 nextTick

    在 Vue3 中,nextTick 的实现有了较大变化,主要是为了解决浏览器对 Promise 的缺陷和问题。

    Promise 在浏览器中的问题

    在浏览器中,Promise 有一个缺陷:如果 Promise 在当前事件循环中被解决,那么在 then 回调函数之前添加的任务将不能在同一个任务中执行。

    例如:

    1. javascript
    2. Promise.resolve().then(() => {
    3. console.log('Promise 1')
    4. }).then(() => {
    5. console.log('Promise 2')
    6. })
    7. console.log('Hello')

    输出结果为:

    1. Hello
    2. Promise 1
    3. Promise 2

    这是因为 Promise 虽然是微任务,但是需要等到当前宏任务结束才能执行。

    Vue3 中解决 Promise 缺陷的方法

    在 Vue3 中,通过使用 MutationObserver 和 Promise.resolve().then() 来解决 Promise 在浏览器中的缺陷。具体实现如下:

    1. javascript
    2. const queue: Array<Function> = []
    3. let has: { [key: number]: boolean } = {}
    4. let flushing = false
    5. let index = 0
    6. function resetSchedulerState() {
    7. queue.length = 0
    8. has = {}
    9. flushing = false
    10. }
    11. function flushSchedulerQueue() {
    12. flushing = true
    13. let job
    14. while ((job = queue.shift())) {
    15. if (!has[job.id]) {
    16. has[job.id] = true
    17. job()
    18. }
    19. }
    20. resetSchedulerState()
    21. }
    22. let macroTimerFunc
    23. let microTimerFunc
    24. if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    25. macroTimerFunc = () => {
    26. setImmediate(flushSchedulerQueue)
    27. }
    28. } else {
    29. macroTimerFunc = () => {
    30. setTimeout(flushSchedulerQueue, 0)
    31. }
    32. }
    33. if (typeof Promise !== 'undefined' && isNative(Promise)) {
    34. const p = Promise.resolve()
    35. microTimerFunc = () => {
    36. p.then(flushSchedulerQueue)
    37. if (isIOS) setTimeout(noop)
    38. }
    39. } else {
    40. microTimerFunc = macroTimerFunc
    41. }
    42. export function nextTick(fn?: Function): Promise<void> {
    43. const id = index++
    44. const job = fn.bind(null)
    45. queue.push(job)
    46. if (!flushing) {
    47. if (useMacroTask) {
    48. macroTimerFunc()
    49. } else {
    50. microTimerFunc()
    51. }
    52. }
    53. if (!fn && typeof Promise !== 'undefined') {
    54. return new Promise(resolve => {
    55. resolvedPromise.then(() => {
    56. if (has[id] || !queue.includes(job)) {
    57. return
    58. }
    59. queue.splice(queue.indexOf(job), 1)
    60. resolve()
    61. })
    62. })
    63. }
    64. }

    在 Vue3 中,nextTick 的实现原理基于MutationObserver 和 Promise.resolve().then(),通过 MutationObserver 监测 DOM 变化,在下一个微任务中执行回调函数。

    而如果当前浏览器不支持原生 Promise,则使用 setTimeout 来模拟 Promise 的行为,并在回调函数执行前添加一个空的定时器来强制推迟执行(解决 iOS 中 setTimeout 在非激活标签页中的问题)。

    如果需要等待所有回调函数执行完成,则可以通过返回一个 Promise 对象来实现。

    1. javascript
    2. export function nextTick(fn?: Function): Promise<void> {
    3. const id = index++
    4. const job = fn.bind(null)
    5. queue.push(job)
    6. if (!flushing) {
    7. if (useMacroTask) {
    8. macroTimerFunc()
    9. } else {
    10. microTimerFunc()
    11. }
    12. }
    13. if (!fn && typeof Promise !== 'undefined') {
    14. return new Promise(resolve => {
    15. resolvedPromise.then(() => {
    16. if (has[id] || !queue.includes(job)) {
    17. return
    18. }
    19. queue.splice(queue.indexOf(job), 1)
    20. resolve()
    21. })
    22. })
    23. }
    24. }

    以上就是Vue2和Vue3的nextTick怎么实现的详细内容,更多关于Vue2和Vue3的nextTick怎么实现的资料请关注九品源码其它相关文章!