简简单单明白Vue中的nextTick方法,轻便明了Vue中的nextTick

By admin in 4858美高梅 on 2019年6月1日

Vue中的nextTick涉及到Vue中DOM的异步更新,认为很风趣,特意领悟了一下。当中关于nextTick的源码涉及到诸多学问,诸多不太掌握,近来依照本人的有个别觉醒介绍下nextTick。

简言之明了Vue中的nextTick方法,驾驭vuenexttick

Vue中的nextTick涉及到Vue中DOM的异步更新,感到很有意思,特地通晓了一下。当中有关nextTick的源码涉及到繁多学问,繁多不太了解,权且依照本人的1部分醒来介绍下nextTick。

一、示例

先来2个演示掌握下有关Vue中的DOM更新以及nextTick的作用。

模板

<div class="app">
 <div ref="msgDiv">{{msg}}</div>
 <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
 <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
 <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
 <button @click="changeMsg">
  Change the Message
 </button>
</div>

Vue实例

new Vue({
 el: '.app',
 data: {
  msg: 'Hello Vue.',
  msg1: '',
  msg2: '',
  msg3: ''
 },
 methods: {
  changeMsg() {
   this.msg = "Hello world."
   this.msg1 = this.$refs.msgDiv.innerHTML
   this.$nextTick(() => {
    this.msg2 = this.$refs.msgDiv.innerHTML
   })
   this.msg3 = this.$refs.msgDiv.innerHTML
  }
 }
})

点击前

4858美高梅 1

点击后

4858美高梅 2

从图中能够识破:msg1和msg叁显得的内容依然调换此前的,而msg二显示的剧情是改换之后的。其根本原因是因为Vue中DOM更新是异步的(详细表明在末端)。

二、应用场景

上面掌握下nextTick的机要行使的气象及原因。

在Vue生命周期的created()钩子函数实行的DOM操作必然要放在Vue.nextTick()的回调函数中

在created()钩子函数执行的时候DOM
其实并没有开展其它渲染,而此时进展DOM操作无差别于徒劳,所以那边一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之相应的便是mounted()钩子函数,因为该钩子函数推行时享有的DOM挂载和渲染都已产生,此时在该钩子函数中举行其余DOM操作都不会有题目。

在数码变化后要实行的某部操作,而以此操作必要动用随多寡变动而更改的DOM结构的时候,这一个操作都应有放进Vue.nextTick()的回调函数中。

切实原因在Vue的合法文书档案中详尽解释:

Vue 异步推行 DOM 更新。只要观看到多少变动,Vue
将开启三个行列,并缓冲在同样事件循环中暴发的保有数据变动。倘若同一个watcher
被反复接触,只会被推入到行列中二次。这种在缓冲时去除重复数据对于防止不须要的总结和
DOM 操作上非常首要。然后,在下1个的轩然大波循环“tick”中,Vue
刷新队列并进行实际 (已去重的) 专门的事业。Vue 在里头尝试对异步队列使用原生的
Promise.then 和MessageChannel,假若举行碰到不帮助,会利用 setTimeout(fn,
0)代替。

譬如,当你设置vm.someData = ‘new
value’,该器件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下几个“tick”更新。繁多景色大家无需关爱这几个历程,但是假使你想在
DOM 状态更新后做点什么,这就只怕会有一点点为难。即使 Vue.js
经常鼓励开荒人士沿着“数据驱动”的艺术考虑,制止直接触及
DOM,可是有时大家实在要这么做。为了在数码变动之后等待 Vue 完结换代 DOM
,能够在数额变化今后立即接纳Vue.nextTick(callback) 。这样回调函数在 DOM
更新实现后就能调用。

3、nextTick源码浅析

作用

Vue.nextTick用于延迟实施一段代码,它接受1个参数(回调函数和试行回调函数的上下文境况),要是未有提供回调函数,那么将赶回promise对象。

源码

/**
 * Defer a task to execute it asynchronously.
 */
export 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]()
  }
 }

 // the nextTick behavior leverages the microtask queue, which can be accessed
 // via either native Promise.then or MutationObserver.
 // MutationObserver has wider support, however it is seriously bugged in
 // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
 // completely stops working after triggering a few times... so, if native
 // Promise is available, we will use it:
 /* istanbul ignore if */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve()
  var logError = err => { console.error(err) }
  timerFunc = () => {
   p.then(nextTickHandler).catch(logError)
   // in problematic UIWebViews, Promise.then doesn't completely break, but
   // it can get stuck in a weird state where callbacks are pushed into the
   // microtask queue but the queue isn't being flushed, until the browser
   // needs to do some other work, e.g. handle a timer. Therefore we can
   // "force" the microtask queue to be flushed by adding an empty timer.
   if (isIOS) setTimeout(noop)
  }
 } else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
 )) {
  // use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  var counter = 1
  var observer = new MutationObserver(nextTickHandler)
  var textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
   characterData: true
  })
  timerFunc = () => {
   counter = (counter + 1) % 2
   textNode.data = String(counter)
  }
 } else {
  // fallback to setTimeout
  /* istanbul ignore next */
  timerFunc = () => {
   setTimeout(nextTickHandler, 0)
  }
 }

 return function queueNextTick (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)
   }
  })
  if (!pending) {
   pending = true
   timerFunc()
  }
  if (!cb && typeof Promise !== 'undefined') {
   return new Promise((resolve, reject) => {
    _resolve = resolve
   })
  }
 }
})()

