Home  >  Article  >  Web Front-end  >  Detailed explanation of the implementation method of Vue.nextTick

Detailed explanation of the implementation method of Vue.nextTick

小云云
小云云Original
2018-01-22 10:06:361380browse

This article mainly introduces the implementation method of Vue.nextTick. The editor thinks it is quite good. Now I will share it with you and give it as a reference. Let’s follow the editor to take a look, I hope it can help everyone.

This is a source code analysis of vue.nextTick API implementation following event loop and MicroTask.

Preheat, write a sleep function


function sleep (ms) {
 return new Promise(resolve => setTimeout(resolve, ms)
}
async function oneTick (ms) {
 console.log('start')
 await sleep(ms)
 console.log('end')
}
oneTick(3000)

Explain the sleep function

async function Function execution is suspended when await PromiseFn() is performed, and we also know that this PromiseFn is currently executed within a microTask. When the microTask has not been executed, the subsequent macroTask will not be executed. We also implemented a sleep function through the event loop feature of microTask to prevent the execution of console.log

Process

1Execute console.log('start')
2Execute await to pause the execution, wait for the PromiseFn after the await function to complete execution in microTask
3In the sleep function, delay ms to return
4Execute console.log('end') after returning to resolve

nextTick API

How to use nextTick in vue


vue.nextTick(() => {
 // todo...
})

After understanding the usage, take a look at the source code


const nextTick = (function () {
 const callbacks = []
 let pending = false
 let timerFunc // 定时函数

 function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0) // 复制
  callbacks.length = 0 // 清空
  for (let i = 0; i < copies.length; i++) {
   copies[i]() // 逐个执行
  }
 }

 if (typeof Promise !== &#39;undefined&#39; && isNative(Promise)) {
  var p = Promise.resolve()
  var logError = err => { console.error(err) }
  timerFunc = () => {
   p.then(nextTickHandler).catch(logError) // 重点
  }
 } else if (&#39;!isIE MutationObserver&#39;) {
  var counter = 1
  var observer = new MutationObserver(nextTickHandler) // 重点
  var textNode = document.createTextNode(string(conter))

  observer.observe(textNode, {
   characterData: true
  })
  timerFunc = () => {
   counter = (counter + 1) % 2
   textNode.data = String(counter)
  }
 } else {
  timerFunc = () => {
   setTimeout(nextTickHandler, 0) // 重点
  }
 }


 return function queueNextTick (cb, ctx) { // api的使用方式
  let _resolve
  callbacks.push(() => {
   if (cb) {
    try {
     cb.call(ctx)
    } catch (e) {
     err
    }
   } else if (_resolve) {
    _resolve(ctx)
   }
  })
  if (!pending) {
   pending = true
   timerFunc()
  }
  if (!cb && typeof Promise !== &#39;undefined&#39;) {
   return new Promise((resolve, reject) => {
    _resolve =resolve
   })
  }
 }
})() // 自执行函数

If you take a rough look at the source code, you can understand that nextTick api is a self-executing function

Since it is a self-executing function, look directly at its return type, return function queueNextTick (cb, ctx) {...}


return function queueNextTick (cb, ctx) { // api的使用方式
  let _resolve
  callbacks.push(() => {
   if (cb) {
    try {
     cb.call(ctx)
    } catch (e) {
     err
    }
   } else if (_resolve) {
    _resolve(ctx)
   }
  })
  if (!pending) {
   pending = true
   timerFunc()
  }
  if (!cb && typeof Promise !== &#39;undefined&#39;) {
   return new Promise((resolve, reject) => {
    _resolve =resolve
   })
  }
 }

Only focus on the main process queueNextTick function and push the () we passed in => { // todo... } into the callbacks


 if (typeof Promise !== &#39;undefined&#39; && isNative(Promise)) {
  var p = Promise.resolve()
  var logError = err => { console.error(err) }
  timerFunc = () => {
   p.then(nextTickHandler).catch(logError) // 重点
  }
 } else if (&#39;!isIE MutationObserver&#39;) {
  var counter = 1
  var observer = new MutationObserver(nextTickHandler) // 重点
  var textNode = document.createTextNode(string(conter))

  observer.observe(textNode, {
   characterData: true
  })
  timerFunc = () => {
   counter = (counter + 1) % 2
   textNode.data = String(counter)
  }
 } else {
  timerFunc = () => {
   setTimeout(nextTickHandler, 0) // 重点
  }
 }

We can look at this section The three marked points indicate that Promise, MutationObserver or setTimeout(fn, 0) are used in different browser environments to execute nextTickHandler


function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0) // 复制
  callbacks.length = 0 // 清空
  for (let i = 0; i < copies.length; i++) {
   copies[i]() // 逐个执行
  }
 }

