vue源码分析之nextTick源码分析-逐行逐析-个人困惑

nextTick的使用背景

  • 在vue项目中,经常会使用到nextTick这个api,一直在猜想其是怎么实现的,今天有幸研读了下,虽然源码又些许问题,但仍值得借鉴

核心源码解析

判断当前环境使用最合适的API并保存函数

promise

  • 判断是否支持promise,如果支持就使用Promise对象的then方法包裹要执行的 flushCallbacks函数

MutationObserver

  • 判断是否支持MutationObserver,如果支持就创建一个MutationObserver 用于监听dom改动之后执行 flushCallbacks 函数 并赋值给 observer

setImmediate

  • 判断是否支持setImmediate,如果支持就使用setImmediate包裹 flushCallbacks函数

setTimeout

  • 如果以上三种都不支持使用setTimeout包裹 flushCallbacks函数
export let isUsingMicroTask = false//是否使用微任务标志

const callbacks = []//任务对列 调用nextTick时传入的回调函数组成的数组
let pending = false//初始化 是否在进行中状态  默认是false  

function flushCallbacks() {
  //循环执行 callbacks  任务队列中的任务
  pending = false
  const copies = callbacks.slice(0)//
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    // 依次执行callbacks数组中的函数
    copies[i]()
  }
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // 向下兼容操作 如果支持Promise 则使用Promise
  const p = Promise.resolve()//直接返回一个resolved状态的Promise对象
  timerFunc = () => {
    p.then(flushCallbacks)
    // 在Promise的then方法中执行 flushCallbacks 函数
    if (isIOS) setTimeout(noop)//ios中在一些异常的webview中,promise结束后任务队列并没有刷新,所以强制执行setTimeout(noop)来刷新任务队列
  }
  isUsingMicroTask = true//重置使用微任务标示为true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||

  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// MutationObserver 属性支持,则使用MutationObserver 
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  // 创建一个MutationObserver 用于监听dom改动之后执行 flushCallbacks 函数 并赋值给 observer
  const textNode = document.createTextNode(String(counter))
  // 创建一个文本节点
  observer.observe(textNode, {
    characterData: true
  })
  // 每次执行timeFunc都会让文本节点的内容在0/1之间切换
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  // 切换之后将新值赋值到那个我们MutationObserver观测的文本节点上去
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// setImmediate 属性支持,则使用setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
// 以上都不支持,则使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

调用异步函数执行回调对列

入参分析

nextTick(cb?: Function, ctx?: Object) {

}
  • cb是 传入的回调函数
  • ctx是函数执行的上下文
  • 而两者又都是可选参数, 有所困惑 下文有解析

函数执行逻辑

  • 将调用nextTick是传入的执行函数添加到 callbacks中
  • 可使用call和传入的ctx修改传入的执行函数的this指向
export function nextTick(cb?: Function, ctx?: Object) {
  /*  nexttick的参数中cb不能为可选参数,如果cb参数不传将没有回调函数,
    nextTick将没有意义,并且ctx将成为第一个参数,由于是形参,ctx将顶替cb,此时ctx相当于没有了,
    resolve出去的将是一个undefined */
  // cb 是 nextTick 包裹的执行函数
  // ctx 是函数执行的上下文 
  // console.log("nextTick000",cb,ctx)
  let _resolve//伪代码
  // 向 callbacks 数组中添加一个函数
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)//修改回调函数的this指向 
      } catch (e) {
        //  异常捕获 如果cb不是函数 捕获异常
        handleError(e, ctx, 'nextTick')
      }
      console.log("00000")
    } else if (_resolve) {
      // 伪代码
      console.log('ctx')
      _resolve(ctx)
    }
  })
  console.log("9999")
  // console.log("pendingcallbacks",callbacks,pending)
  if (!pending) {
    // console.log("pendingcallback0999",callbacks,pending)
    pending = true
    timerFunc()
  }
    if (!cb && typeof Promise !== 'undefined') {//判断浏览器是否支持 Promise
//  cb不可能没有,由于传递的是形参,第一个回调函数如果没有传递,则第二个参数ctx顶上,所以cb不可能没有
// 并且如果cb没有了,则表示没有传递任何参数
    console.log("111")
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
  • 这个代码有删减,因为其余代码执行 有所困惑 下文有解析

代码分析

在这里插入图片描述

  • nexttick的参数中cb不能为可选参数,如果cb参数不传将没有回调函数,nextTick将没有意义,并且ctx将成为第一个参数,由于是形参,ctx将顶替cb,此时ctx相当于没有了,resolve出去的将是一个undefined

代码实测

this.$nextTick()

在这里插入图片描述

  • 结果很意外,得到的是当前组件的实例
  • 实在没看明白,这个ctx 组件的实例 是怎么出现在这个代码中的

依次执行nextTick

  • 循环执行 callbacks 任务队列中的任务
function flushCallbacks() {
  //循环执行 callbacks  任务队列中的任务
  pending = false
  const copies = callbacks.slice(0)//
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    // 依次执行callbacks数组中的函数
    copies[i]()
  }
}

源码