率先,先精晓nextTick中定义的四个至关首要变量。

  1. callbacks:用来囤积全体须要推行的回调函数
  2. pending:用来注明是不是正在实践回调函数
  3. timerFunc:用来触发实施回调函数

接下来,了解nextTickHandler()函数。

function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
   copies[i]()
  }
 }

那一个函数用来实践callbacks里积存的有着回调函数。

接下去是将触及形式赋值给timerFunc。

先判别是或不是原生补助promise,假诺帮助,则选取promise来触发执行回调函数;

要不,即使帮忙MutationObserver,则实例化2个观察者对象,观望文本节点发生变化时,触发实施全数回调函数。

一经都不扶助,则动用setTimeout设置延时为0。

最终是queueNextTick函数。因为nextTick是叁个即时函数,所以queueNextTick函数是重返的函数,接受用户传入的参数,用来往callbacks里存入回调函数。

4858美高梅 3

上海体育场面是整套实施流程,关键在于timeFunc(),该函数起到延迟实行的魔法。

从地点的牵线,能够识破timeFunc()一共有三种落成格局。

  1. Promise
  2. MutationObserver
  3. setTimeout

其间Promise和setTimeout很好精晓,是3个异步职分,会在一同职责以及创新DOM的异步职分之后回调具体函数。

上面器重介绍一下MutationObserver。

MutationObserver是HTML5中的新API,是个用来监视DOM变动的接口。他能监听三个DOM对象上发生的子节点删除、属性修改、文本内容改换等等。

调用进度非常粗大略,但是有一点点不太平时:你供给先给她绑回调:

var mo = new MutationObserver(callback)

经过给MutationObserver的构造函数字传送入叁个回调,能获得贰个MutationObserver实例,那么些回调就能够在MutationObserver实例监听到变动时接触。

本条时候你只是给MutationObserver实例绑定好了回调,他现实监听哪个DOM、监听节点删除如故监听属性修改,还未曾设置。而调用他的observer方法就可以形成这一步:

var domTarget = 你想要监听的dom节点
mo.observe(domTarget, {
   characterData: true //说明监听文本内容的修改。
})

4858美高梅 4

在nextTick中
MutationObserver的功用如同上海体育场所所示。在监听到DOM更新后,调用回调函数。

事实上选拔 MutationObserver的原故就是nextTick想要二个异步API,用来在近些日子的联手代码实践实现后,试行笔者想举办的异步回调,包涵Promise和
setTimeout都以基于这几个原因。个中浓密还提到到microtask等剧情,临时不精通,就不深切介绍了。

上述正是本文的全部内容,希望对大家的就学抱有帮衬,也希望大家多多帮助帮客之家。

Vue中的nextTick涉及到Vue中DOM的异步更新,以为很风趣,特地了然了一下。在那之中有关nextTick的源码…

Vue中的nextTick关系到Vue中DOM的异步更新,感觉很风趣,专门精通了一下。个中关于nextTick的源码涉及到相当的多文化,诸多不太精晓,这段时间依照本人的部分觉醒介绍下nextTick

写在前面

一、示例

一、示例

先来八个示范精晓下有关Vue中的DOM更新以及nextTick的作用。

模板

<div class="app">
  <div ref="msgDiv">{{msg}}</div>
  <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
  <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
  <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
  <button @click="changeMsg">
    Change the Message
  </button>
</div>

Vue实例

new Vue({
  el: '.app',
  data: {
    msg: 'Hello Vue.',
    msg1: '',
    msg2: '',
    msg3: ''
  },
  methods: {
    changeMsg() {
      this.msg = "Hello world."
      this.msg1 = this.$refs.msgDiv.innerHTML
      this.$nextTick(() => {
        this.msg2 = this.$refs.msgDiv.innerHTML
      })
      this.msg3 = this.$refs.msgDiv.innerHTML
    }
  }
})

点击前

4858美高梅 5

点击后

4858美高梅 6

从图中得以摸清:msg1和msg三来得的剧情依旧转变在此以前的,而msg2呈现的开始和结果是更改之后的。其根本原因是因为Vue中DOM更新是异步的(详细解释在后边)。

因为对Vue.js很感兴趣,而且日常干活的才干栈也是Vue.js,那多少个月花了些时间研讨学习了须臾间Vue.js源码,并做了总计与出口。

