用过Vue的朋友多多少少都知道$nextTick~ 在正式讲解nextTick之前,我想你应该清楚知道 Vue 在更新 DOM 时是异步执行的,因为接下来讲解过程会结合组件更新一起讲~ 事不宜迟,我们直进主题吧(本文以v2.6.14版本的Vue源码进行讲解)

一、nextTick小测试

你真的了解nextTick吗?来,直接上题~

<template>
  <div id="app">
    <p ref="name">{{ name }}</p>
    <button @click="handleClick">修改name</button>
  </div>
</template>
<script>
  export default {
  name: 'App',
  data () {
    return {
      name: '井柏然'
    }
  },
  mounted() {
    console.log('mounted', this.$refs.name.innerText)
  },
  methods: {
    handleClick () {
      this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText))
      this.name = 'jngboran'
      console.log('sync log', this.$refs.name.innerText)
      this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText))
    }
  }
}
</script>

请问上述代码中,当点击按钮“修改name”时,'nextTick1''sync log''nextTick2'对应的this.$refs.name.innerText分别会输出什么?注意,这里打印的是DOM的innerText~(文章结尾处会贴出答案)

如果此时的你有非常坚定的答案,那你可以不用继续往下看了~但如果你对自己的答案有所顾虑,那不如跟着我,接着往下看。相信你看完,不需要看到答案都能有个肯定的答案了~!

二、nextTick源码实现

源码位于core/util/next-tick中。可以将其分为4个部分来看,直接上代码

1. 全局变量

callbacks队列、pending状态

const callbacks = [] // 存放cb的队列
let pending = false // 是否马上遍历队列,执行cb的标志

2. flushCallbacks

遍历callbacks执行每个cb

function flushCallbacks () {
  pending = false // 注意这里,一旦执行,pending马上被重置为false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i  ) {
    copies[i]() // 执行每个cb
  }
}

3. nextTick的异步实现

根据执行环境的支持程度采用不同的异步实现策略

let timerFunc // nextTick异步实现fn
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // Promise方案
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks) // 将flushCallbacks包装进Promise.then中
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // MutationObserver方案
  let counter = 1
  const observer = new MutationObserver(flushCallbacks) // 将flushCallbacks作为观测变化的cb
  const textNode = document.createTextNode(String(counter)) // 创建文本节点
  // 观测文本节点变化
  observer.observe(textNode, {
    characterData: true
  })
  // timerFunc改变文本节点的data,以触发观测的回调flushCallbacks
  timerFunc = () => { 
    counter = (counter   1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // setImmediate方案
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 最终降级方案setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

这里用个真实案例加深对MutationObserver的理解。毕竟比起其他三种异步方案,这个应该是大家最陌生的

const observer = new MutationObserver(() => console.log('观测到文本节点变化'))
const textNode = document.createTextNode(String(1))
observer.observe(textNode, {
    characterData: true
})
console.log('script start')
setTimeout(() => console.log('timeout1'))
textNode.data = String(2) // 这里对文本节点进行值的修改
console.log('script end')

知道对应的输出会是怎么样的吗?

script startscript end会在第一轮宏任务中执行,这点没问题

setTimeout会被放入下一轮宏任务执行

MutationObserver是微任务,所以会在本轮宏任务后执行,所以先于setTimeout

结果如下图:

4. nextTick方法实现

cbPromise方式

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 往全局的callbacks队列中添加cb
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      // 这里是支持Promise的写法
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    // 执行timerFunc,在下一个Tick中执行callbacks中的所有cb
    timerFunc()
  }
  // 对Promise的实现,这也是我们使用时可以写成nextTick.then的原因
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
  • 深入细节,理解pending有什么用,如何运作?
  • 案例1,同一轮Tick中执行2次$nextTicktimerFunc只会被执行一次
this.$nextTick(() => console.log('nextTick1'))
this.$nextTick(() => console.log('nextTick2'))
  • 用图看看更直观?

三、Vue组件的异步更新

这里如果有对Vue组件化、派发更新不是十分了解的朋友,可以先戳这里,看图解Vue响应式原理了解下Vue组件化和派发更新的相关内容再回来看噢~

Vue的异步更新DOM其实也是使用nextTick来实现的,跟我们平时使用的$nextTick其实是同一个~

这里我们回顾一下,当我们改变一个属性值的时候会发生什么?

 根据上图派发更新过程,我们从watcher.update开时讲起,以渲染Watcher为例,进入到queueWatcher

1. queueWatcher做了什么?

// 用来存放Wathcer的队列。注意,不要跟nextTick的callbacks搞混了,都是队列,但用处不同~
const queue: Array<Watcher> = []
function queueWatcher (watcher: Watcher) {
  const id = watcher.id // 拿到Wathcer的id,这个id每个watcher都有且全局唯一
  if (has[id] == null) {
    // 避免添加重复wathcer,这也是异步渲染的优化做法
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    }
    if (!waiting) {
      waiting = true
      // 这里把flushSchedulerQueue推进nextTick的callbacks队列中
      nextTick(flushSchedulerQueue)
    }
  }
}

