文章采集调用(闭包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等。

  终于看完了,真的好长,不过我觉得值得

  

  

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线