先来一个示范明白下关于Vue中的DOM更新以及nextTick的机能。

二、应用场景

上边精通下nextTick的重大行使的场景及原因。

  • 在Vue生命周期的created()钩子函数进行的DOM操作必然要放在Vue.nextTick()的回调函数中

created()钩子函数实行的时候DOM
其实并未有实行别的渲染,而此刻开始展览DOM操作无异于徒劳,所以这里一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之相应的正是mounted()钩子函数,因为该钩子函数施行时有所的DOM挂载和渲染都已成功,此时在该钩子函数中开始展览别的DOM操作都不会有毛病。

  • 在数码变动后要实践的某部操作,而那些操作须求使用随多寡变动而改造的DOM结构的时候,那个操作都应有放进Vue.nextTick()的回调函数中。

具体原因在Vue的合法文书档案中详尽分解:

Vue 异步推行 DOM 更新。只要观望到数码变化,Vue
将展开贰个连串,并缓冲在同等事件循环中产生的有着数据变动。若是同三个watcher
被频仍接触,只会被推入到行列中壹次。这种在缓冲时去除重复数据对于防止不供给的总计和
DOM 操作上极度主要。然后,在下叁个的轩然大波循环“tick”中,Vue
刷新队列并实行实际 (已去重的) 职业。Vue 在里头尝试对异步队列使用原生的
Promise.thenMessageChannel,假设实践意况不帮助,会选择
setTimeout(fn, 0)简简单单明白Vue中的nextTick方法,轻便明了Vue中的nextTick。代替。

举例说,当您设置vm.someData = 'new value',该零件不会立时重新渲染。当刷新队列时,组件会在事件循环队列清空时的下二个“tick”更新。很多气象大家无需关爱那几个历程,不过假若您想在
DOM 状态更新后做点什么,那就恐怕会有个别为难。尽管 Vue.js
日常鼓励开辟职员沿着“数据驱动”的法子考虑,幸免直接接触
DOM,但是不时我们实在要这么做。为了在数量变化现在等待 Vue 完结更新 DOM
,能够在数额变动之后随即接纳Vue.nextTick(callback) 。那样回调函数在
DOM 更新实现后就能够调用。

文章的原地点:。

模板

三、nextTick源码浅析

作用

Vue.nextTick用以延迟执行1段代码,它承受3个参数(回调函数和实践回调函数的上下文意况),即使未有提供回调函数,那么将回来promise对象。

源码

/**
 * Defer a task to execute it asynchronously.
 */
export 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]()
    }
  }

  // the nextTick behavior leverages the microtask queue, which can be accessed
  // via either native Promise.then or MutationObserver.
  // MutationObserver has wider support, however it is seriously bugged in
  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
  // completely stops working after triggering a few times... so, if native
  // Promise is available, we will use it:
  /* istanbul ignore if */
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    var p = Promise.resolve()
    var logError = err => { console.error(err) }
    timerFunc = () => {
      p.then(nextTickHandler).catch(logError)
      // in problematic UIWebViews, Promise.then doesn't completely break, but
      // it can get stuck in a weird state where callbacks are pushed into the
      // microtask queue but the queue isn't being flushed, until the browser
      // needs to do some other work, e.g. handle a timer. Therefore we can
      // "force" the microtask queue to be flushed by adding an empty timer.
      if (isIOS) setTimeout(noop)
    }
  } else if (!isIE && typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    // use MutationObserver where native Promise is not available,
    // e.g. PhantomJS, iOS7, Android 4.4
    var counter = 1
    var observer = new MutationObserver(nextTickHandler)
    var textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
      characterData: true
    })
    timerFunc = () => {
      counter = (counter + 1) % 2
      textNode.data = String(counter)
    }
  } else {
    // fallback to setTimeout
    /* istanbul ignore next */
    timerFunc = () => {
      setTimeout(nextTickHandler, 0)
    }
  }

  return function queueNextTick (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)
      }
    })
    if (!pending) {
      pending = true
      timerFunc()
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        _resolve = resolve
      })
    }
  }
})()

首先,先了解nextTick中定义的多个珍视变量。

  • callbacks

用来囤积全部要求实行的回调函数

  • pending

用来表达是还是不是正在实施回调函数

  • timerFunc

用来触发试行回调函数

接下来,了解nextTickHandler()函数。

function nextTickHandler () {
    pending = false
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i < copies.length; i++) {
      copies[i]()
    }
  }

其1函数用来进行callbacks里积攒的持有回调函数。

接下去是将触发情势赋值给timerFunc

  • 先判别是或不是原生帮衬promise,若是援助,则动用promise来触发试行回调函数;
  • 不然,即使协理MutationObserver,则实例化三个观看者对象,观望文本节点产生变化时,触发施行全数回调函数。
  • 即使都不辅助,则接纳setTimeout设置延时为0。

最后是queueNextTick函数。因为nextTick是2个即时函数,所以queueNextTick函数是回去的函数,接受用户传入的参数,用来往callbacks里存入回调函数。

4858美高梅 7

上海教室是整套执行流程,关键在于timeFunc(),该函数起到延迟实践的成效。

从上边的介绍,可以查出timeFunc()合计有三种达成格局。

  • Promise
  • MutationObserver
  • setTimeout

其中PromisesetTimeout很好通晓,是一个异步职分,会在壹块职责以及立异DOM的异步职责之后回调具体函数。

下边珍视介绍一下MutationObserver

MutationObserver是HTML5中的新API,是个用来监视DOM变动的接口。他能监听二个DOM对象上发生的子节点删除、属性修改、文本内容更动等等。
调用进程极粗略,然则有一点点不太平时:你需求先给她绑回调:

var mo = new MutationObserver(callback)

通过给MutationObserver的构造函数字传送入3个回调,能得到1个MutationObserver实例,这几个回调就能够在MutationObserver实例监听到变动时接触。

本条时候你只是给MutationObserver实例绑定好了回调,他现实监听哪个DOM、监听节点删除依旧监听属性修改,还尚未安装。而调用他的observer艺术就足以做到这一步:

var domTarget = 你想要监听的dom节点
mo.observe(domTarget, {
      characterData: true //说明监听文本内容的修改。
})

4858美高梅 8

nextTick
MutationObserver的效果就如上海教室所示。在监听到DOM更新后,调用回调函数。

实际上使用 MutationObserver的缘由正是
nextTick想要一个异步API,用来在最近的协同代码实行完结后,试行笔者想进行的异步回调,包蕴Promise
setTimeout都以依据那个缘故。在这之中深切还涉及到microtask等内容,权且不知情,就不深切介绍了。

在攻读进度中,为Vue加上了国文的注释,希望可以对别的想深造Vue源码的伴儿有所支持。

<div class="app">
 <div ref="msgDiv">{{msg}}</div>
 <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
 <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
 <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
 <button @click="changeMsg">
  Change the Message
 </button>
</div>

想必会有领悟存在偏差的地点,应接提issue建议,共同学习,共同进步。

Vue实例

操作DOM

new Vue({
 el: '.app',
 data: {
  msg: 'Hello Vue.',
  msg1: '',
  msg2: '',
  msg3: ''
 },
 methods: {
  changeMsg() {
   this.msg = "Hello world."
   this.msg1 = this.$refs.msgDiv.innerHTML
   this.$nextTick(() => {
    this.msg2 = this.$refs.msgDiv.innerHTML
   })
   this.msg3 = this.$refs.msgDiv.innerHTML
  }
 }
})

在动用vue.js的时候,偶然候因为有的一定的事情场景,不得不去操作DOM,比如那样:

点击前

<template>
 <div>
 <div ref="test">{{test}}</div>
 <button @click="handleClick">tet</button>
 </div>
</template>


export default {
 data () {
  return {
   test: 'begin'
  };
 },
 methods () {
  handleClick () {
   this.test = 'end';
   console.log(this.$refs.test.innerText);//打印“begin”
  }
 }
}

4858美高梅 9

打字与印刷的结果是begin,为何大家鲜明已经将test设置成了“end”,获取真实DOM节点的innerText却尚无得到大家预料中的“end”,而是获得此前的值“begin”呢?

点击后

Watcher队列

4858美高梅 10

带着疑问,大家找到了Vue.js源码的沃特ch落成。当某些响应式数据产生变化的时候,它的setter函数会公告闭包中的Dep,Dep则会调用它管理的装有沃特ch对象。触发Watch对象的update达成。我们来看一下update的达成。

从图中能够得知:msg一和msg三体现的开始和结果还是转换此前的,而msg二呈现的内容是改动之后的。其根本原因是因为Vue中DOM更新是异步的(详细分解在后面)。

update () {
 /* istanbul ignore else */
 if (this.lazy) {
  this.dirty = true
 } else if (this.sync) {
  /*同步则执行run直接渲染视图*/
  this.run()
 } else {
  /*异步推送到观察者队列中,下一个tick时调用。*/
  queueWatcher(this)
 }
}

二、应用场景

4858美高梅,我们发掘Vue.js暗中认可是行使异步试行DOM更新。

上面明白下nextTick的重中之重行使的气象及原因。

当异步实施update的时候,会调用queue沃特cher函数。

在Vue生命周期的created()钩子函数进行的DOM操作必然要放在Vue.nextTick()的回调函数中

 /*将一个观察者对象push进观察者队列,在队列中已经存在相同的id则该观察者对象将被跳过,除非它是在队列被刷新时推送*/
export function queueWatcher (watcher: Watcher) {
 /*获取watcher的id*/
 const id = watcher.id
 /*检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验*/
 if (has[id] == null) {
 has[id] = true
 if (!flushing) {
  /*如果没有flush掉,直接push到队列中即可*/
  queue.push(watcher)
 } else {
  // if already flushing, splice the watcher based on its id
  // if already past its id, it will be run next immediately.
  let i = queue.length - 1
  while (i >= 0 && queue[i].id > watcher.id) {
  i--
  }
  queue.splice(Math.max(i, index) + 1, 0, watcher)
 }
 // queue the flush
 if (!waiting) {
  waiting = true
  nextTick(flushSchedulerQueue)
 }
 }
}

在created()钩子函数施行的时候DOM
其实并未进行别的渲染,而此刻拓展DOM操作未有差距于徒劳,所以那边一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数实施时怀有的DOM挂载和渲染都已做到,此时在该钩子函数中张开其余DOM操作都不会有题目。

翻看queue沃特cher的源码大家开采,沃特ch对象并不是随即更新视图,而是被push进了三个类别queue,此时境况处于waiting的情事,那时候会一而再会有沃特ch对象被push进这几个行列queue,等待下多个tick时,这么些沃特ch对象才会被遍历收取,更新视图。同不常间,id重复的沃特cher不会被一再进入到queue中去,因为在最后渲染时,我们只供给关爱数据的结尾结果。

在多少变动后要施行的有个别操作,而那些操作必要运用随多寡变动而改动的DOM结构的时候,这些操作都应当放进Vue.nextTick()的回调函数中。

那正是说,什么是下二个tick?

现实原因在Vue的法定文书档案中详细分解:

nextTick

Vue 异步实施 DOM 更新。只要观看到数量变动,Vue
将拉开一个队列,并缓冲在同一事件循环中发出的全体数据变动。借使同1个watcher
被1再触及,只会被推入到行列中一遍。这种在缓冲时去除重复数据对于防止不须求的图谋和
DOM 操作上格外重大。然后,在下多个的风云循环“tick”中,Vue
刷新队列并实践实际 (已去重的) 职业。Vue 在里头尝试对异步队列使用原生的
Promise.then 和MessageChannel,假诺进行情况不支持,会使用 setTimeout(fn,
0)取代。

vue.js提供了贰个nextTick函数,其实也正是下面调用的nextTick。

举例,当您设置vm.someData = ‘new
value’,该器件不会即时重新渲染。当刷新队列时,组件会在事变循环队列清空时的下三个“tick”更新。大多情景大家没有要求关爱这些历程,不过倘若你想在
DOM 状态更新后做点什么,那就可能会略带劳顿。即便 Vue.js
平常鼓励开采人士沿着“数据驱动”的不二等秘书籍思考,制止直接触及
DOM,然而有时大家真的要这么做。为了在数量变动之后等待 Vue 完毕换代 DOM
,能够在数码变化以往马上使用Vue.nextTick(callback) 。那样回调函数在 DOM
更新达成后就能够调用。

nextTick的兑现相比轻便,实行的目标是在microtask也许task中推入二个funtion,在此时此刻栈推行达成(也行还有1部分排在前边的内需推行的职分)以往实行nextTick传入的funtion,看一下源码:

3、nextTick源码浅析