nextTickHandler is to put what we put before callbacks() => { // todo... } is executed within the current tasks.

Write a simple nextTick

The source code may be complicated, let’s write a simple nextTick ourselves


const simpleNextTick = (function () {
 let callbacks = []
 let timerFunc

 return function queueNextTick (cb) {
  callbacks.push(() => { // 给callbacks 推入cb()
   cb()
  })

  timerFunc = () => {
   return Promise.resolve().then(() => {
    const fn = callbacks.shift()
    fn()
   })
  }
  timerFunc() // 执行timerFunc,返回到是一个Promise
 }
})()

simpleNextTick(() => {
 setTimeout(console.log, 3000, &#39;nextTick&#39;)
})

We can see from here that the principle of nextTick is to return a Promise, and our todo code is executed in this Promise. Now we can continue to simplify


const simpleNextTick = (function () {
 return function queueNextTick (cb) {
  timerFunc = () => {
   return Promise.resolve().then(() => {
    cb()
   })
  }
  timerFunc()
 }
})()

simpleNextTick(() => {
 setTimeout(console.log, 3000, &#39;nextTick&#39;)
})

directly Write it like this.


const simpleNextTick = function queueNextTick (cb) {
  timerFunc = () => {
   return Promise.resolve().then(() => {
    cb()
   })
  }
  timerFunc()
 }

simpleNextTick(() => {
 setTimeout(console.log, 3000, &#39;nextTick&#39;)
})

This time we also simplify the self-executing function


##

const simpleNextTick = function queueNextTick (cb) {
   return Promise.resolve().then(cb)
 }

simpleNextTick(() => {
 setTimeout(console.log, 3000, &#39;nextTick&#39;)
})

Now we simplify it directly to the end, now I found that the core content of nextTick is Promise, a microtask.

Now we return to vue’s nextTick API official example


<p id="example">{{message}}</p>
var vm = new Vue({
 el: &#39;#example&#39;,
 data: {
  message: &#39;123&#39;
 }
})
vm.message = &#39;new message&#39; // 更改数据
vm.$el.textContent === &#39;new message&#39; // false
Vue.nextTick(function () {
 vm.$el.textContent === &#39;new message&#39; // true
})

It turns out that the dom update after the data in vue is updated is to be executed after the next event loop of.

The principle of using nextTick is mainly to solve the scenario of operating DOM immediately after updating data in a single event.

Now that we know that the core of nextTick uses microTasks, let’s compare the simplified nextTick with the sleep function at the beginning.


const simpleNextTick = function queueNextTick (cb) {
   return Promise.resolve().then(cb)
 }

simpleNextTick(() => {
 setTimeout(console.log, 3000, &#39;nextTick&#39;) // 也可以换成ajax请求
})


function sleep (ms) {
 return new Promise(resolve => setTimeout(resolve, ms) // 也可以换成ajax请求
}
async function oneTick (ms) {
 console.log(&#39;start&#39;)
 await sleep(ms)
 console.log(&#39;end&#39;)
}
oneTick(3000)

We see that the execution results of nextTick and oneTick we wrote are so similar. The only difference is that nextTick wraps the callback with a Promise and returns it and executes it, while oneTick uses await to execute a Promise function, and this Promise has its own wrapped webapi function.

When making an ajax request, can we directly use axios to return the Promise library?


async function getData () {
  const data = await axios.get(url)
  // 操作data的数据来改变dom
  return data
}

This can also achieve the same effect as nextTick

Finally, we can also see from the source code that when the browser environment does not support Promise, you can use MutationObserver or setTimeout(cb, 0) to achieve the same effect. But the ultimate core is microTask

Related recommendations:


Detailed explanation of Vue + Vuex Detailed explanation of using vm.$nextTick instance

Detailed explanation of the nextTick function source code in Vue

Usage examples of process.nextTick in Node.js

The above is the detailed content of Detailed explanation of the implementation method of Vue.nextTick. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn