写文章不容易,点个赞呗兄弟 专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】数组
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧闭包
上一篇,咱们已经分析过了 基础数据类型的 依赖收集学习
这一篇内容是针对 引用数据类型的数据的 依赖收集分析,由于引用类型数据要复杂些,必须分开写prototype
文章很长,高能预警,作好准备耐下心好,确定仍是有点收获的code
可是两个类型的数据的处理,又有不少重复的地方,因此打算只写一些差别性的地方就行了,不然显得废话不少server
两个步骤,都有不一样的地方对象
一、数据初始化blog
二、依赖收集
若是数据类型是引用类型,须要对数据进行额外的处理。
处理又分了 对象 和 数组 两种,会分开来说
1对象
一、遍历对象的每一个属性,一样设置响应式,假设属性都是基本类型,处理流程跟上一篇同样
二、每一个数据对象会增长一个 ob 属性
好比设置一个 child 的数据对象
下图,你能够看到 child 对象处理以后添加了一个 ob 属性
ob_ 属性有什么用啊?
你能够观察到,ob 有一个 dep 属性,这个 dep 是否是有点属性,是的,在上一篇基础数据类型中讲过
那么这个 ob 属性有什么用啊?
你能够观察到,ob 有一个 dep 属性,这个 dep 是否是有点属性,是的,在上一篇基础数据类型中讲过
dep 正是存储依赖的地方
好比 页面引用了 数据child,watch 引用了数据child,那么child 就会把这个两个保存在 dep.subs 中
dep.subs = [ 页面-watcher,watch-watcher ]
可是,在上一篇基础类型种, dep 是做为闭包存在的啊,并非保存在什么【ob.dep】 中啊
没错,这就是 引用类型 和 基础类型的区别了
基础数据类型,只使用 【闭包dep】 来存储依赖
引用数据类型,使用 【闭包dep】 和 【 ob.dep】 两种来存储依赖
什么?你说闭包dep 在哪里?好吧,在 defineReactive 的源码中,你去看看这个方法的源码,下面有
那么,为何,引用类型须要 使用__ob__.dep 存储依赖呢?
首先,明确一点,存储依赖,是为了数据变化时通知依赖,因此 ob.dep 也是为了变化后的通知
闭包 dep 只存在 defineReactive 中,其余地方没法使用到,因此须要保存另一个在其余地方使用
在其余什么地方会使用呢?
在Vue挂载原型上的方法 set 和 del 中,源码以下
function set(target, key, val) { var ob = (target).__ob__; // 通知依赖更新 ob.dep.notify(); } Vue.prototype.$set = set;
function del(target, key) { var ob = (target).__ob__; delete target[key]; if (!ob) return // 通知依赖更新 ob.dep.notify(); } Vue.prototype.$delete = del;
这两个方法,你们应该都用过,为了给对象动态 添加属性和 删除属性
可是若是直接添加属性或者删除属性,Vue 是监听不到的,好比下面这样
child.xxxx=1 delete child.xxxx
因此必需要经过 Vue 包装过的方法 set 和 del 来操做
在 set 和 del 执行完,是须要通知依赖更新的,可是我怎么通知?
此时,【ob.dep】 就发挥做用了!就由于依赖多收集了一份在 ob.dep 中
使用就是上面一句话,通知更新
ob.dep.notify();
二、数组
一、须要遍历数组,可能数组是对象数组,以下面
[{name:1},{name:888}]
遍历时,若是遇到子项是对象的,会跟上面解析对象同样操做
二、给数组保存一个 ob 属性
好比设置一个 arr 数组
看到 arr数组 加多了一个 ob 属性
其实这个 ob 属性 和 上一段讲对象 的做用是差很少的,这里也只是说 ob.dep
数组中的 ob.dep 存储的也是依赖,给谁用呢?
给 Vue 封装的数组方法使用,要知道要想数组变化也被监听到,是必须使用Vue封装的数组方法的,不然没法实时更新
这里举重写方法之一 push,其余的还有 splice 等,Vue 官方文档已经有过说明
var original = Array.prototype.push; Array.prototype.push = function() { var args = [], len = arguments.length; // 复制 传给 push 等方法的参数 while (len--) args[len] = arguments[len]; // 执行 原方法 var result = original.apply(this, args); var ob = this.__ob__; // notify change ob.dep.notify(); return resul }
看到在执行完 数组方法以后,一样须要通知依赖更新,也就是通知 ob.dep 中收集的依赖去更新
如今,咱们知道了,响应式数据对 引用类型作了什么额外的处理,主要是加了一个 ob 属性
咱们已经知道了 ob 有什么用,如今看看源码是怎么添加 ob 的
// 初始化Vue组件的数据 function initData(vm) { var data = vm.$options.data; data = vm._data = typeof data === 'function' ? data.call(vm, vm) : data || {}; ....遍历 data 数据对象的key ,重名检测,合规检测 observe(data, true); } function observe(value) { if (Array.isArray(value) || typeof value == "object") { ob = new Observer(value); } return ob }
function Observer(value) { // 给对象生成依赖保存器 this.dep = new Dep(); // 给 每个对象 添加一个 __ob__ 属性,值为 Observer 实例 value.__ob__ = this if (Array.isArray(value)) { // 遍历数组,每一项都须要经过 observe 处理,若是是对象就添加 __ob__ for (var i = 0, l =value.length; i < l; i++) { observe(value[i]); } } else { var keys = Object.keys(value); // 给对象的每个属性设置响应式 for (var i = 0; i < keys.length; i++) { defineReactive(value, keys[i]); } } };
源码的流程跟上一篇差很少,只是处理引用数据类型会增长多几行源码的额外处理
咱们以前只说了一种对象数据类型,好比下面这样
若是会嵌套多层对象呢?好比这样,会怎么处理
没错,Vue 会递归处理,当遍历属性,使用 defineReactive 处理时,递归调用 observe 处理(源码标红加粗)
若是值是对象,那么一样给 值加多一个 ob
若是不是,那么正常往下走,设置响应式
源码以下
function defineReactive(obj, key, value) { // dep 用于中收集全部 依赖个人 东西 var dep = new Dep(); var val = obj[key] // 返回的 childOb 是一个 Observer 实例 // 若是值是一个对象,须要递归遍历对象 var childOb = observe(val); Object.defineProperty(obj, key, { get() {...依赖收集跟初始化无关,下面会讲}, set() { .... } }); }
画一个流程图,仅供参考
哈哈哈,上面写得好长啊,是有点,可是没办法,想说详细点啊,好吧,还有一段,可是比较短一些哈哈哈,反正看完的人,我jio 得很厉害了,答应我,若是你仔细看完了,评论一下好吗,让我知道有人仔细看了
收集流程,就是重点关注 Object.defineProperty 设置的 get 方法了
跟 基础类型数据 对比,引用类型的 收集方法也只是多了几行处理,差别在两行代码
childOb.dep.depend,被我 简单化为 childOb.dep.addSub(Dep.target) dependArray(value) 能够先看下源码,以下
function defineReactive(obj, key, value) { var dep = new Dep(); var val = obj[key] var childOb = observe(val); Object.defineProperty(obj, key, { get() { var value = val if (Dep.target) { // 收集依赖进 dep.subs dep.addSub(Dep.target); // 若是值是一个对象,Observer 实例的 dep 也收集一遍依赖 if (childOb) { childOb.dep.addSub(Dep.target) if (Array.isArray(value)) { dependArray(value); } } } return value } }); }
上面的源码,混杂了 对象和 数组的处理,咱们分开说
一、对象
在数据初始化的流程中,咱们已经知道值是对象的话,会存储多一份依赖在 ob.dep 中
就只有一句话
childOb.dep.depend();
数组还有另一个处理,就是
dependArray(value);
看下源码,以下
function dependArray(value) { for (var i = 0, l = value.length; i < l; i++) { var e = value[i]; // 只有子项是对象的时候,收集依赖进 dep.subs e && e.__ob__ && e.__ob__.dep.addSub(Dep.target); // 若是子项仍是 数组,那就继续递归遍历 if (Array.isArray(e)) { dependArray(e); } } }
显然,是为了防止数组里面有对象,从而须要给 数组子项对象也保存一份
你确定会问,为何子项对象也要保存一份依赖?
一、页面依赖了数组,数组子项变化了,是否是页面也须要更新?可是子项内部变化怎么通知页面更新?因此须要给子项对象也保存一份依赖?
二、数组子项数组变化,就是对象增删属性,必须用到Vue封装方法 set 和 del,set 和 del 会通知依赖更新,因此子项对象也要保存
看个栗子
页面模板
看到数组的数据,就存在两个 ob
到这里,就能够很清楚,引用类型和 基础类型的处理差别了
一、引用类型会多添加一个 __ob__属性,其中包含 dep,用于存储 收集到的依赖
二、对象使用 ob.dep,做用在 Vue 自定义的方法 set 和 del 中
三、数组使用 ob.dep,做用在 Vue 重写的数组方法 push 等中
终于写完了,真的好长,可是我以为值得了