/**
 * Defer a task to execute it asynchronously.
 */
 /*
 延迟一个任务使其异步执行,在下一个tick时执行,一个立即执行函数,返回一个function
 这个函数的作用是在task或者microtask中推入一个timerFunc,在当前调用栈执行完以后以此执行直到执行到timerFunc
 目的是延迟到当前调用栈执行完以后执行
*/
export const nextTick = (function () {
 /*存放异步执行的回调*/
 const callbacks = []
 /*一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送*/
 let pending = false
 /*一个函数指针,指向函数将被推送到任务队列中,等到主线程任务执行完时,任务队列中的timerFunc被调用*/
 let timerFunc

 /*下一个tick时的回调*/
 function nextTickHandler () {
 /*一个标记位,标记等待状态(即函数已经被推入任务队列或者主线程,已经在等待当前栈执行完毕去执行),这样就不需要在push多个回调到callbacks时将timerFunc多次推入任务队列或者主线程*/
 pending = false
 /*执行所有callback*/
 const copies = callbacks.slice(0)
 callbacks.length = 0
 for (let i = 0; i < copies.length; i++) {
  copies[i]()
 }
 }

 // the nextTick behavior leverages the microtask queue, which can be accessed
 // via either native Promise.then or MutationObserver.
 // MutationObserver has wider support, however it is seriously bugged in
 // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
 // completely stops working after triggering a few times... so, if native
 // Promise is available, we will use it:
 /* istanbul ignore if */

 /*
 这里解释一下,一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法
 优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法都会在microtask中执行,会比setTimeout更早执行,所以优先使用。
 如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。
 */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
 /*使用Promise*/
 var p = Promise.resolve()
 var logError = err => { console.error(err) }
 timerFunc = () => {
  p.then(nextTickHandler).catch(logError)
  // in problematic UIWebViews, Promise.then doesn't completely break, but
  // it can get stuck in a weird state where callbacks are pushed into the
  // microtask queue but the queue isn't being flushed, until the browser
  // needs to do some other work, e.g. handle a timer. Therefore we can
  // "force" the microtask queue to be flushed by adding an empty timer.
  if (isIOS) setTimeout(noop)
 }
 } else if (typeof MutationObserver !== 'undefined' && (
 isNative(MutationObserver) ||
 // PhantomJS and iOS 7.x
 MutationObserver.toString() === '[object MutationObserverConstructor]'
 )) {
 // use MutationObserver where native Promise is not available,
 // e.g. PhantomJS IE11, iOS7, Android 4.4
 /*新建一个textNode的DOM对象,用MutationObserver绑定该DOM并指定回调函数,在DOM变化的时候则会触发回调,该回调会进入主线程(比任务队列优先执行),即textNode.data = String(counter)时便会触发回调*/
 var counter = 1
 var observer = new MutationObserver(nextTickHandler)
 var textNode = document.createTextNode(String(counter))
 observer.observe(textNode, {
  characterData: true
 })
 timerFunc = () => {
  counter = (counter + 1) % 2
  textNode.data = String(counter)
 }
 } else {
 // fallback to setTimeout
 /* istanbul ignore next */
 /*使用setTimeout将回调推入任务队列尾部*/
 timerFunc = () => {
  setTimeout(nextTickHandler, 0)
 }
 }

 /*
 推送到队列中下一个tick时执行
 cb 回调函数
 ctx 上下文
 */
 return function queueNextTick (cb?: Function, ctx?: Object) {
 let _resolve
 /*cb存到callbacks中*/
 callbacks.push(() => {
  if (cb) {
  try {
   cb.call(ctx)
  } catch (e) {
   handleError(e, ctx, 'nextTick')
  }
  } else if (_resolve) {
  _resolve(ctx)
  }
 })
 if (!pending) {
  pending = true
  timerFunc()
 }
 if (!cb && typeof Promise !== 'undefined') {
  return new Promise((resolve, reject) => {
  _resolve = resolve
  })
 }
 }
})()

作用

它是一个当下实践函数,重返3个queueNextTick接口。

Vue.nextTick用于延迟施行一段代码,它承受3个参数(回调函数和实行回调函数的上下文情状),假使未有提供回调函数,那么将回来promise对象。

传播的cb会被push进callbacks中存放起来,然后执行timerFunc(pending是2个动静标志,保险timerFunc在下1个tick从前只进行二次)。

源码

timerFunc是什么?

/**
 * Defer a task to execute it asynchronously.
 */
export 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]()
  }
 }

 // the nextTick behavior leverages the microtask queue, which can be accessed
 // via either native Promise.then or MutationObserver.
 // MutationObserver has wider support, however it is seriously bugged in
 // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
 // completely stops working after triggering a few times... so, if native
 // Promise is available, we will use it:
 /* istanbul ignore if */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve()
  var logError = err => { console.error(err) }
  timerFunc = () => {
   p.then(nextTickHandler).catch(logError)
   // in problematic UIWebViews, Promise.then doesn't completely break, but
   // it can get stuck in a weird state where callbacks are pushed into the
   // microtask queue but the queue isn't being flushed, until the browser
   // needs to do some other work, e.g. handle a timer. Therefore we can
   // "force" the microtask queue to be flushed by adding an empty timer.
   if (isIOS) setTimeout(noop)
  }
 } else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
 )) {
  // use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  var counter = 1
  var observer = new MutationObserver(nextTickHandler)
  var textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
   characterData: true
  })
  timerFunc = () => {
   counter = (counter + 1) % 2
   textNode.data = String(counter)
  }
 } else {
  // fallback to setTimeout
  /* istanbul ignore next */
  timerFunc = () => {
   setTimeout(nextTickHandler, 0)
  }
 }

 return function queueNextTick (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)
   }
  })
  if (!pending) {
   pending = true
   timerFunc()
  }
  if (!cb && typeof Promise !== 'undefined') {
   return new Promise((resolve, reject) => {
    _resolve = resolve
   })
  }
 }
})()

看了源码开采timerFunc会检验当前条件而不相同实现,其实正是依据Promise,MutationObserver,setTimeout优先级,哪个存在利用哪个,最不济的意况下行使setTimeout。

首先,先掌握nextTick中定义的七个非常重要变量。

这里解释一下,1共有Promise、MutationObserver以及setTimeout三种尝试获得timerFunc的办法。
预先利用Promise,在Promise不存在的事态下行使MutationObserver,那多少个方法的回调函数都会在microtask中实行,它们会比setTimeout更早试行,所以优先利用。
即便上述三种方法都不援助的情形则会动用setTimeout,在task尾巴部分推入那么些函数,等待调用实行。

  1. callbacks:用来积累全数必要施行的回调函数
  2. pending:用来表明是不是正在施行回调函数
  3. timerFunc:用来触发执行回调函数

