专栏首页Vue源码 & 前端进阶体系【Vue原理】NextTick - 源码版 之 服务Vue

【Vue原理】NextTick - 源码版 之 服务Vue

写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧

研究基于 Vue版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧

【Vue原理】NextTick - 源码版 之 服务Vue

初次看的兄弟可以先看 【Vue原理】NextTick - 白话版 简单了解下NextTick

好的,今天,就来详细记录 Vue 和 nextTick 的那些事

公众号

nextTick 在 Vue 中,最重要的就是~~~

协助 Vue 进行更新操作!

上篇文章

NextTick-源码版之独立自身

提到过,nextTick 帮助 Vue 避免频繁的更新,这里简单提一下,

每次修改数据,都会触发数据的依赖更新

也就是说数据被修改的时候,会调用一遍【引用这个数据的实例】的更新函数

那么,按道理来说,修改3次,就应该调用3遍更新函数,但是实际上只会调用一遍

比如我们使用 watch 监听 data(data 便收集了 watch 的 watcher,监听回调就是更新函数)

公众号

结果就是只打印一次

公众号

至于依赖更新,可以看下面的文章

依赖更新 - 源码版

其实,修改数据能够只更新一次,不止是 nextTick 起了作用,Vue 也做了其他处理,比如过滤实例,清空队列等等,下面就来说一下

一切先从【实例更新函数】开始

第一个要说的就是 watcher!每个实例都有一个 watcher,然后 watcher 保存着实例的更新函数

每个实例都会通过 new Vue 生成的,所以会有一个专属的 watcher

更新函数被保存在 watcher.getter 上

function Vue(){
    ....
    new Watcher(vm, 实例更新函数)
}



function Watcher(vm, expOrFn) {    

    this.getter = expOrFn;

};



Watcher.prototype.get = function() {  

    this.getter.call(vm, vm);

};

Watcher.prototype.update = function() {
    queueWatcher(this);
};
Watcher.prototype.run = function() {  

    this.get();

};

我们知道, Vue 的 data 是响应式的,就是通过 Object.defineProperty 设置 get 和 set

当数据被修改的时候, set 函数被触发,函数内部会通知所有的实例进行更新(就是调用每个实例的 watcher.update 方法)

具体可以看这个

响应式原理 - 白话版

依赖更新 - 源码版

那么我们现在的重点就在 watcher.update 上了,看看上面的 Watcher 代码

出现了一个 queueWatcher 的东西

公众号

更新队列

速度看源码!

var queue = [];

var has = {};

var index = 0;

var flushing = false;

var waiting= false;



function queueWatcher(watcher) {  



    var id = watcher.id;    



    // 如果是同一个 Vue 实例,就不要重复添加了

    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.
            var i = queue.length - 1;            



            // 跳过所有比我大的

            while (i > index && queue[i].id > watcher.id) {
                i--;
            }  

         

            // 最后放在队列中,一个比我老的 watcher 后面

            queue.splice(i + 1, 0, watcher);
        }        



        // 在 flushSchedulerQueue 执行之后设置为 false

        if (!waiting) {
            waiting = true;
            nextTick(flushSchedulerQueue);
        }
    }
}

先说说其中涉及的几个变量

公众号

has

是一个对象,用来过滤watcher。

当这个watcher 已经调用过更新函数,那么就在 has 中标记这个 id

也就是,你同时间调用多次 watcher.update ,其实只有第一次调用有用,后面的都会被过滤掉

queue

一个数组,watcher 更新队列,存放需要更新的 watcher

flushSchedulerQueue

watcher 更新队列执行函数,下面有讲到

waiting

为 true 表示已经把 【watcher 更新队列执行函数】 注册到宏微任务上了(或者说存放进 callbacks 中)。

正在等待JS栈为空后,就可以执行更新。直到所有watcher 更新完毕,才重置为 false

flushing

为 true 表示 watcher 更新队列正在执行更新(就是开始遍历 watcher 队列,逐个调用 watcher 更新了)

直到所有watcher 更新完毕,才重置为 false

queueWatcher 源码不算很复杂,主要做两件事

1、处理watcher 更新队列 queue

2、注册 【watcher 更新队列 执行函数】进宏微任务

处理 watcher 更新队列 queue

当 flushing 为 false时,表示 queue 还没有开始遍历执行,直接 push

当 flushing 为 true,表示 queue 已经开始遍历,执行其中的 watcher 更新了

然后,做了一个很特殊的插入操作(为了方便看,把上面的源码截取了)

公众号

我还是没有看懂这是为什么? 直到我看到了 flushSchedulerQueue 的 源码!

因为在 flushSchedulerQueue 执行的时候(此时设置了 flushing = true),内部把 queue 升序排列了!

所以在 flushing 的时候,queue已经是有序状态,中途进来的 watcher,当然也要按顺序来

所以,这一段的作用就是给 新来的 watcher 排序

其中 index 表示 现在正遍历到第几个 watcher(在 flushSchedulerQueue 中设置)