/* @flow */
/* globals MutationObserver */

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// isIE 判段运行环境是否在ie浏览器
// isIOS 判断是否是ios
//isNative  判断函数是否是由JavaScript引擎原生实现的
export let isUsingMicroTask = false//是否使用微任务标志

const callbacks = []//任务对列 调用nextTick时传入的回调函数组成的数组
let pending = false//初始化 是否在进行中状态  默认是false  

function flushCallbacks() {
  //循环执行 callbacks  任务队列中的任务
  pending = false
  const copies = callbacks.slice(0)//
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    // 依次执行callbacks数组中的函数
    copies[i]()
  }
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // 向下兼容操作 如果支持Promise 则使用Promise
  const p = Promise.resolve()//直接返回一个resolved状态的Promise对象
  timerFunc = () => {
    p.then(flushCallbacks)
    // 在Promise的then方法中执行 flushCallbacks 函数
    if (isIOS) setTimeout(noop)//ios中在一些异常的webview中,promise结束后任务队列并没有刷新,所以强制执行setTimeout(noop)来刷新任务队列
  }
  isUsingMicroTask = true//重置使用微任务标示为true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||

  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// MutationObserver 属性支持,则使用MutationObserver 
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  // 创建一个MutationObserver 用于监听dom改动之后执行 flushCallbacks 函数 并赋值给 observer
  const textNode = document.createTextNode(String(counter))
  // 创建一个文本节点
  observer.observe(textNode, {
    characterData: true
  })
  // 每次执行timeFunc都会让文本节点的内容在0/1之间切换
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  // 切换之后将新值赋值到那个我们MutationObserver观测的文本节点上去
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// setImmediate 属性支持,则使用setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
// 以上都不支持,则使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick(cb?: Function, ctx?: Object) {
   /*  nexttick的参数中cb不能为可选参数,如果cb参数不传将没有回调函数,
    nextTick将没有意义,并且ctx将成为第一个参数,由于是形参,ctx将顶替cb,此时ctx相当于没有了,
    resolve出去的将是一个undefined */
  // cb 是 nextTick 包裹的执行函数
  // ctx 是函数执行的上下文 
  // console.log("nextTick000",cb,ctx)
  let _resolve//伪代码
  // 向 callbacks 数组中添加一个函数
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)//修改回调函数的this指向 
      } catch (e) {
        //  异常捕获 如果cb不是函数 捕获异常
        handleError(e, ctx, 'nextTick')
      }
      console.log("00000")
    } else if (_resolve) {
      console.log('ctx')
      _resolve(ctx)
    }
  })
  console.log("9999")
  // console.log("pendingcallbacks",callbacks,pending)
  if (!pending) {
    // console.log("pendingcallback0999",callbacks,pending)
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {//判断浏览器是否支持 Promise
//  cb不可能没有,由于传递的是形参,第一个回调函数如果没有传递,则第二个参数ctx顶上,所以cb不可能没有
// 并且如果cb没有了,则表示没有传递任何参数
    console.log("111")
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

个人困惑

  • 本人实在是不太理解在没有传入参数时,ctx是怎么拿到的,并且返回resolve有何作用
  _resolve(ctx)

致谢

  • 感谢您百忙之中抽时间阅读我写的博客,谢谢你的肯定,也希望对您能有所帮助
  • 如果您有更好的见解请在评论区留言或者私聊我,期待与您的交流
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
`Vue.nextTick`是Vue提供的一个API,它可以让我们在DOM更新之后执行一些操作。在Vue中,当数据发生变化时,Vue异步执行DOM更新操作。也就是说,当我们修改了Vue实例中的某个属性,Vue并不会立即去更新DOM,而是先将这个更新操作加入到一个队列中,在下一个事件循环时,Vue会清空这个队列,依序执行其中的更新操作。 由于Vue异步更新机制,有时候我们需要在DOM更新之后执行一些操作,比如获取更新后的DOM节点的尺寸或位置等。此时,我们就可以使用`Vue.nextTick`来确保这些操作是在DOM更新后执行的。 下面是一个使用`Vue.nextTick`的例子: ```javascript new Vue({ el: '#app', data: { message: 'Hello Vue.js!' }, methods: { updateMessage: function () { this.message = 'Updated!' this.$nextTick(function () { // DOM已经更新 console.log(this.$el.textContent) // "Updated!" }) } } }) ``` 在`updateMessage`方法中,我们首先修改了`message`属性的值,然后调用了`this.$nextTick`方法,在回调函数中打印了`this.$el.textContent`。由于`this.$nextTick`方法会在DOM更新之后执行回调函数,所以打印出来的是更新后的内容。 需要注意的是,`Vue.nextTick`不是立即执行的,而是在下一个事件循环时执行的。这意味着如果我们在某个方法中多次调用`Vue.nextTick`,那么这些回调函数会被加入到同一个队列中,在下一个事件循环时一起执行。这个特性可以帮助我们避免不必要的DOM操作,从而提高性能。 总的来说,`Vue.nextTick`可以帮助我们更好地掌控Vue异步更新机制,避免出现一些奇怪的bug。同时,它也为我们提供了一个优化性能的机会,让我们能够更好地利用Vue异步更新机制。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值