为什么要前期采用microtask?作者在顾轶灵在腾讯网的应对中学习到:

接下来,了解nextTickHandler()函数。

JS 的 event loop 实行时会区分 task 和 microtask,引擎在各样 task
试行完成,从队列中取下3个 task 来举行以前,会先实施完全体 microtask
队列中的 microtask。

function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
   copies[i]()
  }
 }

setTimeout 回调会被分配到3个新的 task 中实行,而 Promise 的
resolver、MutationObserver 的回调都会被计划到2个新的 microtask
中实行,会比 setTimeout 发生的 task 先推行。

其一函数用来执行callbacks里积累的全部回调函数。

要创立三个新的 microtask,优先采纳 Promise,要是浏览器不帮衬,再品尝
MutationObserver。

接下去是将触发格局赋值给timerFunc。

实际可怜,只好用 setTimeout 创造 task 了。

先判断是或不是原生帮忙promise,假若辅助,则运用promise来触发实施回调函数;

为何要用 microtask?

不然,假诺援救MutationObserver,则实例化多个观望者对象,观看文本节点发生变化时,触发试行全部回调函数。

基于 HTML 斯坦dard,在种种 task 运维完未来,UI 都会重渲染,那么在
microtask 中就水到渠成多少更新,当前 task 停止就足以获取最新的 UI 了。

假如都不扶助,则应用setTimeout设置延时为0。

反之固然新建八个 task 来做多少更新,那么渲染就能够进展三次。

最终是queueNextTick函数。因为nextTick是三个即时函数,所以queueNextTick函数是回来的函数,接受用户传入的参数,用来往callbacks里存入回调函数。

仿照效法顾轶灵博客园的应对

4858美高梅 11

首先是Promise,(Promise.resolve()).then()能够在microtask中加入它的回调,

上海体育场合是成套推行流程,关键在于timeFunc(),该函数起到延迟进行的意义。

MutationObserver新建二个textNode的DOM对象,用MutationObserver绑定该DOM并点名回调函数,在DOM变化的时候则会触发回调,该回调会跻身microtask,即textNode.data
= String(counter)时便会参与该回调。

从地方的牵线,能够查出timeFunc()1共有两种达成格局。

setTimeout是最后的一种备选方案,它会将回调函数出席task中,等到执行。

  1. Promise
  2. MutationObserver
  3. setTimeout

综上,nextTick的目标正是产生三个回调函数出席task大概microtask中,当前栈实践完事后(只怕当中还大概有别的排在前边的函数)调用该回调函数,起到了异步触发(即下三个tick时接触)的目标。

在那之中Promise和setTimeout很好精晓,是四个异步职分,会在一道任务以及更新DOM的异步任务之后回调具体函数。

flushSchedulerQueue

上面器重介绍一下MutationObserver。

/*Github:https://github.com/answershuto*/
/**
 * Flush both queues and run the watchers.
 */
 /*nextTick的回调函数,在下一个tick时flush掉两个队列同时运行watchers*/
function flushSchedulerQueue () {
 flushing = true
 let watcher, id

 // Sort queue before flush.
 // This ensures that:
 // 1. Components are updated from parent to child. (because parent is always
 // created before the child)
 // 2. A component's user watchers are run before its render watcher (because
 // user watchers are created before the render watcher)
 // 3. If a component is destroyed during a parent component's watcher run,
 // its watchers can be skipped.
 /*
 给queue排序,这样做可以保证:
 1.组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。
 2.一个组件的user watchers比render watcher先运行,因为user watchers往往比render watcher更早创建
 3.如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过。
 */
 queue.sort((a, b) => a.id - b.id)

 // do not cache length because more watchers might be pushed
 // as we run existing watchers
 /*这里不用index = queue.length;index > 0; index--的方式写是因为不要将length进行缓存,因为在执行处理现有watcher对象期间,更多的watcher对象可能会被push进queue*/
 for (index = 0; index < queue.length; index++) {
 watcher = queue[index]
 id = watcher.id
 /*将has的标记删除*/
 has[id] = null
 /*执行watcher*/
 watcher.run()
 // in dev build, check and stop circular updates.
 /*
  在测试环境中,检测watch是否在死循环中
  比如这样一种情况
  watch: {
  test () {
   this.test++;
  }
  }
  持续执行了一百次watch代表可能存在死循环
 */
 if (process.env.NODE_ENV !== 'production' && has[id] != null) {
  circular[id] = (circular[id] || 0) + 1
  if (circular[id] > MAX_UPDATE_COUNT) {
  warn(
   'You may have an infinite update loop ' + (
   watcher.user
    ? `in watcher with expression "${watcher.expression}"`
    : `in a component render function.`
   ),
   watcher.vm
  )
  break
  }
 }
 }

 // keep copies of post queues before resetting state
 /**/
 /*得到队列的拷贝*/
 const activatedQueue = activatedChildren.slice()
 const updatedQueue = queue.slice()

 /*重置调度者的状态*/
 resetSchedulerState()

 // call component updated and activated hooks
 /*使子组件状态都改编成active同时调用activated钩子*/
 callActivatedHooks(activatedQueue)
 /*调用updated钩子*/
 callUpdateHooks(updatedQueue)

 // devtool hook
 /* istanbul ignore if */
 if (devtools && config.devtools) {
 devtools.emit('flush')
 }
}

