文章采集调用(闭包dep:来存储依赖引用数据类型的数据依赖收集 )
优采云 发布时间: 2022-02-14 22:02文章采集调用(闭包dep:来存储依赖引用数据类型的数据依赖收集
)
上一篇我们分析了基础数据类型的依赖集合
本文是关于引用数据类型的数据依赖采集和分析,因为引用类型数据比较复杂,必须单独写
文章很长,高能预警,做好准备,耐心等待,一定会有收获
不过这两类数据的处理有很多重复,所以我打算只写一些区别,否则会废话很多。
两个步骤,两者都不一样
1、数据初始化
2、依赖集合
数据初始化过程
如果数据类型是引用类型,则需要对数据进行额外处理。
处理分为对象和数组两种,分别讨论。
1 个对象
1、 遍历对象的各个属性,也设置响应性。假设属性都是基本类型,处理流程和上一个一样
2、每个数据对象都会添加一个ob属性
例如,设置子数据对象
在下图中,可以看到子对象处理完后添加了一个ob属性
ob_ 属性有什么用?
你可以观察到 ob 有一个 dep 属性。这个 dep 是不是有点属性?是的,在上一篇关于基本数据类型的文章中提到过
那么这个ob属性有什么用呢?
你可以观察到 ob 有一个 dep 属性。这个 dep 是不是有点属性?是的,在上一篇关于基本数据类型的文章中提到过
dep 正是存储依赖项的位置
比如page指的是data child,watch指的是data child,那么child会将这两个保存在dep.subs中
dep.subs = [ 页面-watcher,watch-watcher ]
但是,在上一篇关于基本类型的文章中,dep 作为闭包存在,而不是任何 [ob.dep]。
是的,这就是引用类型和原创类型的区别
基本数据类型,仅使用 [closure dep] 存储依赖关系
引用数据类型,使用[closure dep]和[ob.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();
2、数组
1、需要遍历数组,可能数组是对象数组,如下
[{name:1},{name:888}]
遍历的时候,如*敏*感*词*item是一个对象,会和上面解析对象一样操作。
2、保存一个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() { .... }
});
}
绘制流程图仅供参考
哈哈哈,上面的很长,有一点点,但是忍不住了。我想更详细一点。好吧,还有一段,但它有点短。答应我,如果你仔细阅读,请发表评论,让我知道有人仔细阅读过
依赖采集过程
采集过程重点是Object.defineProperty设置的get方法
与基本类型数据相比,引用类型的采集方式只是多了几行处理,区别就在于两行代码
childOb.dep.depend,我将其简化为 childOb.dep.addSub(Dep.target)
依赖数组(值)
可以先看源码,如下
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
}
});
}
以上源码混合了对象和数组的处理,我们分开讲
1、对象
在数据初始化的过程中,我们已经知道如果值是一个对象,在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);
}
}
}
显然,为了防止数组中的对象,需要保存数组的子项对象的副本。
你一定要问,为什么子对象还要保存一个依赖?
1、页面依赖数组,数组的子项发生了变化。页面是否也需要更新?但是子项的内部变化是如何通知页面更新的呢?所以你还需要为子对象保存一个依赖项吗?
2、数组子项数组的变化就是对象属性的增删。必须使用 Vue 封装方法 set 和 del。set和del会通知依赖更新,所以子项对象也要保存
看栗子
[外链图片传输失败(img-PuPHYChy-59)()]
页面模板
看数组的数据,有两个ob
总结
至此,引用类型和基本类型的区别就很清楚了。
1、引用类型会添加一个__ob__属性,里面收录dep,用来存放采集到的依赖
2、对象使用ob.dep,作用于Vue的自定义方法set和del
3、数组使用ob.dep,作用于Vue重写的数组方法push等。
终于看完了,真的好长,不过我觉得值得