写文章不容易,点个赞呗兄弟
专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧
研究基于 Vue版本 【2.5.17】
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧数组
若是对依赖收集彻底没有概念的同窗,能够先看我这篇函数
依赖收集,主要是为了解决一个问题,什么问题呢?this
首先,咱们都知道,Vue 的数据是响应式更新的,一旦数据改变了,那么相应使用到 数据的地方也会跟着改变。spa
那么问题来了,数据改变的时候,Vue 怎么知道,去让那些使用到数据的地方也改变呢?prototype
这就是依赖收集要解决的问题!code
他是怎么解决的?简单说就是把依赖了数据的地方,给集中收集起来以便变化后通知!咱们今天来看源码的流程server
首先,响应式更新,分为两步,依赖收集和依赖更新对象
今天讲的是依赖收集,如何去收集 使用了数据的地方
依赖收集,又分为两个流程
一、数据初始化流程
二、依赖收集流程
当前篇,先以基本数据类型为例讲解,由于 基本数据和 引用数据 在处理上有很大的不一样,引用类型须要理解的东西更多更复杂,因此须要按部就班,分两篇描述
首先,在实例初始化的时候,须要对数据进行响应式处理,也就是给每一个属性都使用 Object.defineProperty 处理
处理的流程是怎么样的呢?
一、实例初始化中,调用 initState 处理部分选项数据,initData 用于处理选项 data
Vue.prototype._init=function(){ ... initState(this) ... } function initState(vm) { var opts = vm.$options; ... props,computed,watch 等选项处理 if (opts.data) { initData(vm); } };
二、initData 遍历 data,definedReactive 处理每一个属性
function initData(vm) { var data = vm.$options.data; data = typeof data === 'function' ? data.call(vm, vm) : data || {}; // ... 遍历 data 数据对象的key,重名检测,合规检测等代码 new Observer(data); } function Observer(value) { var keys = Object.keys(value); // ...被省略的代码 for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } };
三、definedReactive 给对象的属性 经过 Object.defineProperty 设置响应式
function defineReactive(obj, key) { // dep 用于中收集全部 依赖个人 东西 var dep = new Dep(); var val = obj[key] Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { ...依赖收集,详细源码下个流程放出 }, set() { ....依赖更新,源码下篇文章放出 } }); }
写一个小例子来解析
new Vue({ el: document.getElementsByTagName("div")[0], data(){ return { name:11 } } })
页面模板
页面引用了数据 name,name 须要保存 页面的watcher,以便于 name 变化时,通知 页面watcher 更新
一、页面渲染函数
with(this){ return _c('div',{},[name]) }
二、读取 name
渲染函数执行,上下文对象绑定为 实例,因而name读取到实例上的 name
三、保存 watcher
name 被读取,天然走到 Object.defineProperty.get 方法上,从这里开始收集 watcher
先来观察下 defineReactive 中省略的 get 的源码
function defineReactive(obj, key) { var dep = new Dep(); var val = obj[key] Object.defineProperty(obj, key, { get() { if (Dep.target) { // 收集依赖 dep.addSub(Dep.target) } return val } }); }
哈哈哈,这里就有意思了,这段代码就是 依赖收集的核心,主要是三个点
Dep.target
Dep
dep.addSub
一、Dep.target
Dep.target 指向的是各类 watcher,watch的watcher,页面的watcher 等等
Dep.target 是变化的,根据当前解析流程,不停地指向不一样的 watcher (指向,其实就是直接赋值 ,以下)
Dep.target = 具体watcher
“固然没有这么简单,就是表示一个意思而已”
简单想,指向哪一个watcher,那么就是那个 watcher 正在使用数据,数据就要收集这个watcher
你能够先不用管 Dep.target 究竟是怎么指向,你只用记住 在 watcher
好比当前页面开始渲染时,Dep.target 会提早指向当前页面的 watcher。
因而页面渲染函数执行,并引用了数据 name 后,name 直接收集 Dep.target,就会收集到当前页面的 watcher
watcher 有负责实例更新的功能,因此会被收集起来,数据变化时通知 watcher,就能够调用 watcher 去更新了
watcher 在 依赖收集中只起到被收集的做用,因此不会在这里详细解释
二、Dep
Dep 是一个构造函数,用于建立实例,并带有不少方法
实例会包含一个属性 subs 数组,用于存储不一样数据 【收集的依赖】
看下dep的构造函数
var Dep = function Dep() { // 保存watcher 的数组 this.subs = []; };
三、dep.addSub
原型上的方法,做用是往 dep.subs 存储器中 中直接添加 watcher
Dep.prototype.addSub = function(sub) { this.subs.push(sub); };
因此,【dep.addSub(Dep.target) 】就会直接添加当前 watcher
因而,收集流程大概是这样
一、页面的渲染函数执行, name 被读取
二、触发 name的 Object.defineProperty.get 方法
三、因而,页面的 watcher 就会被收集到 name 专属的闭包dep 的 subs 中
为何须要依赖收集,以前也已经说过,是为了变化时通知 那些使用过数据的地方
就比如,你去商店买东西,东西尚未发售,因而你把你的电话给老板,老板把你的记在电话本上。当东西发售时,就会打你的电话通知你,让你来领取(完成更新)。
其中涉及的几个步骤,按上面的例子来转化一下
一、你买东西,就是你要使用数据 name
二、你把电话给老板,电话就是你的 watcher,用于通知
三、老板记下电话在电话本,就是把 watcher 保存在 subs 中。
四、剩下的步骤属于依赖更新