MutationObserver是HTML5中的新API,是个用来监视DOM变动的接口。他能监听二个DOM对象上发生的子节点删除、属性修改、文本内容改换等等。

flushSchedulerQueue是下3个tick时的回调函数,主要目标是举行沃特cher的run函数,用来更新视图

调用进度很轻便,不过有一点点不太日常:你须求先给她绑回调:

为何要异步更新视图

var mo = new MutationObserver(callback)

来看一下底下那1段代码

通过给MutationObserver的构造函数字传送入贰个回调,能赢得2个MutationObserver实例,那一个回调就能在MutationObserver实例监听到变动时接触。

<template>
 <div>
 <div>{{test}}</div>
 </div>
</template>


export default {
 data () {
  return {
   test: 0
  };
 },
 created () {
  for(let i = 0; i < 1000; i++) {
  this.test++;
  }
 }
}

其偶然候你只是给MutationObserver实例绑定好了回调,他现实监听哪个DOM、监听节点删除照旧监听属性修改,还尚未安装。而调用他的observer方法就足以成功这一步:

未来有那样的一种情形,created的时候test的值会被++循环施行一千次。

var domTarget = 你想要监听的dom节点
mo.observe(domTarget, {
   characterData: true //说明监听文本内容的修改。
})

历次++时,都会依附响应式触发setter->Dep->沃特cher->update->patch。

4858美高梅 12

1旦那时未有异步更新视图,那么每回++都会一贯操作DOM更新视图,那是极度消耗品质的。

在nextTick中
MutationObserver的意义就好像上海教室所示。在监听到DOM更新后,调用回调函数。

故而Vue.js完结了2个queue队列,在下三个tick的时候会联合执行queue中沃特cher的run。同一时间,具有同样id的沃特cher不会被再次投入到该queue中去,所以不会施行1000次沃特cher的run。最后更新视图只会平昔将test对应的DOM的0形成1000。
保障更新视图操作DOM的动作是在脚下栈实践完现在下二个tick的时候调用,大大优化了性能。

实在使用 MutationObserver的由来就是nextTick想要2个异步API,用来在当下的联手代码施行实现后,实践小编想举行的异步回调,包蕴Promise和
setTimeout都是依附这些缘故。在那之中深刻还涉及到microtask等内容,暂且不驾驭,就不深远介绍了。

访问真正DOM节点更新后的多寡

以上就是本文的全体内容,希望对大家的读书抱有接济,也期望大家多多协理脚本之家。

故此大家须要在修改data中的数据后走访真正的DOM节点更新后的数量,只要求如此,大家把稿子第一个例子实行修改。

你也许感兴趣的小说:

  • 浅谈Vuejs中nextTick()异步更新队列源码深入分析
  • 详解Vue + Vuex 怎么着利用
    vm.$nextTick
  • 浅谈Vue.nextTick
    的落到实处方式
  • Vue中之nextTick函数源码深入分析详解
  • 详解从Vue.js源码看异步更新DOM战略及nextTick
  • 关于Vue.nextTick()的不易使用办法剖析
  • vue.js全局API之nextTick周密分析
  • vue之nextTick周全分析
  • 深深掌握Vue nextTick 机制
<template>
 <div>
 <div ref="test">{{test}}</div>
 <button @click="handleClick">tet</button>
 </div>
</template>


export default {
 data () {
  return {
   test: 'begin'
  };
 },
 methods () {
  handleClick () {
   this.test = 'end';
   this.$nextTick(() => {
    console.log(this.$refs.test.innerText);//打印"end"
   });
   console.log(this.$refs.test.innerText);//打印“begin”
  }
 }
}

动用Vue.js的global
API的$nextTick方法,就能够在回调中拿走已经更新好的DOM实例了。

如上就是本文的全体内容,希望对我们的就学抱有帮助,也指望我们多多支持脚本之家。

您恐怕感兴趣的文章:

  • 简言之明了Vue中的nextTick方法
  • 浅谈Vuejs中nextTick()异步更新队列源码分析
  • 详解Vue + Vuex 怎么着使用
    vm.$nextTick
  • 浅谈Vue.nextTick
    的兑现情势
  • Vue中之nextTick函数源码剖析详解
  • 有关Vue.nextTick()的准确利用格局分析
  • vue.js全局API之nextTick周详深入分析
  • vue之nextTick周密分析
  • 浓厚领悟Vue nextTick 机制

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有