vue2源码笔记之nextTick__Vue.js
发布于 3 年前 作者 banyungong 1092 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

概念

nextTick 接收一个回调函数作为参数, 它的作用是将回调延迟到下次dom更新周期之后, 如果没有回调且在支持Promise的环境中,则返回一个Promise

应用场景

当更新了数据之后, 需要对新的dom做一些操作, 实际上刚更新数据后,视图不会马上更新,这时我们是获取不到更新后的dom,因为还没有重新渲染 在时候就需要nextTick方法

 ...
 methods: {
    handle () {
        // 修改数据
        this.msg = 'change'
        this.$nextTick(() => {
            // dom更新了, 做点什么...
        })
    }
 }
 ....

更新流程

在vue.js中,当状态发生变化时,watcher会得到通知, 然后触发虚拟DOM的渲染流程, 而这个操作是异步的,所以存在一个任务队列, 每当需要渲染时, 会将watcher推送到这个队列 ,在下次事件循环中再让watcher触发渲染的流程

事件循环

js是一门单线程非阻塞的语言,说明在执行js代码时会有一个主线程来处理所有任务, 而非阻塞是指当代码需要处理异步任务时,主线程会挂起这个任务,当异步任务处理完毕之后 主线程再根据一定规则去执行相应的回调

异步任务有两种类型: 宏任务微任务

当执行栈中的所有任务都执行完毕后,会去检查微任务队列中是否有事件存在, 如果存在,则会一次执行微任务队列中事件对应的回调,直到为空。 然后去宏任务队列中取出一个事件,把对应的回调加入到当前执行栈, 当执行栈中的所有任务都执行完,检查微任务队列中是否有事件存在。 无限重复此过程,就形成了一个无限循环:事件循环

常见微任务:

  • Promise.then
    
  • MutationObserver
    
  • Object.observe
    
  • process.nextTick (服务端)
    

常见宏任务:

  • setTimeout
    
  • setInterval
    
  • setImmediate
    
  • MessageChannel
    
  • requestAnimationFrame
    
  • I/O
    
  • UI交互事件
    

执行栈:

当执行一个方法时,js会生成一个与这个方法对应的执行环境,又叫执行上下文。 这个执行环境中有这个方法的私有作用域、上层作用域的指向、方法参数、私有作用域中定义的变量以及this对象。 这个执行环境会被添加到一个执行栈中,这个栈就是执行栈

vue原型上的$nextTick方法只是调用了nextTick方法

import { nextTick } from '../util/index'
Vue.prototype.$nextTick = function(fn) {
    return nextTick(fn, this)
}

简单实现

const callbacks = []
let pending = false
function flushCallbacks () {
  pending = false                           // 执行回调后把状态改回初始
  let copies = callbacks.slice(0)           // 浅拷贝下回调数组
  callbacks.length = 0                      // 一次执行全部回调,执行清空回调
  for(let i = 0; i < copies.length; i++) {
    copies[i]()                             // 调用全部回调
  }
}
let p = Promise.resolve()
let microTimerFunc = () => {                // 把回调放入微任务中
  p.then(flushCallbacks)
}

function nextTick (cb, ctx) {
  callbacks.push(() => {
    if (cb) {
      cb.call(ctx)
    }
  })
  if (!pending){                            // 一次事件循环,调用多个nextTick, 只执行一次
    pending = true
    microTimerFunc()
  }
} 

源码实现

在vue源码中,加上了兼容处理, 如果所在环境不支持Promise, 会转成宏任务的方法, 还加上强制使用宏任务的方式。

const callbacks = []
let pending = false
// 执行回调
function flushCallbacks () {
  pending = false
  let copies = callbacks.slice(0)
  callbacks.length = 0
  for(let i = 0; i < copies.length; i++){
    copies[i]()
  }
}
let microTimerFunc
let macroTimerFunc
let useMacroTask = false

//判断浏览器支持哪种宏任务
if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){
  macroTimerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  MessageChannel.toString === '[object MessageChannelConstructor]'
)) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = flushCallbacks
  macroTimerFunc = () => {
    port.postMessage(1)
  }
} else {
  macroTimerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

// 如果不支持Promise, 直接使用宏任务
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  microTimerFunc = () => {
    p.then(flushCallbacks)
  }
} else {
  microTimerFunc = macroTimerFunc
}

export function withMacroTask (fn){
  return fn._withTask || (fn._withTask = function () {
    useMacroTask = true
    const res = fn.apply(null, arguments)
    useMacroTask = false
    return res
  })
}

export function nextTick (cb, ctx) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      cb.call(ctx)
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending){
    pending = true
    if (useMacroTask) { // 是否使用宏任务
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // 如果没有回调函数,返回一个promise
  if (!cb && typeof Promise !== 'undefined'){
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

// 是否是原生方法
export function isNative (Ctor) {
  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}

```<p style="line-height: 20px; color: #ccc">
        版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
        作者: shetia_az
        原文链接:<a href='https://juejin.im/post/6966065844874919973'>https://juejin.im/post/6966065844874919973</a>
      </p>
回到顶部