所以,也必然是排到已经执行过的 watcher 后面的(不然就遍历不到这个watcher 了啊)

注册 【watcher 更新队列 执行函数】进宏微任务

已经讲到 flushSchedulerQueue 了,他就是 注册宏微任务的异步回调

公众号

直接存放进 异步任务队列 callbacks 中的

关于 nextTick 的 异步任务队列 ,可以看

NextTick - 源码版 之 独立自身

接下来,就看 flushSchedulerQueue


执行更新队列

function flushSchedulerQueue() {



    flushing = true;    

    var watcher;    

    

    // 升序排列

    queue.sort(function(a, b) {        

        return a.id - b.id;

    });    

    

    for (index = 0; index < queue.length; index++) {

        watcher = queue[index];
        has[watcher.id] = null;
        watcher.run();
    }    



    // 所有watcher 完成更新,重置状态

    queue.length = 0;
    has = {};
    waiting = flushing = false;

}

flushSchedulerQueue 的作用

1、升序排列 watcher 更新队列

2、遍历 watcher 更新队列,然后逐个调用 watcher 更新

3、watcher 更新队列执行完毕,重置状态

其他我都看得明白,唯独我不懂一个问题

为什么要把 queue 按照 watcher.id 升序排列??

首先,watcher.id 越大,表示这个 watcher 越年轻,实例是越后面生成的

vue 的官方回答

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.

我只挑一点

先更新父组件,再更新子组件(因为父组件比子组件先创建)

为什么先更新父组件,再更新子组件,我还是想不通啊?

个人认为,因为父组件跟子组件是有联系的,什么联系呢?

比如 props

当 父组件传给子组件的数据变化的时候,父组件需要把 变化后的数据 传给 子组件,子组件才能知道数据变了

那么 子组件才能更新组件内使用 props 的地方

所以,父组件必须先更新,把最新数据传给 子组件,子组件再更新,此时才能获取最新的数据

不然你子组件更新了,父组件再传数据过来,那就不会子组件就不会显示最新的数据了啊

至于 父组件更新时怎么传 数据给子组件的?

【Vue原理】Props - 白话版

最后,走个简单流程

数据变化,通知 watcher 更新,watcher.update

queueWatcher 把 watcher 添加进 【queue 更新队列】

把 flushSchedulerQueue 注册进宏微任务

JS 主栈执行完,开始执行异步代码

flushSchedulerQueue 遍历 queue ,逐个调用 watcher 更新

完成更新

公众号
公众号

本文分享自微信公众号 - 神仙朱(skying-zhu)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【JS】218-JavaScript简史:从网景到前端框架三巨头

    从上世纪 90 年代中期发行以来,JavaScript 就已经成为最流行的 Web 开发语言之一了。尽管 JavaScript 最初只是一个浏览器脚本语言,但是...

    pingan8787
  • 【Vuejs】242-7个有用的Vue开发技巧

    随着组件的细化,就会遇到多组件状态共享的情况, Vuex当然可以解决这类问题,不过就像 Vuex官方文档所说的,如果应用不够大,为避免代码繁琐冗余,最好不要使用...

    pingan8787
  • 基于SpringCloud的RBAC权限管理系统

    基于 Spring Cloud Greenwich.SR2 、Spring Security OAuth2 的RBAC权限管理系统;基于数据驱动视图的理念封装 ...

    程序源代码
  • .net Core中Vue.js里使用Element-UI

    每次自己尝试用新东西时,所花的时间真的不是一般的多,以前刚开始学Vue的时候用VSCode直接引入ElementUI比较简单,这次项目因为用.net Core生...

    Vaccae
  • VueJs里定义类去引用实现赋值加页面刷新

    最近项目中要做前端的东西,前阵子简单学习了一点Vue,所以这次项目中直接用Vue来进行编码,因为这个项目的目的主要就是练习加提高自己,所以就边学边做。

    Vaccae
  • 【Vuejs】212- 如何优雅的在 vue 中添加权限控制

    本文转载自掘金专栏,作者:邪瓶张起灵https://juejin.im/post/5c7bae3ff265da2db27950f3

    pingan8787
  • 【干货】Vue TypeScript根据类生成签名字符串

    今天我们来说一下VueJs里根据类生成签名,标题里面说了TypeScript是因为语法相对于JavaScript严格的多,导致里面一个小问题又花费了我一两天的时...

    Vaccae
  • Vue + Flask 小知识(三)

    我们做一个简单的页面,两个输入框用来输入搜索条件,一个表格用来展示数据,先看下页面效果

    周萝卜
  • 实习一周的总结

    实习一周的感觉,就是,因为我大学学的不是计算机专业的,所以没有多大经验,包括自己自学的话,是有很多局限性的,因为不知道外面企业的项目是怎么写的,而且我自己在自学...

    公众号---志学Python
  • .net Core创建vuejs应用

    在cmd中输入dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

    Vaccae

扫码关注云+社区

领取腾讯云代金券