最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue原理 [组件为何采用异步渲染-nextTick的实现原理]

    正文概述 掘金(半路杀出个程咬金)   2020-11-28   460

    前言

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

    其实在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 方法是异步的 ,那么它又是如何实现的异步更新呢 ?来看张图 Vue原理 [组件为何采用异步渲染-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() 也是如此 .


    起源地下载网 » Vue原理 [组件为何采用异步渲染-nextTick的实现原理]

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元