2. flushSchedulerQueue做了什么?

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  // 排序保证先父后子执行更新,保证userWatcher在渲染Watcher前
  queue.sort((a, b) => a.id - b.id)
  // 遍历所有的需要派发更新的Watcher执行更新
  for (index = 0; index < queue.length; index  ) {
    watcher = queue[index]
    id = watcher.id
    has[id] = null
    // 真正执行派发更新,render -> update -> patch
    watcher.run()
  }
}
  • 最后,一张图搞懂组件的异步更新过程

四、回归题目本身

相信经过上文对nextTick源码的剖析,我们已经揭开它神秘的面纱了。这时的你一定可以坚定地把答案说出来了~话不多说,我们一起核实下,看看是不是如你所想!

如图所示,mounted时候的innerText是“井柏然”的中文 

接下来是点击按钮后,打印结果如图所示 

没错,输出结果如下(意不意外?惊不惊喜?)

sync log 井柏然

nextTick1 井柏然

nextTick2 jngboran

下面简单分析一下每个输出:

this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText))
this.name = 'jngboran'
console.log('sync log', this.$refs.name.innerText)
this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText))

sync log:这个同步打印没什么好说了,相信大部分童鞋的疑问点都不在这里。如果不清楚的童鞋可以先回顾一下EventLoop,这里不多赘述了~

nextTick1:注意其虽然是放在$nextTick的回调中,在下一个tick执行,但是他的位置是在this.name = 'jngboran&#39;的前。也就是说,他的cb会比App组件的派发更新(flushSchedulerQueue)更先进入队列,当nextTick1打印时,App组件还未派发更新,所以拿到的还是旧的DOM值。

nextTick2就不展开了,大家可以自行分析一下。相信大家对它应该是最肯定的,我们平时不就是这样拿到更新后的DOM吗?

最后来一张图加深理解

写在最后,nextTick其实在Vue中也算是比较核心的一个东西了。因为贯穿整个Vue应用的组件化、响应式的派发更新与其息息相关~深入理解nextTick的背后实现原理,不仅能让你在面试的时候一展风采,更能让你在日常开发工作中,少走弯路少踩坑!

以上就是Vue.nextTick纯干货使用方法详解的详细内容,更多关于Vue.nextTick使用方法的资料请关注Devmax其它相关文章!

Vue.nextTick纯干货使用方法详解的更多相关文章

  1. jQuery使用方法

    本文主要介绍了jQuery的使用方法,具有很好的参考价值,下面跟着小编一起来看下吧

  2. Vue.nextTick纯干货使用方法详解

    这篇文章主要为大家介绍了Vue.nextTick使用方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  3. JavaScript Try...Catch 声明的 使用方法

    try...catch的作用是测试代码中的错误。有两种在网页中捕获错误的方法:使用try...catch声明。Try...Catch声明try...catch可以测试代码中的错误。try部分包含需要运行的代码,而catch部分包含错误发生时运行的代码。")}[CtrlA全选注:引入外部Js需再刷新一下页面才能执行]我们可以添加try...catch声明,这样当错误发生时可以采取更适当的措施。下面的例子用try...catch声明重新修改了脚本。

  4. JavaScript For...In 使用方法

    JavaScriptFor...In声明For...In声明用于对数组或者对象的属性进行循环操作。varxvarmycars=newArray()mycars[0]="Saab"mycars[1]="Volvo"mycars[2]="BMW"for{document.write}[CtrlA全选注:引入外部Js需再刷新一下页面才能执行]

  5. jQuery 3.0 的变化及使用方法

    jQuery ,是迄今为止世界上最流行的 JavaScript 库,一直是我们这些 Web 开发者的神器。自从它在 2006 年发布最初版本直到如今,已经有非常多的 Web 开发者在项目中引入了这个优秀的库来使开发工作变得更轻松

  6. 各种AJAX方法的使用比较详解

    AJAX技术经过这么多年的发展,出现了一些框架或类库用于简化开发工作,不同的框架类库的使用方法也各不相同。 现在,再回头看看这些技术,看看这些框架类库,我们能感受到技术在不断地发展,AJAX开发越来越容易了。

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部