写文章不容易,点个赞呗兄弟
专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧
研究基于 Vue版本 【2.5.17】
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧vue
【Vue原理】NextTick - 源码版 之 服务Vue 数组
初次看的兄弟能够先看 【Vue原理】NextTick - 白话版 简单了解下NextTick异步
好的,今天,就来详细记录 Vue 和 nextTick 的那些事函数
nextTick 在 Vue 中,最重要的就是~~~学习
协助 Vue 进行更新操做!this
上篇文章 spa
NextTick-源码版之独立自身
提到过,nextTick 帮助 Vue 避免频繁的更新,这里简单提一下,prototype
每次修改数据,都会触发数据的依赖更新3d
也就是说数据被修改的时候,会调用一遍【引用这个数据的实例】的更新函数code
那么,按道理来讲,修改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); } } }
先说说其中涉及的几个变量
是一个对象,用来过滤watcher。
当这个watcher 已经调用过更新函数,那么就在 has 中标记这个 id
也就是,你同时间调用屡次 watcher.update ,其实只有第一次调用有用,后面的都会被过滤掉
一个数组,watcher 更新队列,存放须要更新的 watcher
flushSchedulerQueue
watcher 更新队列执行函数,下面有讲到
为 true 表示已经把 【watcher 更新队列执行函数】 注册到宏微任务上了(或者说存放进 callbacks 中)。
正在等待JS栈为空后,就能够执行更新。直到全部watcher 更新完毕,才重置为 false
为 true 表示 watcher 更新队列正在执行更新(就是开始遍历 watcher 队列,逐个调用 watcher 更新了)
直到全部watcher 更新完毕,才重置为 false
queueWatcher 源码不算很复杂,主要作两件事
一、处理watcher 更新队列 queue
二、注册 【watcher 更新队列 执行函数】进宏微任务
当 flushing 为 false时,表示 queue 尚未开始遍历执行,直接 push
当 flushing 为 true,表示 queue 已经开始遍历,执行其中的 watcher 更新了
而后,作了一个很特殊的插入操做(为了方便看,把上面的源码截取了)
我仍是没有看懂这是为何? 直到我看到了 flushSchedulerQueue 的 源码!
由于在 flushSchedulerQueue 执行的时候(此时设置了 flushing = true),内部把 queue 升序排列了!
因此在 flushing 的时候,queue已是有序状态,中途进来的 watcher,固然也要按顺序来
因此,这一段的做用就是给 新来的 watcher 排序!
其中 index 表示 如今正遍历到第几个 watcher(在 flushSchedulerQueue 中设置)
因此,也必然是排到已经执行过的 watcher 后面的(否则就遍历不到这个watcher 了啊)
已经讲到 flushSchedulerQueue 了,他就是 注册宏微任务的异步回调
直接存放进 异步任务队列 callbacks 中的
关于 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; }
一、升序排列 watcher 更新队列
二、遍历 watcher 更新队列,而后逐个调用 watcher 更新
三、watcher 更新队列执行完毕,重置状态
其余我都看得明白,惟独我不懂一个问题
为何要把 queue 按照 watcher.id 升序排列??
首先,watcher.id 越大,表示这个 watcher 越年轻,实例是越后面生成的
This ensures that:
我只挑一点
为何先更新父组件,再更新子组件,我仍是想不通啊?
我的认为,由于父组件跟子组件是有联系的,什么联系呢?
好比 props
当 父组件传给子组件的数据变化的时候,父组件须要把 变化后的数据 传给 子组件,子组件才能知道数据变了
那么 子组件才能更新组件内使用 props 的地方
因此,父组件必须先更新,把最新数据传给 子组件,子组件再更新,此时才能获取最新的数据
否则你子组件更新了,父组件再传数据过来,那就不会子组件就不会显示最新的数据了啊
至于 父组件更新时怎么传 数据给子组件的?
数据变化,通知 watcher 更新,watcher.update
queueWatcher 把 watcher 添加进 【queue 更新队列】
把 flushSchedulerQueue 注册进宏微任务
JS 主栈执行完,开始执行异步代码
flushSchedulerQueue 遍历 queue ,逐个调用 watcher 更新
完成更新