收集依赖在整个数据响应式中算是比较难的
首先 要理解这里所指的依赖
依赖 可能vue项目做多了就会想到 npm i 但其实跟这个是没有什么关系的
我们这里所指的依赖 是用到数据的地方
什么地方用到数据 什么地方就是依赖 简单说 就是依赖这个响应式数据
首先 我们看一下 vue1 和 vue2的区别
vue1的话 是 细粒度的依赖 他是 你在dom中 {{ 响应式数据 }} 这样去使用了 他就叫依赖
vue2的话 做了一定的优化 他 中等粒度的依赖 就是 看组件有没有用
而有一句非常 哲学 的话 在get中收集依赖 在set中触发依赖
简单说 用到这个数据的地方 就会触发get 谁触发get 谁就是依赖
而在set中 触发依赖 去更新依赖的数据
然后呢 关于实现原理 我这里就不直接讲了 开始我看到那个人都麻了 非常的蒙
我打算用我慢慢理解这个东西的顺序讲述出来 帮助大家更好的理解
先打开我们一直在写的案例 然后 在src里面创建一个
Dep.js
先创建一个类的结构
export default class Dep {
constructor(value) {
}
};
然后 再在src下创建一个 Watcher.js
也是写一个类结构
export default class Watcher {
constructor(value) {
}
}
然后 我们找到dataResp.js
现在最上面引入一下Dep 这个类
import Dep from './Dep.js';
然后在Observer类中这样改一下
class Observer{
constructor(value) {
this.dep = new Dep();
//相当于 给拿到的对象 其中的__ob__绑定 值为thsi,在类中用this 表示取实例本身给__ob__赋值 最后一个enumerable为false 表示属性不参与for遍历
def(value,'__ob__',this,false);
if(Array.isArray(value)){
Object.setPrototypeOf(value, arrayMethods);
this.observeArray(value);
}else{
this.walk(value);
}
}
walk(value) {
for(let key in value){
defineReactive(value,key);
}
}
observeArray(arr) {
for(let i = 0;i < arr.length;i++) {
observe(arr[i]);
}
}
}
就是 先简答通过new 存了一下 Dep类对象 这里就涉及一个概念
每一个Observer的实例中都有一个Dep实例
而 这里 我们知道 每一个响应式的对象中 都会有一个 ob 而他存的就是 他走到Observer中new的实例对象
除此之外 还有 两个地方需要dep 还在 dataResp 文件下 我们的defineReactive修改函数体如下
const defineReactive = function(data,key,val) {
const dep = new Dep();
if(arguments.length == 2){
val = data[key];
}
let subset = observe(val);
Object.defineProperty(data,key,{
enumerable: true,
configurable: true,
get() {
console.log(`您正在获取${key}的值`);
return val
},
set(value) {
console.log(`您正在修改${key}的值,更改后的值为${value}`);
if(value == val) {
return
}
val = value;
subset = observe(value);
dep.notify();
}
});
}
我们这里 new得到一个Dep类对象 然后 在set时调用类中的notify
还有就是 既然响应式数据需要监听 我们的数组自然也需要
找到Arrays.js 改写代码如下
import { def } from './def.js';
const arrayPrototype = Array.prototype;
export const arrayMethods = Object.create(arrayPrototype);
const redefineArrayMethod = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
redefineArrayMethod.forEach(item =>{
const backupFunction = arrayPrototype[item];
def(arrayMethods,item,function(){
const result = backupFunction.apply(this, arguments);
const ob = this.__ob__;
const args = [...arguments];
let inserted = [];
switch (item) {
case "push":
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
break;
}
if(inserted){
ob.observeArray(inserted);
}
console.log('数组执行了',item,'操作,值被修改为',this);
ob.dep.notify();
return result;
},false);
})
因为我们对象上面是有 ob 这个属性的 我们直接通过const ob = this.ob;获取到 这就是个Observer对象 然后我们直接通过它去用dep下面的notify就好了
那么 首先 Dep 我们现在还是一个空的类 你这样直接调他里面的notify肯定报错 我们来写一下Dep.js
var uid = 0;
export default class Dep {
constructor(value) {
this.subs = [];
this.id = uid++;
}
addSub(sub) {
this.subs.push(sub);
}
depend(){
if (Dep.target) {
this.addSub(Dep.target);
}
}
notify() {
const subs = this.subs.slice();
for (let i = 0;i < subs.length; i++) {
subs[i].update();
}
}
};
首先 这个uid的作用在于每个id用于区分 第一个自然是 1 然后没实例一个 就会加一
然后 subs 则是用来存放订阅的数据的
然后 相对 我们的depend 也比较需要理解 这一块 先判断 拿不拿到当前的目标 Dep.target 这就是收集依赖的方法 谁触发了 get谁就是依赖 我们就需要通过Dep.target拿到当前这个目标节点 然后 通过addSub将这个依赖加到订阅数组里
那么 get收集依赖 我们自然就要改一下dataResp.js下defineReactive下get的代码了
const defineReactive = function(data,key,val) {
const dep = new Dep();
if(arguments.length == 2){
val = data[key];
}
let subset = observe(val);
Object.defineProperty(data,key,{
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
Dep.depend();
if(subset) {
subset.dep.depend();
}
}
console.log(`您正在获取${key}的值`);
return val
},
set(value) {
console.log(`您正在修改${key}的值,更改后的值为${value}`);
if(value == val) {
return
}
val = value;
subset = observe(value);
dep.notify();
}
});
}
其实也就是在get中 判断当前有没有Dep.target这个目标节点 如果有就调一下depend存一下当前依赖订阅
然后判断 子集subset有没有 有的话 就也一起调用了
然后 我们写一下Watcher.js
import Dep from './Dep.js';
var uid = 0;
export default class Watcher {
constructor(target, expression, callback) {
this.id = uid++;
this.target = target;
this.getter = parsePath(expression);
this.callback = callback;
this.value = this.get();
}
update() {
this.run();
}
get() {
var value;
Dep.target = this;
const obj = this.target;
try {
value = this.getter(obj);
} finally {
Dep.target = null;
}
return value;
}
run() {
this.getAndInvoke(this.callback);
}
getAndInvoke(cb) {
const value = this.get();
if (value !== this.value || typeof value == "object") {
const oldValue = this.value;
this.value = value;
cb.cal1(this.target, value, oldValue);
}
}
}
function parsePath(str) {
var segments = str.split( ' .' );
return (obj) => {
for(let i = 0 ; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]]
}
return obj;
}
}
最后 我们来实例化监听一个依赖实例收集
来到src下的 output.js 编写代码如下
import { observe } from "./dataResp"
import Watcher from "./Watcher"
const output = () => {
var obj = {
data: {
data: {
map: {
dom: {
isgin: true
}
},
arg: 13
},
name: "小猫猫"
},
bool: [1,2,3,4]
};
observe(obj);
new Watcher(obj, "data.data.arg",(res) =>{
console.log("arg的值被改为了"+res);
})
obj.data.data.arg = 24;
document.getElementById("text").innerHTML = obj.data.name;
}
export default output
这里 我们new了一个Watcher类实例 他弟第一个参数 要监听那个对象 我们传了obj 第二个 要监听哪一个具体字段 这里 我们指向了 data.data.arg
这里 大家可以仔细看一下Watcher中的parsePath函数 做的正式 一层一层去把他找到的一个事情 然后 第三个参数 当数据改变触发依赖时 要做的事情
这里 我们简单输出了一下 arg的值被改为了 加他的新的值
监听完 我们也是马上写了 obj.data.data.arg = 24; 去修改监听的值来触发依赖
我们运行代码
然后 我们再改成这样
import { observe } from "./dataResp"
import Watcher from "./Watcher"
const output = () => {
var obj = {
data: {
data: {
map: {
dom: {
isgin: true
}
},
arg: 13
},
name: "小猫猫"
},
bool: [1,2,3,4]
};
observe(obj);
new Watcher(obj, "data.data.arg",(res) =>{
console.log("arg的值被改为了"+res);
})
new Watcher(obj, "data.name",(res) =>{
console.log("name的值被改为了"+res);
})
obj.data.data.arg = 24;
obj.data.name = "大猫猫";
obj.data.data.arg = 24;
document.getElementById("text").innerHTML = obj.data.name;
}
export default output
同一时间多监听一个 并来回调一次