Vue原理 【 组件为何采用异步渲染 - nextTick的实现原理 】

前言

有人说:是为了提高性能,对,根本上也是这么个道理 ;那到底是如何做的呢 ?

其实在vue中,响应式数据是组件级的,也就是说,每一次的更新都是渲染整个组件,如果是同步的话,根据我们前边理解的响应式数据原理,一旦修改了data属性,便会触发对应的 watcher,然后调用对应 watcher 下的 update 方法更新视图,那么结果显而易见,太频繁了 !假设:如下代码

// 省略多余模板语法 
data () {
	a:1,
	b:2,
	c:3
}
//如果我们按照同步的逻辑,修改data属性,this.a = 10; this.b = 20; this.c = 30; 
//就会调用三次update渲染视图,岂不是很耗性能 ?而且体验也不好。

所以 vue 采用的是异步渲染 接下来,我们来了解一下,前边也有讲过响应式数据原理,不了解的童鞋可以回过头去看看 数据响应式 Go,这里我就接着数据更新方法update开始;

queueWatcher

src/croe/observer/watcher.js 166 行 ,这里的更新先不考虑计算属性和同步,我们顺着 queueWatcher 往下走,

update () {
    /* istanbul ignore else */
    if (this.lazy) { // 计算属性  依赖的数据发生变化了 会让计算属性的watcher的dirty变成true
      this.dirty = true
    } else if (this.sync) { // 同步 watcher
      this.run()
    } else {
      // 将要更新的 watcher 放入队列
      // 它们依赖同一个 dep 收集器 ,不了解的可以查看上边 数据响应式 链接
      queueWatcher(this)  
    }
}
queueWatcher 逻辑解读

src/core/observer.scheduler.js 164行 ,主要就是实现一个 watcher 队列 ,每一次的 update 都放入到队列中,然后进行统一异步处理 。 看代码:

export function queueWatcher (watcher: Watcher) {
  // 过滤 watcher 
  const id = watcher.id  
  if (has[id] == null) {
    has[id] = true 
    if (!flushing) {
      // 将watcher放到队列中
      queue.push(watcher)
    } else { 
      // 通过对 id 的判断,这里的 id 是自加1,可查看 watcher.js 源码,
      // 如果已经刷新了,则赋值当前的id , 如果id超过了,将运行如下代码
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // 如果不等了,则进行刷新 
    if (!waiting) { 
      waiting = true
      if (process.env.NODE_ENV !== 'production' && !config.async) {
      	// 该方法做了刷新前的 beforUpdate 方法调用,然后 watcher.run() 
        flushSchedulerQueue() 
        return
      }
      // 在下一次tick中刷新 watcher 队列 (借用nextTick)
      //(包含同一watcher的多个data属性),
      // 这里的nextTick 就是我们的常用 api => this.$nextTick()
      nextTick(flushSchedulerQueue)  
    }
  }
}

好了,通过源码简单的分析,明白为啥 vue 为啥采用异步更新了吧,原因很简单,因为vue是组件级更新视图,每一次update都要渲染整个组件,为了提高性能,采用了队列的形式,存储同一个watcher的所有data属性变化,然后统一调用nextTick 方法进行更新渲染(有且只调用一次)。

nextTick 原理

问题来了,nextTick 方法是异步的 ,那么它又是如何实现的异步更新呢 ?来看张图
在这里插入图片描述

从图来看,调用了 nextTick 之后,将watcher队列回调函数暂时存入了一个数组callbacks 中,然后才依次调用 timeFun()方法执行,而真正让watcher异步的关键就在这儿,我们通过代码来看一下:

src/core/util/next-tick.js 87 行

export function nextTick (cb?: Function, ctx?: Object) { 
// flushSchedulerQueue 会使用 nextTick 保证当前视图渲染完成
  let _resolve
  callbacks.push(() => {  // 暂存 watcher 队列
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {  // 状态改变后,调用 timerFun() 方法
    pending = true
    timerFunc()  // 重点,重点,重点! 我们进去看一下
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
timerFunc 异步关键

timerFunc 方法 src/core/util/next-tick.js 33 行

很清晰,它对当前的环境进行了判断,如果支持promise 就用 promise 依次往下: MutationObserver , setImmediate , setTimeout 这四个分别都是异步解决方案,除了 setTimeout 是宏观任务以外,其它三个都是微观任务;

let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks) // then 里边执行 flushCallbacks 
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  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)   // setImmediate 回调里边
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)  // setTimeout 回调里边
  }
}
总结

nextTick 方法主要是使用了宏任务和微任务,定义了一个异步方法.多次调用 nextTick 会将方法存入
队列中,通过这个异步方法清空当前队列。 所以这个 nextTick 方法就是异步方法 。

而我们平常使用的api :vue.nextTick() 也是如此 .

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值