vue源码系列之 nextTick实现原理

深入了解网址

https://mp.weixin.qq.com/s?__biz=MzU4NDA1ODcwNA==&mid=2247489507&idx=1&sn=52cd0921b7e4156f5f9927f3d00b26a6&chksm=fd9ec5dccae94ccaa7000fbc0c6eea348ef6d1d7b700ab1cacf47144a50ee2d1fb39f95830bd&mpshare=1&scene=23&srcid=1221vjbd31QYyBbnvc4p2X1l&sharer_sharetime=1608514985512&sharer_shareid=4e2660e4c2de7c62b2fe690d1b52f383#rd

nextTick

nextTick方法主要是使用了宏任务和微任务定义了一个异步方法,
多次调用nextTick 会将方法存入队列中,

nextTick(cb)
callback.push(cb)
timerFunc()
1.尝试采用promise回调
2.尝试采用MutationObserver回调
3.尝试采用setmmediate回调
4.最终采用setTimeout回调
返回promise,支持promise写法

vue next-tick源码

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

//多次调用nextTick 传入的flushSchedulerQueue函数 存入这个数组里面
const callbacks = []
//异步锁
let pending = false 

function flushCallbacks () {
  pending = false
  //拷贝一份数组,清空callbacks
 
  const copies = callbacks.slice(0)
  callbacks.length = 0
  //指行callbacks数组
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
  	(就是又给这个数组传值,传的新值也会加入回调队列去执行)
	如果 flushCallbacks 不做特殊处理,直接循环执行回调函数,
	会导致里面nextTick 中的回调函数会进入回调队列
}

let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
  //先采用的是微任务
    p.then(flushCallbacks)

    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  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)) {
  timerFunc = () => {
  //让后才用的是这个异步函数
  //该方法用来把一些需要长时间运行的操作放在一个回调函数里,
  //在浏览器完成后面的其他语句后,就立刻执行这个回调函数,
    setImmediate(flushCallbacks)
  }
} else {
  // 最后采用宏任务
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

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)
    }
  })
  // 如果异步锁未锁上,锁上异步锁,
  //调用异步函数,准备等同步函数执行完后,就开始执行回调函数队列
  // nextTick源码中使用了一个异步锁的概念,即接收第一个回调函数时
  //  ,先关上锁,执行异步方法。
  // 此时,浏览器处于等待执行完同步代码就执行异步代码的情况。
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

这里有两个问题需要注意:

如何保证只在接收第一个回调函数时执行异步方法?

nextTick源码中使用了一个异步锁的概念,即接收第一个回调函数时,
先关上锁,执行异步方法。此时,浏览器处于等待执行完同
步代码就执行异步代码的情况。

执行 flushCallbacks 函数时为什么需要备份回调函数队列?
执行的也是备份的回调函数队列?

因为,会出现这么一种情况:nextTick 的回调函数中还使用 nextTick。
如果 flushCallbacks 不做特殊处理,直接循环执行回调函数,
会导致里面nextTick 中的回调函数会进入回调队列。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值