美文网首页
vue 数据驱动视图渲染原理及$nextTick

vue 数据驱动视图渲染原理及$nextTick

作者: 臣以君纲 | 来源:发表于2019-02-17 00:41 被阅读0次

上一节vue 响应式原理解析我们提到当数据发生变化时,会触发所有组件Watcher的update方法,下面我们进入源码分析

  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

这是在Watcher类中定义的update方法,数据修改时,执行queueWatcher函数,我们进入queueWatcher,

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      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 > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

可以看到,函数开头会对当前组件Watcher进行判断,当我们在写vue代码时我们可能会有疑问,如果我们连续改变data中的某个值两次,或者我们连续改变几个data中的值,组件会重新渲染几次呢,在这里我们可以看到,只会渲染一次吗,因为之后的此组件重新渲染会在这里被阻断掉。
进行重新渲染时会把所有的组件Watcher和用户定义的Watcher都添加到queue队列中,最后执行nextTick(flushSchedulerQueue),通过nextTick我们可以想到常用的$nextTick函数就是这个函数,我们进入nextTick函数

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)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

重点是tmierFunc的执行,我们来看timerFunc的定义

let timerFunc

// 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 next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // 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
  // (#6466 MutationObserver is unreliable in IE11)
  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)
  }
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Techinically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

可以看到这时一个执行顺序的问题,我们知道在浏览器js运行环境中有microtask和macrotask,两种方式都是在执行完js环境中代码后最后运行,mic在mac前面,这里就是把nextTick执行的代码放入到这两个其中一个环境中,具体哪个和当前使用的浏览器有关,总之是所有其他同步代码执行完毕后执行,最后看赋值给timerFunc的flushCallbacks

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

将多次调用nextTick中传入callbacks数组的函数循环执行,执行的也就是queueWatcher中的flushSchedulerQueue
我们进入flushSchedulerQueue的定义

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.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    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
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

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

对update中的Watcher队列进行排序,排序原因注释也写得很清楚,执行组件Watcher的beforeUpdate钩子,执行Watcher.run方法,
run方法中执行Wathcer的get方法,get方法执行getter函数,又回到了之前vm._update(vm._render(), hydrating) 这里就会触发一系列的render函数,patch,createElem等等完成组件的重新渲染,

通过这两节的介绍我们可以清楚的看到vue 响应式原理

相关文章

  • vue 数据驱动视图渲染原理及$nextTick

    上一节vue 响应式原理解析我们提到当数据发生变化时,会触发所有组件Watcher的update方法,下面我们进入...

  • vue 响应式原理解析,

    vue 的数据驱动渲染逻辑大家已经清楚,下面我们来研究下vue 的数据改变驱动视图重新渲染原理 记得第一节在讲vu...

  • 深入理解之Vue nextTick

    一.定义【nextTick, 事件循环】 nextTick的由来由于vue是数据驱动视图更新,是异步的,即修改数据...

  • vue原理

    Vue原理 未经允许 禁止转载 MVVM 数据驱动视图 传统组件只是静态渲染,更新还要依赖于操作DOM vue M...

  • vue原理

    组件化 数据驱动视图传统组件,只是静态的渲染,更行还是依赖于操作DOM数据驱动视图--Vue(MVVM)数据驱动视...

  • vue原理与开发逻辑

    1、vue中的$nextTick()的用法和原理 vue的DOM更新是异步的,当数据更新了,再dom中渲染后,自动...

  • Vue MVVM 原理实现

    核心原理 MVVM 双向数据绑定, 数据驱动视图 Vue 实现 MVVM 采用 数据劫持 + 发布订阅模式 : ...

  • nextTick和异步更新视图

    加深vue渲染页面原理记录 1. 栗子 先来了解一个例子关于vue中数据和DOM更新机制以及vm.nextTick...

  • vue简介

    1.vue特性 vue框架有两个特性 数据驱动视图 双向数据绑定 1.1 数据驱动视图 在使用vue的页面中,vu...

  • vue学习笔记

    vue对比jquery vue:mvvm 数据驱动影响视图 适用于复杂数据jquery:mvc 视图塞入数据 ...

网友评论

      本文标题:vue 数据驱动视图渲染原理及$nextTick

      本文链接:https://www.haomeiwen.com/subject/ddrweqtx.html